Target.comの商品価格、評価、在庫、レビューをPythonで収集しようとすると、RequestsとBeautifulSoupだけでは価格フィールドが None になることがあります。これは単純なコードミスではなく、ページの描画方式やアクセス制御が関係しています。
主要な小売サイトの多くでスクレイピング手法を試してきた経験から、Targetは対応事項が多いサイトだといえます。月間訪問数は 2億800万〜2億8,000万 にのぼり、価格、評価、在庫、レビューといった商品データを扱えます。一方、本記事で前提とするTarget.comの構成では、Reactベースのクライアントサイド描画とAkamaiのボット検知が使われており、基本的なHTTPリクエストだけでは必要なデータを取得できない場合があります。
この記事では、Pythonで試せる3つの方法について、取得できるデータ、設定負荷、処理速度、運用上の注意点を比較します。RequestsとBeautifulSoupで最初の試みが失敗する理由を整理したうえで、ブラウザ自動化、内部API、Pythonを使わないノーコードの選択肢まで説明します。
なぜ最初のPythonスクレイピングでTarget.comはNoneを返すのか
解決策を選ぶ前に、まず None が返る仕組みを整理します。初心者がよく書くコードは次のような形です。
import requests
from bs4 import BeautifulSoup
url = "https://www.target.com/p/some-product/-/A-12345678"
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
soup = BeautifulSoup(response.text, "html.parser")
price = soup.select_one('[data-test="current-price"]')
print(price) # None
この例の出力は None です。
これはコードのバグとは限りません。本記事で扱うページ構成では、requests.get() がTargetから受け取るHTMLは初期表示用の骨組みで、商品価格、評価、レビュー、在庫状況は最初のページ読み込みの後にJavaScriptで追加されます。PythonのRequestsライブラリはJavaScriptを実行しないため、対象要素がレスポンス内に存在しない場合があります。
フォーラムには、この壁にぶつかった開発者の投稿があふれています。ある ScrapeOpsの分析 では、こう明言しています。「要素がNoneとして表示されるのは、JavaScriptでレンダリングされており、requestsではJavaScriptでレンダリングされたHTMLを取得できないからだ。」また、Crawlbaseのチュートリアル でも、「TargetのURLにHTTPリクエストを送っても、HTMLレスポンスには意味のあるデータが含まれていない」と確認されています。
JavaScriptを実行できるようにしても、アクセス制御への対応が残ります。本記事の検証条件では、TargetのAkamaiボット検知がTLSハンドシェイクの特徴を判定し、HTTP本文を受信する前にPythonの requests ライブラリからの通信を拒否する場合があります。この点は後ほど詳しく説明します。
Target.comがPythonスクレイピングで難しい理由
Targetでは、JavaScriptによる描画だけでなく、複数のアクセス制御が組み合わされています。各要因を分けて理解すると、必要なデータと運用条件に合う取得方法を選びやすくなります。
JavaScriptで描画される商品データ
Target.comはReactで構築されています。実際のブラウザで商品ページや検索ページを開くと、次のような流れになります。
- サーバーが最小限のHTMLシェルを返す
- JavaScriptバンドルが読み込まれて実行される
- フロントエンドがTarget内部のRedsky APIを呼び出す
- 商品データ(価格、評価、画像、在庫状況)がDOMに描画される
2〜4を飛ばしてしまうと、つまり requests.get() がやっていることそのものですが、空のページしか得られません。GroupBWTのベンチマーク では、静的HTTPリクエストで取得できるのはTargetの利用可能データの約 30% にすぎません。残りの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は、Scrapflyの表現を借りれば、「ボットに使われる可能性が高いため、かなり強いマイナスの信頼スコア」を受けます。住宅回線のIPは異なる評価を受ける可能性があり、TargetではデータセンターIP帯が検知対象になりやすいと報告されています。
JavaScriptフィンガープリント。 AkamaiはJavaScriptを通じて、JSエンジンの仕様、ハードウェア性能、OS情報、フォント、プラグイン、行動データ(入力速度、マウス移動、クリックのタイミング)を収集するとされています。これにより _abck クッキーが生成されます。これは状態を持つフィンガープリントトークンで、有効な _abck がない通信は拒否される場合があります。
レート制限。 Targetでは、IPあたり毎分およそ30〜60リクエストで429エラーが発生するという報告があります。また、実際には「Pardon Our Interruption」のブロックページでありながら 見かけ上は200 OKを返す ケースも報告されているため、ステータスコードだけで成功を判定しない設計が必要です。
難易度の目安として、ScrapeOpsはTargetの難易度を7/10と評価 しています。Akamaiへの対応については 9/10 とする評価もあります。
PythonでTarget.comをスクレイピングする3つの方法を比較
ここでは、3つの方法を、JavaScript描画への対応、速度、検知リスク、設定負荷、データ範囲の観点から比較します。
| 評価基準 | Requests + BS4 | Selenium / Playwright | Redsky API |
|---|---|---|---|
| JavaScript描画への対応 | ❌ いいえ | ✅ はい | ✅ はい(JSON) |
| 1件あたりの速度 | ⚡ 約0.5〜1秒 | 🐢 約5〜10秒 | ⚡ 約0.5〜1秒 |
| ボット検知リスク | ⚠️ 高い(TLS指紋) | ⚠️ 中程度 | ⚠️ 中程度(認証キーが変わる可能性) |
| セットアップの難易度 | 低い | 中程度 | 中〜高(リバースエンジニアリング) |
| データの完全性 | 約30%(静的HTMLのみ) | 約95%(ページ全体) | 約90%(構造化JSON) |
| 最適な用途 | 静的メタデータ、__TGT_DATA__ | 商品ページ全体、レビュー | 大規模な商品データ取得 |
以下で、各方法の設定手順と取得結果を順に説明します。
方法1: PythonのRequestsとBeautifulSoupでTarget.comをスクレイピングする
この方法では、JavaScriptで描画される検索ページの価格までは取得できません。ただし、動作は軽くて速く、探し方さえ知っていれば意外と多くの情報が取れます。
ポイントは、Targetが <script> タグ内に __PRELOADED_QUERIES__ を含む __TGT_DATA__ 変数として、一部の商品データを埋め込んでいることです。このJSONブロブには、商品名、説明、特徴、場合によっては個別商品ページの価格まで入っています。検索結果HTMLから商品タイトルやURLを拾うこともできます。
ステップ1: Python環境を準備する
プロジェクトフォルダを作成し、依存関係をインストールします。
mkdir target-scraper && cd target-scraper
python -m venv venv
source venv/bin/activate # Windowsの場合: venv\Scripts\activate
pip install requests beautifulsoup4 curl_cffi
ここでは標準の requests ではなく curl_cffi を使用します。ブラウザに近いTLSフィンガープリントを利用できるため、Targetへの接続結果が変わる可能性があります。ベンチマークでは curl_cffi のボット回避率は 92% なのに対し、標準の requests は 12% とされ、15倍の改善と説明されています。
ステップ2: Targetの検索結果をスクレイピングする
Targetの検索URL形式はシンプルです: https://www.target.com/s?searchTerm={keyword}
from curl_cffi import requests as cureq
from bs4 import BeautifulSoup
import time, random
headers = {
"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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
}
url = "https://www.target.com/s?searchTerm=bluetooth+headphones"
resp = cureq.get(url, headers=headers, impersonate="chrome124")
soup = BeautifulSoup(resp.text, "html.parser")
# 商品カードはこの data-test 属性を使います
cards = soup.find_all("div", {"data-test": "@web/site-top-of-funnel/ProductCardWrapper"})
for card in cards:
link_tag = card.find("a")
title = link_tag.get_text(strip=True) if link_tag else "N/A"
href = "https://www.target.com" + link_tag["href"] if link_tag and link_tag.get("href") else "N/A"
print(f"{title} — {href}")
このコードでは商品名とURLを取得できます。一方、価格が初期HTMLに含まれないページでは取得できません。これは後続の方法で補います。
ステップ3: 商品ページの埋め込みJSONデータを抽出する
個別の商品ページでは、__TGT_DATA__ スクリプトタグ内により豊富なデータが埋め込まれています。
import re, json
product_url = "https://www.target.com/p/some-product/-/A-12345678"
resp = cureq.get(product_url, headers=headers, impersonate="chrome124")
soup = BeautifulSoup(resp.text, "html.parser")
# __TGT_DATA__ スクリプトを探す
scripts = soup.find_all("script")
for script in scripts:
if script.string and "__TGT_DATA__" in script.string:
# スクリプト本文からJSONを抽出
match = re.search(r'__TGT_DATA__\s*=\s*({.*?});?\s*$', script.string, re.DOTALL)
if match:
tgt_data = json.loads(match.group(1))
# 商品詳細用のJSON構造をたどる
queries = tgt_data.get("__PRELOADED_QUERIES__", {})
# 商品データはこの中にネストされている — 構造はページごとに異なる
print(json.dumps(queries, indent=2)[:500]) # 構造を確認するためのプレビュー
__TGT_DATA__ 内のJSONには、商品名、説明、特徴、価格データが含まれる場合があります。ネスト構造はページによって異なるため、実際の出力を調べて必要なキーを特定します。
ステップ4: ページネーションを処理する
Targetの検索ページネーションは Nao パラメータを使います。1ページ目は Nao=0、2ページ目は Nao=24、3ページ目は Nao=48 という具合で、24ずつ増えます。
for page in range(0, 120, 24): # 最初の5ページ
paginated_url = f"https://www.target.com/s?searchTerm=bluetooth+headphones&Nao={page}"
resp = cureq.get(paginated_url, headers=headers, impersonate="chrome124")
# 解析して抽出...
time.sleep(random.uniform(2, 5)) # 丁寧に扱う
ステップ5: スクレイピングしたデータを保存する
import csv
with open("target_products.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["title", "url", "price", "description"])
writer.writeheader()
for product in products:
writer.writerow(product)
取得できるもの: 商品タイトル、URL、説明、埋め込みメタデータ。安定しては取得できないもの: 検索結果ページの動的価格や評価。これらが必要なら、方法2か3を使ってください。
方法2: SeleniumまたはPlaywrightでTarget.comをスクレイピングする
ヘッドレスブラウザはJavaScriptを実行し、動的コンテンツの読み込み後にDOMを取得できます。価格、評価、レビューなど、初期HTMLに含まれない項目を扱う場合の候補です。
比較資料では、Playwrightの採用率がSeleniumを上回っています — 2026年時点で 45.1%対22.1% — また、特定のベンチマークでは 2.5倍高速(20ページで11秒対28秒)とされています。本記事ではコミュニティとチュートリアルの多さからSeleniumを使います。新規実装では、対象ブラウザ、既存資産、実行環境を比較したうえでPlaywrightも候補にできます。
ステップ1: SeleniumとChromeDriverをインストールする
pip install selenium webdriver-manager
webdriver-manager はChromeDriverの取得とバージョン管理を補助するため、手動管理によるバージョン不一致を減らしやすくなります。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-blink-features=AutomationControlled")
options.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")
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
ステップ2: Targetページを読み込み、コンテンツを待つ
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver.get("https://www.target.com/s?searchTerm=bluetooth+headphones")
# 商品カードの描画を待つ(time.sleepよりexplicit waitの方が良い)
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, '[data-test="product-title"]'))
)
明示的な待機を使うと、固定時間の time.sleep(10) より読み込み時間の差に対応しやすくなります。WebDriverWait は、要素が現れるかタイムアウトするまで500msごとに状態を確認します。
ステップ3: ページをスクロールして全商品を読み込む
Targetでは商品がスクロールに応じて遅延読み込みされる場合があります。スクロールしない条件では、ページ全体ではなく4〜5商品だけが取得されたという検証例があります。
import time
last_height = driver.execute_script("return document.body.scrollHeight")
for _ in range(10):
driver.execute_script("window.scrollBy(0, 300);")
time.sleep(1.5)
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
ScrapeOpsのテストでは 、1.5秒の待機を挟んで10回スクロールすると、スクロールなしの4〜5商品に対して8商品以上が取得できたとされています。200〜300px程度のスクロール幅は、この検証条件に合わせた設定例です。実際の終了条件は、商品件数やページ高さの変化を記録して調整します。
ステップ4: レンダリング済みページから商品データを抽出する
products = []
cards = driver.find_elements(By.CSS_SELECTOR, '[data-test="@web/site-top-of-funnel/ProductCardWrapper"]')
for card in cards:
try:
title = card.find_element(By.CSS_SELECTOR, '[data-test="product-title"]').text
except:
title = "N/A"
try:
price = card.find_element(By.CSS_SELECTOR, '[data-test="current-price"]').text
except:
price = "N/A"
try:
link = card.find_element(By.CSS_SELECTOR, 'a[href*="/p/"]').get_attribute("href")
except:
link = "N/A"
products.append({"title": title, "price": price, "link": link})
for p in products:
print(f'{p["title"]} — {p["price"]}')
Target向けの主要な data-test セレクタ(2026年確認済み)はこちらです。
| データ項目 | セレクタ |
|---|---|
| 商品カード | data-test="@web/site-top-of-funnel/ProductCardWrapper" |
| 商品タイトル | data-test="product-title" |
| 現在価格 | data-test="current-price" |
| 評価値 | data-test="rating-value" |
| 評価件数 | data-test="rating-count" |
ステップ5: 商品レビューをスクレイピングする
個別の商品ページへ移動し、レビューセクションまでスクロールしてレビュー情報を抽出します。
from bs4 import BeautifulSoup
driver.get("https://www.target.com/p/some-product/-/A-12345678")
# レビューを読み込むために下へスクロール
for _ in range(5):
driver.execute_script("window.scrollBy(0, 500);")
time.sleep(2)
soup = BeautifulSoup(driver.page_source, "html.parser")
reviews = soup.find_all("div", {"data-test": "review-card--text"})
for review in reviews:
print(review.get_text(strip=True)[:100])
レビューはBazaarvoiceの統合で読み込まれ、ページネーションは最大51ページ、新着順ソート、写真のみフィルタに対応するとされています。ScrapeOpsのベンチマーク では、Seleniumで1件あたり約5.1秒と報告されています。
処理後はブラウザを閉じます。
driver.quit()
方法3: Redsky APIを使ってTarget.comをスクレイピングする
Targetのフロントエンドは、redsky.target.com の内部APIから商品情報を取得します。Pythonから同じエンドポイントへアクセスできる条件では、HTML解析やブラウザによるJavaScript描画を省き、構造化JSONを処理できます。レスポンスには、価格、評価、レビュー、画像、在庫、配送、仕様、バリエーションなど40以上のデータフィールドが含まれるとされています。大量の商品データを扱う場合は、速度とデータ構造の面で候補になりますが、文書化されていない内部APIであるため、仕様変更や利用条件を運用上のリスクとして扱う必要があります。
ステップ1: Chrome DevToolsでRedsky APIを見つける
最初に、ブラウザが実際に送信しているリクエストをDevToolsで調べます。手順は次のとおりです。
- Chromeで任意のTarget商品ページを開く
- DevTools(F12)を開き、Network タブへ移動する
- Fetch/XHR でフィルタする
- ページを再読み込みする
redsky.target.comまたはredsky.a]target.comへのリクエストを探す- 1つクリックして Request URL と Headers を確認する
すると、次のようなURLが見つかります。
https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key=9f36aeafbe60771e321a7cc95a78140772ab3e96&tcin=12345678&store_id=2148&zip=55401
主なパラメータは以下です。
key— APIキー(確認時点ではURL内にあり、エンドポイントごとに異なるキーを使う)tcin— Target.com Item Number(8桁の商品ID)store_id— Target店舗の場所zip— 配送情報用のZIPコード
APIキーは、Networkタブに記録されたRequest URLのクエリパラメータから確認できます。
ステップ2: PythonからRedsky APIへ直接リクエストする
from curl_cffi import requests as cureq
import json
API_KEY = "9f36aeafbe60771e321a7cc95a78140772ab3e96" # DevToolsから抽出
TCIN = "12345678"
url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={TCIN}&store_id=2148&zip=55401"
headers = {
"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",
"Accept": "application/json",
"Origin": "https://www.target.com",
"Referer": "https://www.target.com/",
"Sec-Fetch-Site": "same-site",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
}
resp = cureq.get(url, headers=headers, impersonate="chrome124")
data = resp.json()
# JSONレスポンスから商品詳細を抽出する
product = data.get("data", {}).get("product", {})
title = product.get("item", {}).get("product_description", {}).get("title", "N/A")
price = product.get("price", {}).get("formatted_current_price", "N/A")
rating = product.get("ratings_and_reviews", {}).get("statistics", {}).get("rating", {}).get("average", "N/A")
print(f"{title} — {price} — 評価: {rating}")
この方法ではHTML解析を行わず、構造化されたレスポンスから必要なキーを取得します。
ステップ3: API経由で検索結果の商品を取得する
product_summary_with_fulfillment_v1 エンドポイントは、複数のTCINをまとめて受け付けます。
tcins = ["12345678", "23456789", "34567890"]
tcin_str = ",".join(tcins)
search_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"
resp = cureq.get(search_url, headers=headers, impersonate="chrome124")
results = resp.json()
for item in results.get("data", {}).get("product_summaries", []):
title = item.get("title", "N/A")
price = item.get("price", {}).get("formatted_current_price", "N/A")
print(f"{title} — {price}")
TCINを取得するには、検索ページのHTMLから拾う方法(商品URL内に /A-XXXXXXXX として出てくる)か、__TGT_DATA__ に埋め込まれたJSONから取る方法があります。
ステップ4: 並列リクエストでスケールさせる
from concurrent.futures import ThreadPoolExecutor
import time, random
def fetch_product(tcin):
url = f"https://redsky.target.com/redsky_aggregations/v1/web/pdp_fulfillment_v1?key={API_KEY}&tcin={tcin}&store_id=2148&zip=55401"
time.sleep(random.uniform(2, 5))
resp = cureq.get(url, headers=headers, impersonate="chrome124")
return resp.json()
tcin_list = ["12345678", "23456789", "34567890", "45678901"]
with ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(fetch_product, tcin_list))
並列数は控えめに保ちましょう。3〜5スレッドで、2〜5秒のランダム遅延を入れるのが無難です。Targetのレート制限は、IPあたり毎分30〜60リクエスト 程度です。
Redsky APIに関する重要な注意点
本番パイプラインでは、次の仕様変更リスクと運用条件を事前に扱います。
- APIキーはエンドポイントごとに異なる場合があります。 確認時点ではRedskyの各エンドポイントで別のキーが使われています。固定値として長期保存せず、通信エラーが出た場合に再取得できる設計にします。
- これは文書化されていない内部APIです。 Targetのエンジニアリングチームは 意図的に公開向けであると認めています とされていますが、SLA付きの公式公開APIではありません。公開資料の位置付けと利用規約を分けて判断する必要があります。
- 商品バリエーション(色、サイズ)ごとに固有のTCINがあります。 各バリエーションの情報が必要な場合は、対応するTCINを取得して個別に問い合わせます。
Sec-Fetch-*ヘッダーが判定に影響する場合があります。Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-Destを含め、ブラウザが送信するヘッダーとの整合性を検証します。
Target.comを大規模に取得する際の運用上の注意点
以下では、継続運用時に検討する通信間隔、IP、TLS、ヘッダー、セッション管理を整理します。
データセンターではなく住宅プロキシをローテーションする
TargetのAkamai実装では、データセンターIP帯が検知要因になると報告されています。継続的なスクレイピングでは、取得規模や対象地域に応じて住宅プロキシが必要になる場合があります。価格例として、Smartproxy/Decodoは1GBあたり4.50ドルから、Bright Dataは1GBあたり5.04ドル とされ、大量利用では3〜4ドル/GB程度まで下がるという情報があります。
ローテーション頻度は、プロキシ事業者の仕様、応答状況、対象サイトへの負荷を基準に決めます。原文の目安は50〜100リクエストごと、またはプロキシプールが対応している場合は毎リクエストです。
curl_cffiでブラウザ互換のTLSフィンガープリントを利用する
curl_cffi は requests に近い呼び出し方で導入でき、TLSフィンガープリントの差が接続結果に影響する環境で比較対象になります。
from curl_cffi import requests as cureq
# 標準requests — 保護サイトでの成功率は12%
# resp = requests.get(url, headers=headers)
# curl_cffi — 成功率92%
resp = cureq.get(url, headers=headers, impersonate="chrome124")
curl_cffi はGitHubスター6,100以上を持ち、chrome99 から chrome146 までのChromeバージョンに加え、Safari、Edge、モバイル版にも対応しています。同期モードでは tls_clientより20〜30%高速 です。
現実的なリクエスト間隔とヘッダーを設定する
- ランダム遅延: 原文ではリクエスト間を2〜7秒としています。固定値にせず、応答状況と対象サイトへの負荷を記録して調整します。
- User-Agentのローテーション: 実在するブラウザのUser-Agent文字列を5〜10個用意する方法がありますが、文字列だけを変えず、ほかのヘッダーとの整合性も保ちます。
- セッションのウォームアップ: 商品ページへ移動する前に
target.comのトップページを開き、必要なクッキーを取得する方法があります。 - ヘッダーの整合性:
Sec-Ch-UaはUser-Agentで示すブラウザバージョン、Sec-Ch-Ua-PlatformはOSと整合させます。不一致は検知要因になる可能性があります。 - セッションの継続: セッション内ではクッキーを維持します。Scraperlyは 住宅プロキシをローテーションしつつ、48時間のセッション安定性を推奨しています。
ノーコード代替: ThunderbitでTarget.comからデータを取得する
Target.comをPythonで処理する場合は、JavaScript描画、TLSフィンガープリント、IP評価、ブラウザドライバー管理など、複数の実装・保守項目があります。仕組みを学ぶ用途には適していますが、商品データを一度取得して表形式で確認することが目的なら、開発工数が見合うかを先に判断する必要があります。
コードを用意せず、ブラウザで表示できる範囲のデータを抽出したい場合は、Thunderbit が選択肢になります。
ThunderbitがTarget.comの課題をどう解決するか
ThunderbitのAIウェブスクレイパーはブラウザ内で動作し、ブラウザで描画されたページを対象に項目を抽出します。そのため、SeleniumやChromeDriverを個別に設定せずに試せます。ただし、取得できる範囲は対象ページの表示状態、アクセス条件、ログイン状態、製品プランによって異なります。
使い方は次の通りです。
- Thunderbit Chrome拡張機能 をインストールし、Targetの商品ページまたは検索ページを開く
- 「AIで項目を提案」 をクリックする — Thunderbitがページを読み取り、列名(商品名、価格、評価、画像URLなど)を提案する
- 「スクレイプ」 をクリックする — 描画済みページからデータ抽出を実行する
通常のブラウザ表示を対象にする場合は、利用者がプロキシやTLSフィンガープリントを個別設定せずに試せます。抽出後は、価格や評価の欠損、商品件数、出力列を元ページと照合してから利用範囲を広げます。
Targetの商品一覧ページと詳細ページをスクレイピングする
複数ページを扱う場合は、Targetの検索結果ページから商品一覧を取得し、サブページスクレイピング が利用できる条件で各商品URLの情報を追加できます。説明文、レビュー全文、仕様などの取得可否は、詳細ページの構成、表示状態、対象フィールドによって異なります。
Excel、Google スプレッドシート、Airtable、Notion への直接エクスポートは、現在の製品仕様とプランで利用できる出力先を確認して使います。対応する出力先では、CSV保存コードや文字コード処理を個別に実装せずに共有できます。
定期的なTarget.comスクレイピングを自動化する
継続的な価格監視や在庫追跡では、利用中のプランで対応している場合、Thunderbitの スケジュールスクレイパー を候補にできます。スケジュールを自然言語で指定する例として「毎週月曜の午前9時」があり、定期実行用のcronジョブやサーバーを個別に管理しない運用を検討できます。これは、競合価格データを追跡する ECチームの用途にも当てはまります。
外部調査では、米国小売業者の81% が自動価格スクレイピングを利用し、価格インテリジェンスのROIは平均 27:1 とされています。導入判断では、この外部指標をそのまま自社効果とせず、取得精度、更新頻度、運用費用、価格変更後の行動を測定します。
PythonでTarget.comをスクレイピングする際に、どの方法を使うべきか
必要なデータ範囲、処理件数、保守担当、実行頻度を基準に選びます。
| あなたの状況 | 推奨方法 |
|---|---|
| Python学習中、小規模プロジェクト | 方法1: Requests + BS4(静的データと __TGT_DATA__ 用) |
| 価格やレビューを含む商品ページ情報が必要 | 方法2: Selenium / Playwright |
| 大規模な商品データ抽出が必要 | 方法3: Redsky API(仕様変更への保守が可能な場合) |
| コードを書かずにデータを抽出したい | Thunderbit(対象ページとプランが対応する場合) |
| 定期的な価格監視 | Thunderbit スケジュールスクレイパー(利用プランに応じる)、またはRedsky API + cron |
| 1回限りの調査、非技術チーム | Thunderbit — ブラウザで抽出結果を検証したい場合の候補 |
本記事の比較条件では、大量の商品データを構造化して処理し、内部APIの仕様変更を保守できる場合、方法3(Redsky API)が速度とデータ構造の面で候補になります。単発調査やPython担当者がいないチームでは、Thunderbitで対象項目を抽出できるか試す方法があります。ウェブスクレイピングの仕組みを学ぶ場合は、方法1 → 方法2 → 方法3 の順に進むと、静的HTML、ブラウザ描画、APIレスポンスの違いを比較できます。
Target.comをスクレイピングする際の法的・倫理的な考慮事項
Targetの robots.txt には約120以上のDisallowパスがあり、確認時点では /p/(商品)や /c/(カテゴリ)は含まれていません。ただし、robots.txt の記載だけでクロールやデータ利用への法的・契約上の許可が与えられるわけではありません。カート、アカウント、チェックアウトページは制限対象として記載されています。
Targetの利用規約は自動アクセスを禁止しているとされています。また、Redsky APIについてTargetのエンジニアリングが 意図的に公開向けである と説明していても、それだけでAPI利用が利用規約や他の権利上許可されるとは限りません。公開資料、利用規約、取得対象データ、利用目的を分けて検討する必要があります。
関連する米国の裁判例として、次が挙げられます。
- hiQ v. LinkedIn(第9巡回区、2022年): 公開データへのアクセスとCFAAの適用範囲が争われた事例
- Meta v. Bright Data(2024年): 公開データの取得をめぐる契約・CFAA上の主張が争われた事例
これらの裁判例から、Target.comでの具体的な取得行為が一律に適法だとは判断できません。大規模な商用スクレイピングでは、対象地域の法律とTargetの利用規約について法律専門家に相談してください。市場調査、価格比較、個人プロジェクトであっても、取得対象、保存期間、再利用方法、アクセス頻度を定め、サーバー負荷や個人情報・著作権への影響を抑える必要があります。
結論と重要ポイント
Target.comで基本的なRequests + BeautifulSoupの方法が失敗する主な理由は、商品データがJavaScriptで描画されることと、Akamaiによる通信・ブラウザ環境の判定があることです。必要なデータ範囲と保守体制に応じて方法を選びます。
本記事の比較条件における信頼性順:
- Redsky API — 大量データを構造化JSONで処理する場合の候補です。DevToolsでAPIエンドポイントを調べ、仕様変更を継続的に保守する必要があります。
- Selenium / Playwright — JavaScript描画後の情報を取得できます。処理時間とブラウザ管理の負荷を見込む必要があります。
- Requests + BeautifulSoup — 静的HTMLと埋め込み
__TGT_DATA__JSONを対象にします。処理は軽い一方、取得範囲は限定されます。
技術面で検証する項目:
- 標準の
requestsとcurl_cffiを同じ条件で比較する。出典の 15倍の改善 は、対象サイトとテスト条件をそろえて評価する - 継続的な取得では、データセンターIPと住宅プロキシの結果を比較し、必要性を判断する
Sec-Fetch-*ヘッダーをブラウザの通信と照合し、不整合による拒否がないか検証する- セッションのウォームアップ前後で、応答コードだけでなく取得内容も記録する
Python環境の構築や保守を行わずに試したい場合は、ThunderbitのChrome拡張機能 でブラウザに表示されたページから必要な項目を抽出できます。まず 無料プラン の現在の条件を確認し、1ページで商品件数、価格、評価、出力形式を元ページと照合してから、複数ページや定期運用を検討してください。
さらに詳しいスクレイピングガイドやデータ抽出のコツは、Thunderbitブログ や YouTubeチャンネル も参考になります。
よくある質問
PythonのRequestsとBeautifulSoupだけでTarget.comをスクレイピングできますか?
部分的には可能です。商品ページの __TGT_DATA__ スクリプトタグが存在する場合、商品タイトル、URL、一部の埋め込みJSONデータを取得できます。一方、検索結果ページの価格、評価、レビュー、在庫状況がJavaScriptで描画される構成では、静的HTTPレスポンスに対象要素が含まれません。これらの項目が必要な場合は、対象ページでSelenium/PlaywrightまたはRedsky APIの取得結果を比較します。
なぜTarget.comスクレイパーは価格をNoneで返すのですか?
本記事で扱うページ構成では、Targetは最初のHTML読み込み後にJavaScriptで価格データを追加します。requests.get() を使うとJavaScript実行前のHTMLシェルだけを受け取るため、価格要素がレスポンス内に存在せず、None になる場合があります。対応方法は、JavaScriptを実行できるSeleniumやPlaywrightを使う、Redsky APIからJSONデータを取得する、またはブラウザで描画されたページを対象にする Thunderbit のようなツールを試すことです。対象ページごとに商品件数と価格を元ページと照合してください。
Target.comをスクレイピングするのは合法ですか?
一律には判断できません。hiQ v. LinkedInやMeta v. Bright Dataは公開データへのアクセスをめぐる米国の裁判例ですが、Target.comでの具体的な取得方法、契約関係、データの種類、利用目的まで自動的に適法とするものではありません。Targetの robots.txt で商品ページとカテゴリページがDisallow対象に含まれていない場合でも、それは法的・契約上の許可を意味しません。Targetの利用規約では自動アクセスが禁止されているとされるため、取得対象、アクセス頻度、保存・再利用方法を個別に検討する必要があります。大規模な商用運用では、対象地域の法律と現行規約について弁護士に相談してください。
TargetのRedsky APIとは何で、どうやって使うのですか?
Redskyは、Targetのフロントエンドが商品データを取得する際に利用する内部APIです。公開APIのような利用者向けドキュメントや申請用キーはなく、Reactアプリが商品ページを描画する際の通信からエンドポイントを調べます。Chrome DevToolsを開き、NetworkタブをXHR/Fetchでフィルタし、redsky.target.com へのリクエストを調べると、確認時点のエンドポイントとクエリパラメータを特定できます。APIキーはリクエストURLに含まれる場合があります。Targetのエンジニアリングによる公開性の説明があっても、公式公開APIのSLAや利用許諾と同じ意味ではないため、現行資料と利用規約を別途判断します。
Target.comでブロックされないようにするにはどうすればいいですか?
ブロックを完全に避ける方法はありません。標準のPython requests と curl_cffi ではTLSフィンガープリントが異なるため、同じ対象・回数・IP条件で結果を比較します。参照ベンチマークでは成功率が 12%から92%へ 上がったとされていますが、Target.comで同じ結果になるとは限りません。住宅プロキシ、User-Agent、2〜7秒のランダム遅延、Sec-Fetch-* ヘッダー、トップページからのセッション開始についても、一度に複数を変えず、応答内容と拒否率を記録します。
コードや通信設定を個別に管理したくない場合は、Thunderbit でブラウザに表示されたページから項目を抽出する方法があります。対象ページやプランによって取得範囲が異なるため、最初に商品件数、価格、評価の欠損を元ページと比較します。
さらに詳しく


