Target.comは、一見するとスクレイピングしやすそうに見えるサイトです。ですが、実際に手を動かしてみると話は別です。RequestsとBeautifulSoupでさっとPythonスクリプトを書き、Targetの商品ページに投げたのに、価格フィールドが None で返ってきたことがあるなら、それはあなただけではありません。
主要な小売サイトの多くでスクレイピング手法を試してきた経験から断言できますが、Targetはかなり厄介な部類です。月間訪問数は にのぼり、価格、評価、在庫、レビューといった商品データの宝庫ではあります。ただし、Reactベースのクライアントサイド描画とAkamaiのボット検知が組み合わさっているため、安易な方法ではほぼ即座に失敗します。とはいえ、Pythonで実際に使える方法は3つあります。ここではそれぞれを解説し、最初の試みがなぜ必ず壊れるのかを説明し、Pythonにそこまで手間をかけたくない場合のノーコードの近道も紹介します。
なぜ最初のPythonスクレイピングでTarget.comはNoneを返すのか
解決策の前に、まず問題点から見ていきましょう。初心者がよく書くコードはこんな形です。
1import requests
2from bs4 import BeautifulSoup
3> This paragraph contains content that cannot be parsed and has been skipped.
4price = soup.select_one('[data-test="current-price"]')
5print(price) # None
出力は? None です。毎回そうなります。
これはコードのバグではありません。requests.get() がTargetから返すHTMLは、要するに骨組みだけです。「実際のページを描画するには、このJavaScriptを読み込んでね」と言っているReactの外枠にすぎません。商品価格、評価、レビュー、在庫状況はすべて、最初のページ読み込みの後にJavaScriptで注入されます。PythonのRequestsライブラリはJavaScriptを実行しないため、それらの要素はレスポンス内にそもそも存在しません。
フォーラムには、この壁にぶつかった開発者の投稿があふれています。ある では、こう明言しています。「要素がNoneとして表示されるのは、JavaScriptでレンダリングされており、requestsではJavaScriptでレンダリングされたHTMLを取得できないからだ。」また、 でも、「TargetのURLにHTTPリクエストを送っても、HTMLレスポンスには意味のあるデータが含まれていない」と確認されています。
さらに、JavaScriptの問題を解決したとしても、もう一段あります。TargetのAkamaiボット検知はTLSハンドシェイクの指紋を解析し、HTMLが1バイトもやり取りされる前にPythonの requests ライブラリを弾いてきます。この点は後ほど詳しく説明します。
Target.comがPythonスクレイピングで難しい理由
Targetは単に「JavaScriptを使っているサイト」ではありません。層のある防御システムです。そして、各層を理解することが、適切なスクレイピング手法を選ぶ近道になります。
JavaScriptで描画される商品データ
Target.comはReactで構築されています。実際のブラウザで商品ページや検索ページを開くと、次のような流れになります。
- サーバーが最小限のHTMLシェルを返す
- JavaScriptバンドルが読み込まれて実行される
- フロントエンドがTarget内部のRedsky APIを呼び出す
- 商品データ(価格、評価、画像、在庫状況)がDOMに描画される
2〜4を飛ばしてしまうと、つまり requests.get() がやっていることそのものですが、空のページしか得られません。 では、静的HTTPリクエストで取得できるのはTargetの利用可能データの約 にすぎません。残りの70%にはJavaScriptの実行かAPIアクセスが必要です。
検索結果ページはさらに厳しいです。最初のHTMLにはごく少数の商品しか表示されず、残りはスクロールに応じて読み込まれます。
Targetのボット対策: 「プロキシを使えばいい」で済まない理由
多くのスクレイピングガイドは、ボット対策を「とりあえずプロキシを使えばいい」と雑に扱いがちです。しかしTargetの防御は、もっと具体的に見る必要があります。
TLSフィンガープリント(最大の難関)。 HTTPSハンドシェイク中、クライアントは「Client Hello」パケットを送信します。そこにはTLSバージョン、暗号スイート、拡張、楕円曲線の情報が含まれます。これらはJA3フィンガープリントとしてハッシュ化されます。Pythonの requests ライブラリは — 8d9f7747675e24454cd9b7ed35c58707 — を生成するため、ボット対策データベースに即座にフラグを立てられます。ChromeはGREASE値付きで慎重に並べられた16個の暗号スイートを送りますが、Pythonはブラウザとは異なる順序で60個以上を送ります。ブロックはHTTP本文のやり取りが始まる前に起きます。
IPレピュテーションのスコアリング。 AkamaiはIPを信頼レベルごとに分類します。データセンターのIPは、、「ボットに使われる可能性が高いため、かなり強いマイナスの信頼スコア」を受けます。住宅回線のIPはプラス評価になります。Targetでは特に、データセンターIP帯が即座にフラグ付けされます。
JavaScriptフィンガープリント。 AkamaiはJavaScriptを注入し、JSエンジンの仕様、ハードウェア性能、OS情報、フォント、プラグイン、行動データ(入力速度、マウス移動、クリックのタイミング)を収集します。これにより _abck クッキーが生成されます。これは状態を持つフィンガープリントトークンです。有効な _abck がなければリクエストはブロックされます。
レート制限。 Targetは、IPあたり毎分およそ30〜60リクエストで429エラーを発生させます。中には、実際には「Pardon Our Interruption」のブロックページなのに ケースも報告されており、自動検知を難しくしています。
全体として、 しています。特にAkamai回避は とされています。
PythonでTarget.comをスクレイピングする3つの方法を比較
3つの実用的なアプローチを1か所でまとめて比較した記事は、意外と多くありません。ここで率直に比べてみましょう。
This paragraph contains content that cannot be parsed and has been skipped.
では、1つずつ作っていきましょう。
方法1: PythonのRequestsとBeautifulSoupでTarget.comをスクレイピングする
この方法では、JavaScriptで描画される検索ページの価格までは取得できません。ただし、動作は軽くて速く、探し方さえ知っていれば意外と多くの情報が取れます。
ポイントは、Targetが <script> タグ内に __PRELOADED_QUERIES__ を含む __TGT_DATA__ 変数として、一部の商品データを埋め込んでいることです。このJSONブロブには、商品名、説明、特徴、場合によっては個別商品ページの価格まで入っています。検索結果HTMLから商品タイトルやURLを拾うこともできます。
ステップ1: Python環境を準備する
プロジェクトフォルダを作成し、依存関係をインストールします。
1mkdir target-scraper && cd target-scraper
2python -m venv venv
3source venv/bin/activate # Windowsの場合: venv\Scripts\activate
4pip install requests beautifulsoup4 curl_cffi
ここでは標準の requests ではなく curl_cffi を使いましょう。ブラウザのTLSフィンガープリントを偽装できるため、Targetでブロックを避けるうえで最も大きな差になります。 curl_cffi のボット回避率は なのに対し、標準の requests は にすぎません。つまり15倍の改善です。
ステップ2: Targetの検索結果をスクレイピングする
Targetの検索URL形式はシンプルです: https://www.target.com/s?searchTerm={keyword}
1from curl_cffi import requests as cureq
2from bs4 import BeautifulSoup
3import time, random
4> This paragraph contains content that cannot be parsed and has been skipped.
5url = "https://www.target.com/s?searchTerm=bluetooth+headphones"
6resp = cureq.get(url, headers=headers, impersonate="chrome124")
7soup = BeautifulSoup(resp.text, "html.parser")
8> This paragraph contains content that cannot be parsed and has been skipped.
9商品名とURLは取得できます。価格は? おそらくこのHTMLからは取れません。これは想定内です。
10### ステップ3: 商品ページの埋め込みJSONデータを抽出する
11個別の商品ページでは、`__TGT_DATA__` スクリプトタグ内により豊富なデータが埋め込まれています。
12```python
13import re, json
14product_url = "https://www.target.com/p/some-product/-/A-12345678"
15resp = cureq.get(product_url, headers=headers, impersonate="chrome124")
16soup = BeautifulSoup(resp.text, "html.parser")
17> This paragraph contains content that cannot be parsed and has been skipped.
18`__TGT_DATA__` 内のJSON構造には、商品名、説明、特徴、そして多くの場合価格データが含まれます。ネストの形は正確にはページごとに異なるため、出力を見ながら必要な場所へたどる必要があります。
19### ステップ4: ページネーションを処理する
20Targetの検索ページネーションは `Nao` パラメータを使います。1ページ目は `Nao=0`、2ページ目は `Nao=24`、3ページ目は `Nao=48` という具合で、24ずつ増えます。
21```python
22for page in range(0, 120, 24): # 最初の5ページ
23 paginated_url = f"https://www.target.com/s?searchTerm=bluetooth+headphones&Nao={page}"
24 resp = cureq.get(paginated_url, headers=headers, impersonate="chrome124")
25 # 解析して抽出...
26 time.sleep(random.uniform(2, 5)) # 丁寧に扱う
ステップ5: スクレイピングしたデータを保存する
1import csv
2with open("target_products.csv", "w", newline="", encoding="utf-8") as f:
3 writer = csv.DictWriter(f, fieldnames=["title", "url", "price", "description"])
4 writer.writeheader()
5 for product in products:
6 writer.writerow(product)
取得できるもの: 商品タイトル、URL、説明、埋め込みメタデータ。安定しては取得できないもの: 検索結果ページの動的価格や評価。これらが必要なら、方法2か3を使ってください。
方法2: SeleniumまたはPlaywrightでTarget.comをスクレイピングする
ヘッドレスブラウザはJavaScriptを描画し、動的コンテンツを読み込み、実際のユーザーのように振る舞います。価格、評価、レビューを取得したいなら、この方法です。
SeleniumとPlaywrightのどちらがよいかについては、 — 2026年時点で — さらにベンチマークでは (20ページで11秒対28秒)です。ここではコミュニティとチュートリアルの多さからSeleniumを紹介しますが、今から始めるならPlaywrightの方がより良い選択です。
ステップ1: SeleniumとChromeDriverをインストールする
1pip install selenium webdriver-manager
webdriver-manager がChromeDriverのバージョン管理を自動で行ってくれるので、「ChromeDriverのバージョン不一致」に悩まされることはありません。
1from selenium import webdriver
2from selenium.webdriver.chrome.service import Service
3from selenium.webdriver.chrome.options import Options
4from webdriver_manager.chrome import ChromeDriverManager
5options = Options()
6options.add_argument("--headless=new")
7options.add_argument("--window-size=1920,1080")
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
10driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
ステップ2: Targetページを読み込み、コンテンツを待つ
1from selenium.webdriver.common.by import By
2from selenium.webdriver.support.ui import WebDriverWait
3from selenium.webdriver.support import expected_conditions as EC
4driver.get("https://www.target.com/s?searchTerm=bluetooth+headphones")
5# 商品カードの描画を待つ(time.sleepよりexplicit waitの方が良い)
6WebDriverWait(driver, 15).until(
7 EC.presence_of_element_located((By.CSS_SELECTOR, '[data-test="product-title"]'))
8)
明示的な待機は重要です。time.sleep(10) では、速い読み込みでは時間を無駄にし、遅い読み込みでは足りません。両方の悪いところ取りです。WebDriverWait は要素が現れるかタイムアウトするまで500msごとに確認します。
ステップ3: ページをスクロールして全商品を読み込む
Targetはスクロールに応じて商品を遅延読み込みします。スクロールしないと、ページ全体ではなく4〜5商品しか取れません。
1import time
2last_height = driver.execute_script("return document.body.scrollHeight")
3for _ in range(10):
4 driver.execute_script("window.scrollBy(0, 300);")
5 time.sleep(1.5)
6 new_height = driver.execute_script("return document.body.scrollHeight")
7 if new_height == last_height:
8 break
9 last_height = new_height
、1.5秒の待機を挟んで10回スクロールすると、スクロールなしの4〜5商品に対して8商品以上が取得できることが確認されています。各スクロールは、人間らしく見せるために200〜300px程度にするのがよいです。
ステップ4: レンダリング済みページから商品データを抽出する
1products = []
2cards = driver.find_elements(By.CSS_SELECTOR, '[data-test="@web/site-top-of-funnel/ProductCardWrapper"]')
3for card in cards:
4 try:
5 title = card.find_element(By.CSS_SELECTOR, '[data-test="product-title"]').text
6 except:
7 title = "N/A"
8 try:
9 price = card.find_element(By.CSS_SELECTOR, '[data-test="current-price"]').text
10 except:
11 price = "N/A"
12 try:
13 link = card.find_element(By.CSS_SELECTOR, 'a[href*="/p/"]').get_attribute("href")
14 except:
15 link = "N/A"
16> This paragraph contains content that cannot be parsed and has been skipped.
17for p in products:
18 print(f'{p["title"]} — {p["price"]}')
Target向けの主要な data-test セレクタ(2026年確認済み)はこちらです。
This paragraph contains content that cannot be parsed and has been skipped.
ステップ5: 商品レビューをスクレイピングする(おまけ)
個別の商品ページへ移動し、レビューセクションまでスクロールしてレビュー情報を抽出します。
1from bs4 import BeautifulSoup
2driver.get("https://www.target.com/p/some-product/-/A-12345678")
3# レビューを読み込むために下へスクロール
4for _ in range(5):
5 driver.execute_script("window.scrollBy(0, 500);")
6 time.sleep(2)
7> This paragraph contains content that cannot be parsed and has been skipped.
8レビューはBazaarvoiceの統合で読み込まれ、ページネーション(最大51ページ)、新着順ソート、写真のみフィルタに対応しています。[ScrapeOpsのベンチマーク](https://scrapeops.io/) では、Seleniumで1件あたり約5.1秒とされています。
9最後にブラウザを閉じるのを忘れないでください。
10```python
11driver.quit()
方法3: Redsky APIを使ってTarget.comをスクレイピングする
Targetのフロントエンドは、redsky.target.com にある内部APIからすべてを取得しています。Pythonから直接呼び出せば、HTML解析もブラウザもJavaScript描画も不要です。レスポンスはきれいなJSONで、価格、評価、レビュー、画像、在庫、配送、仕様、バリエーションまで含む40以上のデータフィールドがあります。大量の商品データを扱うなら、これが圧倒的に速く、信頼性も高い方法です。
ステップ1: Chrome DevToolsでRedsky APIを見つける
多くのチュートリアルはここを完全に飛ばします。自分でAPIを見つける方法はこうです。
- Chromeで任意のTarget商品ページを開く
- DevTools(F12)を開き、Network タブへ移動する
- Fetch/XHR でフィルタする
- ページを再読み込みする
redsky.target.comまたはredsky.a]target.comへのリクエストを探す- 1つクリックして Request URL と Headers を確認する
すると、次のようなURLが見つかります。
1https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key=9f36aeafbe60771e321a7cc95a78140772ab3e96&tcin=12345678&store_id=2148&zip=55401
主なパラメータは以下です。
key— APIキー(静的で、頻繁には変わらない。エンドポイントごとに異なるキーを使う)tcin— Target.com Item Number(8桁の商品ID)store_id— Target店舗の場所zip— 配送情報用のZIPコード
APIキーはリクエストヘッダーから抽出できます。URL内のクエリパラメータとして埋め込まれています。
ステップ2: PythonからRedsky APIへ直接リクエストする
1from curl_cffi import requests as cureq
2import json
3API_KEY = "9f36aeafbe60771e321a7cc95a78140772ab3e96" # DevToolsから抽出
4TCIN = "12345678"
5url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={TCIN}&store_id=2148&zip=55401"
6> This paragraph contains content that cannot be parsed and has been skipped.
7resp = cureq.get(url, headers=headers, impersonate="chrome124")
8data = resp.json()
9# JSONレスポンスから商品詳細を抽出する
10product = data.get("data", {}).get("product", {})
11title = product.get("item", {}).get("product_description", {}).get("title", "N/A")
12price = product.get("price", {}).get("formatted_current_price", "N/A")
13rating = product.get("ratings_and_reviews", {}).get("statistics", {}).get("rating", {}).get("average", "N/A")
14print(f"{title} — {price} — 評価: {rating}")
HTML解析は不要です。レスポンスは構造化されており、きれいで、高速です。
ステップ3: API経由で検索結果の商品を取得する
product_summary_with_fulfillment_v1 エンドポイントは、複数のTCINをまとめて受け付けます。
1tcins = ["12345678", "23456789", "34567890"]
2tcin_str = ",".join(tcins)
3search_url = f"https://redsky.target.com/redsky_aggregations/v1/web/product_summary_with_fulfillment_v1?key={API_KEY}&tcins={tcin_str}&store_id=2148&zip=55401"
4resp = cureq.get(search_url, headers=headers, impersonate="chrome124")
5results = resp.json()
6for item in results.get("data", {}).get("product_summaries", []):
7 title = item.get("title", "N/A")
8 price = item.get("price", {}).get("formatted_current_price", "N/A")
9 print(f"{title} — {price}")
TCINを取得するには、検索ページのHTMLから拾う方法(商品URL内に /A-XXXXXXXX として出てくる)か、__TGT_DATA__ に埋め込まれたJSONから取る方法があります。
ステップ4: 並列リクエストでスケールさせる
1from concurrent.futures import ThreadPoolExecutor
2import time, random
3def fetch_product(tcin):
4 url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={tcin}&store_id=2148&zip=55401"
5 time.sleep(random.uniform(2, 5))
6 resp = cureq.get(url, headers=headers, impersonate="chrome124")
7 return resp.json()
8tcin_list = ["12345678", "23456789", "34567890", "45678901"]
9with ThreadPoolExecutor(max_workers=3) as executor:
10 results = list(executor.map(fetch_product, tcin_list))
並列数は控えめに保ちましょう。3〜5スレッドで、2〜5秒のランダム遅延を入れるのが無難です。Targetのレート制限は、 程度です。
Redsky APIに関する重要な注意点
本番パイプラインを組む前に、いくつか注意点があります。
- APIキーは静的ですが、エンドポイントごとに異なります。 Redskyの各エンドポイントは別々のキーを使います。頻繁には変わりませんが、Targetがいつ変更してもおかしくありません。
- これは文書化されていない内部APIです。 Targetのエンジニアリングチームは が、SLA付きの公式公開APIではありません。
- 商品バリエーション(色、サイズ)ごとに固有のTCINがあります。 各バリエーションを個別に問い合わせる必要があります。
Sec-Fetch-*ヘッダーがないと即ブロックされます。 これはよくある落とし穴なので、Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-Destは必ず入れてください。
ブロックされずにTarget.comを大規模スクレイピングするコツ
以下の実践は、どの方法でも本番運用時に役立ちます。
データセンターではなく住宅プロキシをローテーションする
TargetのAkamai実装は、データセンターIP帯を見つけるとすぐにフラグ付けします。継続的なスクレイピングには住宅プロキシが必須です。価格は幅がありますが、、 から始まり、大量利用では3〜4ドル/GB程度まで下がります。
IPは50〜100リクエストごと、あるいはプロキシプールが対応しているなら毎リクエストごとにローテーションしましょう。
curl_cffi でTLSフィンガープリントを偽装する
これが最も効果の大きい変更です。requests の置き換えとしてそのまま使えます。
1from curl_cffi import requests as cureq
2# 標準requests — 保護サイトでの成功率は12%
3# resp = requests.get(url, headers=headers)
4# curl_cffi — 成功率92%
5resp = cureq.get(url, headers=headers, impersonate="chrome124")
はGitHubスター8,200以上を持ち、chrome99 から chrome146 までのChromeバージョンに加え、Safari、Edge、モバイル版にも対応しています。同期モードでは です。
現実的なリクエスト間隔とヘッダーを設定する
- ランダム遅延: リクエスト間は2〜7秒(固定間隔ではなく、ランダム性が重要)
- User-Agentのローテーション: 実在するブラウザのUser-Agent文字列を5〜10個用意して順番に使う
- セッションのウォームアップ: 商品ページに行く前に
target.comのトップページを開き、クッキーを確立する - ヘッダーの整合性:
Sec-Ch-Uaは自称しているUser-Agentのブラウザバージョンと一致している必要があります。Sec-Ch-Ua-Platformも自称OSと一致していなければなりません。不一致は一発で見抜かれます。 - セッションの継続: セッション内ではクッキーを維持する。 住宅プロキシをローテーションしつつ、48時間のセッション安定性を推奨しています。
コードを書かずに済ませる: ThunderbitでTarget.comをスクレイピングする(ノーコード代替)
Target.comは、正直なところ、プログラムでスクレイピングするにはかなり難しい小売サイトの一つです。JavaScript描画、AkamaiのTLSフィンガープリント、データセンタープロキシ検知、ChromeDriverのバージョン問題など、気にする要素が多すぎます。Pythonを学ぶなら良い練習になりますが、実務でTargetの商品データが必要なだけなら、コストと労力が見合わないことも多いです。
エンジニアリング作業なしでデータだけ欲しい読者には、 が難しい部分を自動で処理します。
ThunderbitがTarget.comの課題をどう解決するか
ThunderbitのAIウェブスクレイパーはブラウザ内で動作するため、JavaScriptを自然にレンダリングします。Seleniumのセットアップも、ヘッドレスブラウザ設定も、ChromeDriverのバージョン管理も不要です。ブラウザ自体がスクレイパーです。
使い方は次の通りです。
- をインストールし、Targetの商品ページまたは検索ページを開く
- 「AIで項目を提案」 をクリックする — Thunderbitがページを読み取り、列名(商品名、価格、評価、画像URLなど)を提案する
- 「スクレイプ」 をクリックする — 描画済みページから数秒でデータを抽出
プロキシ設定は不要です。TLSフィンガープリントの偽装も不要です。None の結果ともお別れです。
Targetの商品一覧ページと詳細ページをスクレイピングする
複数ページのワークフローが特に便利です。Targetの検索結果ページから商品一覧を取得し、さらに サブページスクレイピング を使って各商品URLへ自動で移動し、ページネーションのコードもブラウザセッション管理も書かずに、説明文、レビュー全文、仕様などでテーブルを強化できます。
Excel、Google スプレッドシート、Airtable、Notion に直接エクスポートできます。csv.writer のお決まりコードも、ファイルの文字化け対策も不要です。
定期的なTarget.comスクレイピングを自動化する
継続的な価格監視や在庫追跡には、Thunderbitの スケジュールスクレイパー が便利です。スケジュールを自然言語で書くだけで(例: 「毎週月曜の午前9時」)、cronジョブもサーバー設定も、VPS上でPythonスクリプトを生かし続ける必要もありません。これは特に、 ECチームに有用です。 がすでに自動価格スクレイピングを使っており、価格インテリジェンスのROIは平均 だとされています。
PythonでTarget.comをスクレイピングする際に、どの方法を使うべきか
簡単な判断フレームはこちらです。
This paragraph contains content that cannot be parsed and has been skipped.
本番用のデータパイプラインを作るなら、方法3(Redsky API)が速度と信頼性の両面で最適です。単発の調査や、チームにPythonの専門知識がない場合は、Thunderbitで何時間も節約できます。ウェブスクレイピングを学びたいなら、方法1 → 方法2 → 方法3 の順に進むと、各段階で実践的な学びがあります。
Target.comをスクレイピングする際の法的・倫理的な考慮事項
ここは簡単に触れておきます。Targetの robots.txt には約120以上のDisallowパスがありますが、注目すべきことに /p/(商品)や /c/(カテゴリ)はブロックしていません。つまり商品ページとカテゴリページは、クロールが明示的に許可されています。カート、アカウント、チェックアウトページは制限されています。
Targetの利用規約は自動アクセスを禁止しています。ただし、Redsky APIが とTargetのエンジニアリングが認めているため、APIベースのデータ収集については法的リスクがやや下がります。
知っておくべき主な法的先例は以下です。
- (第9巡回区、2022年): 公開データのスクレイピングはCFAA違反ではない
- (2024年): Metaは敗訴 — 公開データのスクレイピングでCFAA違反は認められなかった
大規模な商用スクレイピングを行う場合は、法律専門家に相談してください。市場調査、価格比較、公開データを使った個人プロジェクトであれば、比較的安全な範囲にあります。レート制限は必ず守り、Targetのサーバーに負荷をかけすぎないようにしましょう。
結論と重要ポイント
Target.comが難しいと評価されるのには、それなりの理由があります。安易なRequests + BeautifulSoupの方法が失敗するのは、Targetが商品データをJavaScriptで描画し、レスポンスを受け取る前にAkamaiがTLSハンドシェイクの指紋を見ているからです。ただし、適切な方法を使えば抽出はかなり素直になります。
信頼性順に並べた3つの方法:
- Redsky API — 大量データ向けで最速かつ最も信頼性が高く、きれいなJSONを返します。DevToolsでAPIエンドポイントをリバースエンジニアリングする必要があります。
- Selenium / Playwright — JavaScript描画を処理し、ページ上の情報をほぼすべて取得できます。遅いですが網羅的です。
- Requests + BeautifulSoup — 静的HTMLと埋め込み
__TGT_DATA__JSONに限定されます。速いですが不完全です。
技術面で特に効く改善点:
- 標準の
requestsではなくcurl_cffiを使い、 を得る - 住宅プロキシは必須。データセンターIPは即座にフラグ付けされる
- すべてのリクエストに
Sec-Fetch-*ヘッダーを入れる。欠けていると即ブロックされる - セッションのウォームアップ(最初にトップページを開く)が成功率を大きく上げる
そして、もしあなたの用途に対してPythonで頑張る価値がないなら、 がJavaScript描画、ボット対策、データ出力を自動で処理します。 を試して、数時間ではなく数分で必要なデータが取れるか確かめてみてください。
さらに詳しいスクレイピングガイドやデータ抽出のコツは、 や も参考になります。
よくある質問
PythonのRequestsとBeautifulSoupだけでTarget.comをスクレイピングできますか?
部分的には可能です。商品ページの __TGT_DATA__ スクリプトタグから、商品タイトル、URL、一部の埋め込みJSONデータは取得できます。ただし、検索結果ページの価格、評価、レビュー、在庫状況はJavaScriptで描画されるため、静的HTTPリクエストでは表示されません。完全なデータが必要なら、Selenium/PlaywrightかRedsky APIを使ってください。
なぜTarget.comスクレイパーは価格をNoneで返すのですか?
Targetは、最初のページ読み込みの後にJavaScriptで価格データを読み込みます。requests.get() を使うと、JavaScriptが実行される前のプレレンダリングHTMLシェルを受け取るだけです。つまり、価格要素はレスポンス内に物理的に存在しません。JavaScriptをレンダリングできるヘッドレスブラウザ(SeleniumやPlaywright)を使うか、Redsky APIを直接呼び出してJSONデータを取得するか、あるいはレンダリング済みブラウザページからスクレイピングする のようなツールを使ってください。
Target.comをスクレイピングするのは合法ですか?
公開されているデータのスクレイピングは、現在の米国判例法では一般的に認められています(hiQ v. LinkedIn、Meta v. Bright Data)。Targetの robots.txt も商品ページとカテゴリページのクロールを許可しています。ただし、Targetの利用規約では自動アクセスが禁止されているため、グレーゾーンではあります。公開データを使った市場調査や価格比較であれば、法的には比較的妥当な範囲です。大規模な商用運用では、弁護士に相談してください。
TargetのRedsky APIとは何で、どうやって使うのですか?
Redskyは、Targetのフロントエンドの商品データを支える内部APIです。公開APIのようにドキュメントや申請用キーがあるわけではなく、Reactアプリが商品ページを描画する際に呼び出すバックエンドです。Chrome DevToolsを開き、NetworkタブをXHR/Fetchでフィルタし、redsky.target.com へのリクエストを探せば、エンドポイントを見つけられます。APIキーはリクエストURLのクエリパラメータとして埋め込まれています。Targetのエンジニアリングも、このAPIが意図的に公開向けであることを確認しています。
Target.comでブロックされないようにするにはどうすればいいですか?
最も効果が大きいのは、標準のPython requests の代わりに curl_cffi を使ってブラウザのTLSフィンガープリントを偽装することです。これだけで成功率は 上がります。加えて、住宅プロキシを使う、User-Agent文字列をローテーションする、リクエスト間に2〜7秒のランダム遅延を入れる、Sec-Fetch-* ヘッダーをすべて付ける、最初にトップページを開いてセッションを温める、という対策が有効です。あるいは、 のようにボット対策を自動処理してくれるツールを使えば、設定なしで済みます。
さらに詳しく