Temuの商品・価格データをスクレイピング:API vs HTMLのトレードオフ
Temuには公開APIが存在しない。商品や価格データを取得するには、大きく分けて2つのアプローチがある。1つ目はHTMLページを取得してDOMをパースする方法。2つ目は、Temuのフロントエンドが内部で呼び出しているJSONエンドポイント(/api/poppy/v1/search や商品詳細)を直接叩く方法だ。前者は実装が簡単だが、ハッシュ化されたCSSクラス名が頻繁に変わるためメンテナンスコストが高い。後者は構造化データを直接取得できるが、署名付き anti_content ヘッダーとTLS指紋チェックを突破する必要がある。
実務では、両方を組み合わせるのが現実的だ。検索一覧はJSONエンドポイントから取得し、商品ページの補助データはHTMLの埋め込み状態から抽出する。本記事では、Temuの商品・価格データをスクレイピングするための実践的なアプローチを、ProxyHatの住宅プロキシと組み合わせて解説する。
Temuの月間訪問者数は2024年末時点で約4億6800万人に達しており、膨大な商品カタログを抱えている。価格インテリジェンスチームにとって、このデータを継続的に取得することは競合分析の中核となる。
Temuのアンチボット技術スタック
Temuは複数のレイヤーでボット対策を実装している。各レイヤーを理解しないと、スクレイピングは最初のリクエストでブロックされる。
Cloudflare Turnstile
TemuはCloudflare Turnstileを使用して、ブラウザ環境の正当性を検証している。Turnstileは従来のCAPTCHAとは異なり、ユーザーにチャレンジを表示せずにバックグラウンドでブラウザの挙動を分析する。具体的には、JavaScriptの実行環境、Canvas APIのフィンガープリント、WebGLレンダラー情報などをチェックし、自動化ツールを検出する。
署名付き anti_content ヘッダー
Temuの内部APIリクエストには、anti_content という署名付きトークンが必須だ。このトークンはフロントエンドのJavaScriptで生成され、デバイス情報、タイムスタンプ、セッション状態を含む暗号化ペイロードである。トークンを付けずにAPIを叩くとHTTP 400が返る。トークンの生成ロジックは難読化されており、頻繁に更新される。
TLS/HTTP2 指紋認証
Cloudflareのボット管理システムは、TLSハンドシェイクとHTTP/2フレームの指紋を検証する。Pythonの標準 requests ライブラリは、ChromeのTLS指紋と一致しないため、データセンターIPでなくてもフラグされる可能性がある。これが、curl_cffi のようなブラウザ指紋を模倣するライブラリが必要な理由だ。
データセンターIPの即座ブロック
TemuはIPレピュテーションデータベースと照合し、AWS、GCP、AzureなどのデータセンターIP範囲を即座にブロックする。データセンタープロキシを使った場合、成功率は10〜30%程度に落ち込む。住宅プロキシを使用しても、TLS指紋が一致しなければ同様にブロックされる可能性がある。
データの所在:JSONエンドポイントと埋め込み状態
Temuのページ構造はNext.jsベースであり、サーバーサイドレンダリングされたHTML内に __NEXT_DATA__ 形式の状態ブロブが埋め込まれている。ただし、Temuの場合は厳密には window.__INITIAL_STATE__ または同様のグローバル変数に商品データが格納されている場合もある。
主要なJSONエンドポイント
/api/poppy/v1/search— 検索結果一覧。キーワード、ページ、ソート順をパラメータとして受け取る。/api/poppy/v1/goods/detail— 商品詳細。goods_idをパラメータとして受け取る。/api/poppy/v1/goods/review_list— レビュー一覧。
これらのエンドポイントは、ブラウザの開発者ツールのNetworkタブで poppy をフィルタリングすることで確認できる。
HTMLからのデータ抽出
JSONエンドポイントが利用できない場合は、HTMLからデータを抽出する。TemuのHTMLはハッシュ化されたCSSクラス名(例:_3d2a8f)を使用しており、クラス名でセレクタを書くと頻繁に壊れる。代わりに以下のアプローチを使う:
data-uniqid属性を起点にする — 商品カードには一意のID属性が付与されている。- 構造的セレクタ(
div[data-uniqid] > div:nth-child(2) > span)で価格を抽出する。 - 正規表現で
window.__INITIAL_STATE__のJSONブロブを直接抽出する。
XPathの例://div[@data-uniqid]/div[contains(@class, 'price')]/span/text()
レートリミットと地域別価格の課題
Temuのレートリミットは公開されていないが、実測では以下の傾向が観察される:
- 同一IPで10秒間に約30リクエストを超えると、HTTP 429が返る。
- 429応答後、約60秒間のクールダウンが必要。
- 繰り返し429をトリガーすると、IPが24時間ブロックされる。
さらに重要なのは、Temuの価格と送料は地域によって異なる点だ。米国のIPでアクセスした場合と英国のIPでアクセスした場合で、同じ商品IDでも表示価格が異なる。これは税金、関税、物流コストの地域差が反映されるためだ。
したがって、米国市場の価格データを取得するには、米国の住宅IPを使用する必要がある。ProxyHatでは -country-US フラグで国を指定できる。さらに都市レベルのターゲティング(-country-US-city-newyork)を使えば、地域別の価格差も追跡可能だ。
ProxyHatのロケーション一覧で利用可能な国と都市を確認できる。
Python実装:curl_cffi + ProxyHatで商品ページを取得
以下は、curl_cffi とProxyHatの住宅プロキシを使ってTemu商品ページを取得し、埋め込みJSONから商品データを抽出する実装例だ。
まず、必要なパッケージをインストールする:
pip install curl_cffi beautifulsoup4 lxml
基本的なcurlコマンドで接続をテストする:
curl -x http://user-country-US:pass@gate.proxyhat.com:8080 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept-Language: en-US,en;q=0.9" \
"https://www.temu.com/goods-detail.html?goods_id=601234567890"
Pythonでの実装:
from curl_cffi import requests
import json
import re
# ProxyHat住宅プロキシ(米国指定)
proxy_url = "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml",
}
# curl_cffiでChromeのTLS指紋を模倣
response = requests.get(
"https://www.temu.com/goods-detail.html",
params={"goods_id": "601234567890"},
headers=headers,
proxies={"http": proxy_url, "https": proxy_url},
impersonate="chrome120",
timeout=30,
)
if response.status_code == 200:
html = response.text
# 埋め込み状態から商品データを抽出
match = re.search(
r'window\.__INITIAL_STATE__\s*=\s*({.*?});',
html
)
if match:
state = json.loads(match.group(1))
goods = state.get("goods", {})
print(f"goods_id: {goods.get('goods_id')}")
print(f"price: {goods.get('sale_price', {}).get('amount')}")
print(f"sku_list: {len(goods.get('sku_list', []))} SKUs")
else:
print(f"Request failed: {response.status_code}")
取得できるJSONの例(truncated):
{
"goods": {
"goods_id": "601234567890",
"goods_name": "Wireless Bluetooth Earbuds Pro Max",
"sale_price": {
"amount": 12.98,
"currency": "USD"
},
"original_price": {
"amount": 29.99,
"currency": "USD"
},
"sku_list": [
{
"sku_id": "sku_001",
"sku_name": "Black",
"price": {"amount": 12.98, "currency": "USD"}
},
{
"sku_id": "sku_002",
"sku_name": "White",
"price": {"amount": 13.49, "currency": "USD"}
}
],
"shipping_info": {
"free_shipping": true,
"estimated_days": "5-8"
}
}
}
このアプローチの利点は、anti_content ヘッダーを生成せずにHTMLからデータを抽出できる点だ。ただし、動的にレンダリングされるデータ(レビューなど)は取得できないため、必要に応じてAPIエンドポイントも併用する。
スティッキーセッション、リトライ、ページネーション
スティッキーセッションで一貫性を確保
カート追加や送料計算など、セッション状態に依存する操作を行う場合、リクエストごとにIPが変わると一貫性が失われる。ProxyHatのスティッキーセッション機能を使えば、指定したセッションIDで同じIPを維持できる。
# スティッキーセッション(同一IPを維持)
session_proxy = "http://user-country-US-session-temu001:YOUR_PASSWORD@gate.proxyhat.com:8080"
response = requests.get(
"https://www.temu.com/goods-detail.html",
params={"goods_id": "601234567890"},
headers=headers,
proxies={"http": session_proxy, "https": session_proxy},
impersonate="chrome120",
timeout=30,
)
リトライと指数バックオフ
429や503応答に対しては、指数バックオフでリトライを実装する。単純なリトライはIPブロックを悪化させるだけだ。
import time
import random
def fetch_with_retry(url, params, proxy, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.get(
url, params=params,
headers=headers,
proxies={"http": proxy, "https": proxy},
impersonate="chrome120",
timeout=30,
)
if response.status_code == 200:
return response
elif response.status_code == 429:
wait = (2 ** attempt) + random.uniform(0, 2)
print(f"429 received, waiting {wait:.1f}s")
time.sleep(wait)
else:
print(f"Error: {response.status_code}")
return None
except Exception as e:
print(f"Exception: {e}")
time.sleep(2 ** attempt)
return None
ページネーション
検索結果のページネーションでは、各ページで異なるセッションIDを使用し、ページ間に2〜5秒のランダム遅延を入れる。これにより、人間らしい閲覧パターンをシミュレートできる。
results = []
for page in range(1, 6):
session_id = f"temu_search_p{page}"
page_proxy = f"http://user-country-US-session-{session_id}:PASS@gate.proxyhat.com:8080"
resp = fetch_with_retry(
"https://www.temu.com/api/poppy/v1/search",
params={"keyword": "bluetooth earbuds", "page": page},
proxy=page_proxy,
)
if resp:
data = resp.json()
items = data.get("data", {}).get("items", [])
results.extend(items)
print(f"Page {page}: {len(items)} items")
# ランダム遅延
time.sleep(random.uniform(2, 5))
print(f"Total items: {len(results)}")
比較表:プロキシタイプ別のTemuスクレイピング適性
| プロキシタイプ | 成功率 | 平均レイテンシ | 地域ターゲティング | Temu適性 |
|---|---|---|---|---|
| 住宅プロキシ | 85〜95% | 200〜800ms | 国・都市レベル | 最適 |
| データセンタープロキシ | 10〜30% | 50〜100ms | 国レベルのみ | 不適(即時ブロック) |
| モバイルプロキシ | 90〜98% | 500〜2000ms | 国レベル | 条件付き(コスト高) |
Temuスクレイピングでは住宅プロキシが最適なバランスを提供する。ProxyHatの料金プランで住宅プロキシの詳細を確認できる。
倫理と利用規約:スクレイピングの境界線
スクレイピングを実装する前に、以下の点を確認する必要がある:
公開カタログデータのみ
本記事で解説するのは、ログイン不要で閲覧可能な公開カタログデータ(商品名、価格、SKU、送料情報)の取得のみだ。アカウント情報、注文履歴、個人情報を含むデータのスクレイピングは、Computer Fraud and Abuse Act (CFAA) およびGDPRの対象となる可能性がある。
Temuの利用規約
Temuの利用規約は自動化されたアクセスを禁止している可能性が高い。スクレイピングを実施する前に、利用規約を確認し、法的アドバイザーに相談することを推奨する。
公式マーチャントフィードの優先
Temuで出品者(マーチャント)の場合は、公式のマーチャントセンターやAPI経由で自身の商品データにアクセスできる。この公式ルートが利用可能な場合は、スクレイピングよりも優先すべきだ。
robots.txtの尊重
https://www.temu.com/robots.txt を確認し、許可されていないパスへのアクセスを避ける。これは技術的・法的なベストプラクティスだ。
レートリミットの遵守
1秒間に1リクエスト以下のペースを維持し、サーバーに負荷をかけないよう設計する。大量の並行リクエストはDoS攻撃とみなされる可能性がある。
重要なポイント
Temuの商品・価格データをスクレイピングする際の要点:
- 公開APIがないため、内部JSONエンドポイント(
/api/poppy/v1/)またはHTMLの埋め込み状態からデータを抽出する。- Cloudflare Turnstile、署名付き
anti_contentトークン、TLS/HTTP2指紋認証の3層対策を理解する。curl_cffiでChromeのTLS指紋を模倣し、データセンタープロキシではなく住宅プロキシを使用する。- 地域別価格を正確に取得するため、
-country-USでジオターゲティングを指定する。- スティッキーセッション(
-session-)でカート・送料の一貫性を確保する。- 429応答には指数バックオフで対応し、ページ間にランダム遅延を入れる。
- 公開カタログデータのみを対象とし、利用規約とrobots.txtを尊重する。
実装を始める準備ができたら、ProxyHatのスクレイピングユースケースと公式ドキュメントを参照してほしい。SERPトラッキングや価格監視の自動化にも、同じプロキシインフラを活用できる。
よくある質問
Temuの商品・価格データをスクレイピングするとは何ですか?
Temuには公開APIがないため、商品や価格データを取得するには、WebページのHTMLをパースするか、内部JSONエンドポイント(/api/poppy/v1/)を直接呼び出す必要があります。住宅プロキシとTLS指紋模倣ライブラリ(curl_cffiなど)を組み合わせることで、Cloudflare Turnstileなどのアンチボット対策を回避しながら、構造化された商品データ(goods_id、価格、SKU一覧など)を継続的に収集できます。
なぜTemuスクレイピングにプロキシが必要なのですか?
TemuはCloudflareのボット管理システムを通じて、データセンターIP範囲を即座にブロックします。AWS、GCP、AzureなどのIPアドレスからのリクエストは成功率が10〜30%に低下します。住宅プロキシを使用することで、実際のISP IPアドレスからのリクエストとして認識され、成功率が85〜95%に向上します。さらに、地域別価格を正確に取得するためには、対象国のIPアドレスが必要です。
Temuスクレイピングに最適なプロキシタイプは何ですか?
住宅プロキシが最適です。データセンタープロキシは即時ブロックの対象となり、モバイルプロキシは成功率は高いもののレイテンシが500〜2000msと高く、コストも最も高いです。住宅プロキシは成功率85〜95%、レイテンシ200〜800ms、国・都市レベルのジオターゲティングが可能であり、コストパフォーマンスのバランスが最も優れています。ProxyHatでは-country-USフラグで地域指定が可能です。
Temuスクレイピングでブロックを回避するにはどうすればよいですか?
3つの対策を組み合わせる必要があります。1つ目はcurl_cffiを使ってChromeのTLS/HTTP2指紋を模倣すること。2つ目は住宅プロキシで実際のISP IPを使用すること。3つ目は1秒間に1リクエスト以下のペースを維持し、429応答時には指数バックオフでリトライすることです。また、スティッキーセッション(-session-)を使ってセッション一貫性を保ち、ページ間に2〜5秒のランダム遅延を入れることも重要です。






