Amazon書籍メタデータを841件取得しNuxt書棚UIで一覧化した1日の試行錯誤
Dropboxに No001_〜No863_ の連番で蓄積した書籍PDF 935冊について、Amazonから星評価・レビュー数・出版社・ISBN-13・ページ数・寸法・高解像度カバー画像を取得し、Nuxtで書棚UIを組んで /shelf で公開した。1日で 841件のメタデータ取得とUI公開まで到達した。
途中、PA-AIIで弾かれ、Chrome DevTools MCPで並列の壁にぶつかり、agent-browserの3並列でスタックし、最終的に curl_cffi のTLS偽装で10並列の HTTP 直叩きに行き着いた。star_rating 取得は約12分、841件中 789件で星評価とレビュー数を抽出できた。
出発点: 過去のPA-API資産と44MBのJSON
amazonAPI フォルダに過去スクリプトが残っていた。books_with_review.json は44MB、212件分のデータが入っている。ファイル名に「レビュー」とついているのに中身を開いてもレビューデータは見当たらない。タイトルとメタデータの一部だけ。残りを取りに行く必要があった。
連番フォルダは935冊。重複を除くとユニークが841件。これを当日中にメタデータ取得→UI公開まで持っていくのが目標だった。
試行錯誤1: PA-API 5.0で 403 が返る
過去スクリプトを流用してPA-AIPを叩いたら AssociateNotEligible (403) が返ってきた。アフィリエイトの承認は通っているはずだが、最近の qualifying sales がカウント対象期間から外れているらしい。一定期間売上がないと再度ロックがかかる仕様のようで、この日のうちに解除する手段はなかった。
PA-APIは諦めた。
試行錯誤2: Chrome DevTools MCP は並列化できない
次に Chrome DevTools MCP で Amazon の商品ページを開いて DOM から拾う方針に切り替えた。10件で疎通確認したところ、星評価4.0/レビュー数549件のような値がきれいに取れた。ここまでは順調。
ところが3並列で回そうとした瞬間、根本的な問題に気づいた。Chrome DevTools MCP は単一ブラウザインスタンスを共有する。同時に複数ページを開いても、操作は直列にしか流れない。並列化が原理的に不可能だった。
10件の検証だけで終わり。
試行錯誤3: agent-browser 3並列でスタック
agent-browser ならデーモンを複数起動できるから並列にできるはず、と切り替えた。3並列で起動したところ、何かのタイミングで噛み合わずスタックした。1並列なら確実に動くが、それだと841件取り終わるのに数時間かかる。
デーモン同時起動でブラウザのプロファイルロックか何かがぶつかっているように見えたが、深追いせず別の方針に行くことにした。
試行錯誤4: curl_cffi で TLS 偽装すれば bot 検知を抜けられる
ヘッドレスブラウザを諦めて、HTTP 直叩きに切り替えた。普通の requests だと Amazon の bot 対策で 503 か CAPTCHA に飛ばされる。curl_cffi は curl の TLS フィンガープリントを Chrome に偽装してくれるので、これで突破できないかと試した。
from curl_cffi import requests
r = requests.get(
f"https://www.amazon.co.jp/dp/{asin}",
impersonate="chrome120",
timeout=15,
)
html = r.text
10並列で回したら 0.98秒/件 のスループットが出た。Chrome DevTools MCP の数十倍速い。これで本実行に進める判断ができた。
バグ: aria-label の正規表現でレビュー数に「5」が紛れる
実装初期に妙な値が混じっていた。レビュー数が「5」と記録される本が複数ある。星5つの本が偶然多いはずもないので、抽出ロジックを疑った。
商品ページには aria-label="5つ星のうち4.1、評価詳細" のような属性が複数箇所にある。最初の正規表現は数字を貪欲に拾ってしまい、「5つ星のうち」の「5」をレビュー数として誤抽出していた。
条件を「『レーティング』を含む aria-label のみを対象にする」に絞り直して解決した。
# Before: 全aria-labelから数字を拾うので「5」を誤抽出
m = re.search(r'aria-label="[^"]*?(\d[\d,]*)"', html)
# After: レーティング文脈のみに限定
m = re.search(
r'aria-label="[^"]*?レーティング[^"]*?(\d[\d,]*)\s*個',
html,
)
aria-label の正規表現は「特定キーワードが先に来ること」を前段の条件にしないと、別の数字を拾う。覚えておく。
本実行: 826件を約12分で処理
修正後、826件を10並列で流したところ約12分で完走した。1秒/件を切る並列の威力を体感した。
| 区分 | 件数 |
|---|---|
| 処理対象(連番フォルダ内ユニーク) | 841 |
| star_rating 取得成功 | 789 |
| ASIN見つからず | 52 |
WALロックが途中で発生し318件がリトライ待ちになったが、並列度を下げて再実行したら全件完走した。SQLiteの並列書き込みは、ロックしたら下げて再試行が確実だと再確認した。
Phase A: 詳細メタデータの取得とCAPTCHA
星評価が取れたので、出版社・発売日・ページ数・ISBN-13・寸法・言語・高解像度カバー画像を全件取得するフェーズに入った。1.4秒/件、5並列で約4分で終わる試算。
448件を取得した時点で CAPTCHA が連続して飛んできた。短時間に詳細ページを叩きすぎたらしい。残り341件は「並列度2 + リクエスト間 sleep を 20-30秒」に設定し直してバックグラウンドで再実行に投入した。
別セッションでも続きを流せるよう、引き継ぎ用のマークダウン memo/2026-04-28/amazon-progress.md を残した。バックグラウンド実行は開始ログ以降の進捗が見えにくく、「本当に走っているか」を確認する手段が課題として残った。
Nuxt 書棚UI を /shelf で公開
メタデータが揃ってきたので、book-knowledge-base 側の Nuxt で書棚UIを組んだ。ポート3000には mdx-playground が常駐しているため、3001 で起動した。ローカル開発を複数走らせるとポート衝突するので、事前確認を習慣にする。
UIの仕様:
- 並び順デフォルト: 星評価 DESC → レビュー数 DESC
- フィルター: 出版社別、評価カテゴリ別
- 表示要素: 高解像度カバー画像、星評価、レビュー数、出版社、ISBN-13
- レイアウト: 1画面に多くを詰めて一覧性を優先
amazon_metadata テーブルと既存の books テーブルを book_id で紐付けたところ、30件中 19件で紐付けが成功した。残り11件は連番フォルダ外のソースから入った書籍で、book_id の生成規則が違うため、後日別ロジックで紐付ける。
試行錯誤テーブル
| # | テーマ | 試したこと | 結果 | 気づき |
|---|---|---|---|---|
| 1 | PA-API 疎通 | 過去スクリプト流用 | 403 AssociateNotEligible | アフィリエイト承認後でも一定期間使えない |
| 2 | Chrome DevTools MCP | 1件成功、3並列でスタック | 並列不可 | 単一ブラウザ共有なので原理的に並列化できない |
| 3 | agent-browser 並列 | 3並列で詰まる | 失敗 | デーモン同時起動で何か噛み合わない |
| 4 | curl_cffi 直接GET | TLS偽装で bot 検知回避 | 0.98秒/件 | これが最速・最安定 |
| 5 | レビュー数バグ | aria-label「5つ星のうち4.1」の「5」を誤抽出 | 「レーティング」のみに絞って解決 | aria-label の正規表現は前後の文脈が必要 |
| 6 | WAL ロック | DB登録中に発生 | 318件リトライで全件完走 | 並列度を下げて再実行が確実 |
| 7 | CAPTCHA | 詳細メタ取得で連続発生 | 並列度2 + sleep 20-30秒で再取得 | 高頻度アクセスはCAPTCHAを誘発する |
| 8 | ポート3000衝突 | mdx-playground が稼働中 | 3001で起動 | ローカル開発では事前にポート確認 |
| 9 | バックグラウンド進捗 | 「本当に走ってる?」 | 開始ログ以降進捗が出ない | 進捗の可視化手段が必要 |
学び
- Amazon のメタデータ取得は、PA-API より
curl_cffiのTLS偽装+HTMLパースの方が速くて安定した。承認やキー管理も不要で、即座に動かせる - Chrome DevTools MCP は単発検証用と割り切る。量産には agent-browser か HTTP 直叩きを選ぶ
- aria-label の正規表現は「特定キーワードを前段条件に置く」ルールを守る。今回は「レーティング」を前に置かなかったため「5つ星のうち」の「5」を拾ってしまった
- 12分で 841件 = 1秒/件のスループットは、並列度10で初めて成立する。ヘッドレスブラウザでは到達できない
- CAPTCHA は並列度を下げて sleep を 20-30秒入れれば収まる。頻度の問題なので、止まったら下げる
- 引き継ぎマークダウンを
memo/に残しておくと、別セッションで続きを流せる。バックグラウンド実行と相性が良い
数値結果
- 処理対象: 841件(連番フォルダ内ユニーク)
- star_rating 取得成功: 789件
- ASIN見つからず: 52件
- 処理時間: star_rating 約12分、メタデータ約4分(CAPTCHA再取得分はバックグラウンドで継続)
- books テーブルとの紐付け: 30件中 19件成功、残り11件は連番フォルダ外のソースで後日対応
次にやること
- 残り341件の詳細メタデータ取得をバックグラウンドで完走させる
- バックグラウンド実行の進捗を可視化する仕組みを入れる(行数カウントを定期出力する等)
- 連番フォルダ外の11件について、別の
book_id生成ロジックで紐付けする - 書棚UIに「レビュー数の多い順」「新刊順」のソートを追加する
- ASIN見つからず52件は、書籍タイトル検索で再度ASIN特定を試みる