Temuスクレイピングの基本:APIかHTMLか
Temuの商品・価格データをスクレイピングする場合、まず直面するのが「Temuには公開APIが存在しない」という事実です。したがって、開発者は2つのアプローチから選択することになります。
- HTMLカードの解析:商品一覧ページや詳細ページのDOMを直接パースする手法。CSSクラス名がハッシュ化されて頻繁に変更されるため、保守コストが高い。
- 内部JSONエンドポイントの利用:
/api/poppy/v1/searchや商品詳細のJSONエンドポイントを直接叩く手法。構造が安定しているが、署名付きヘッダーとアンチボット検証が必要。
実務では、HTML解析はフォーマット変更1回でセレクタが全滅するリスクがあるため、可能な限り内部JSONエンドポイントを狙うのが定石です。ただし、TemuはCloudflareの保護下にあり、データセンターIPは即座にブロックされるため、ProxyHatのresidentialプロキシのような住宅IPインフラが必須となります。
Temuスクレイピング(scrape Temu)を成功させる鍵は、(1) アンチボット技術の理解、(2) データの存在場所の特定、(3) 適切なプロキシの選択、(4) 安定したリクエスト戦略の構築、この4点に集約されます。以下、順を追って解説します。
Temuのアンチボットスタックの全体像
Temuは複数レイヤーのアンチボット防御を組み合わせており、単純なHTTPリクエストではほぼ確実にブロックされます。主要な防御メカニズムは以下の通りです。
Cloudflare TurnstileとWAF
TemuはCloudflare TurnstileおよびCloudflare WAFを前面に配置しています。Turnstileはブラウザの環境情報(Canvas fingerprint、WebGL情報、タイミングデータ)を収集し、ボットか人間かを判定します。データセンターIPからのリクエストは、Turnstileの判定前にWAFレベルで403または1015エラーとして拒否されることが多いです。
署名付きanti_contentヘッダー
Temuの内部APIエンドポイントは、リクエストごとにanti_contentという署名トークンを要求します。このトークンはブラウザ側のJavaScriptで生成され、リクエストボディ、タイムスタンプ、デバイス情報などを組み合わせた暗号学的ハッシュを含んでいます。トークンが欠損または無効な場合、APIは空の結果またはエラーを返します。
実務上の注意:anti_contentの生成ロジックはTemuアプリのJSバンドル内で難読化されており、定期的に変更されます。自前で再現するより、ブラウザ自動化ツール(Playwright等)でトークンを抽出し、それをHTTPリクエストに転用する方が現実的です。
TLS/HTTP2フィンガープリンティング
CloudflareはTLSフィンガープリンティング(JA3/JA4)を用いて、リクエスト元のクライアントが本物のブラウザかどうかを判定します。Python標準のrequestsライブラリはJA3ハッシュがChromeと異なるため、Cloudflareのチャレンジページにリダイレクトされます。これを回避するには、ブラウザと同一のTLSハンドシェイクを再現できるライブラリ(curl_cffiやtls-client)を使用する必要があります。
| 防御レイヤー | 技術 | 回避策 |
|---|---|---|
| WAF / IPレピュテーション | Cloudflare WAF | Residentialプロキシ(住宅IP) |
| ボット判定 | Cloudflare Turnstile | ブラウザ自動化でトークン取得 |
| リクエスト署名 | anti_contentトークン | JS実行環境からトークン抽出 |
| TLSフィンガープリント | JA3/JA4ハッシュ | curl_cffi(impersonate=chrome) |
| HTTP2フィンガープリント | HTTP2設定の比較 | curl_cffiのブラウザ模倣機能 |
データの所在:隠しJSONとエンベデッドステート
Temuのページには、画面に表示されている以上のデータが埋め込まれています。効率的なTemu価格スクレイパーを構築するには、これらのデータソースを正確に把握する必要があります。
__NEXT_DATA__風のステートブロブ
Temuの商品詳細ページ(https://www.temu.com/goods-[id].html)には、ページHTML内に<script id="__NEXT_DATA__">タグが含まれており、その中にJSON形式の完全な商品データが埋め込まれています。これを抽出することで、DOMセレクタに依存せずに構造化データを取得できます。
# ページHTMLから__NEXT_DATA__を抽出する例
import re, json
pattern = r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>'
match = re.search(pattern, html_text)
if match:
data = json.loads(match.group(1))
# data["props"]["pageProps"] 内に商品データが格納
product = data["props"]["pageProps"]
print(product.get("goods_id"), product.get("price"))
内部JSONエンドポイント
商品一覧ページの背後では、/api/poppy/v1/searchエンドポイントがJSONレスポンスを返しています。このエンドポイントは検索クエリ、ページネーション、ソート順をパラメータとして受け取り、商品リストを構造化JSONで返します。商品詳細の場合は、/api/poppy/v1/goods/detailエンドポイントが利用可能です。
ただし、これらのエンドポイントは前述のanti_contentトークンを必須とするため、ブラウザ環境からトークンを取得する仕組みが必要です。
ハッシュ化CSSクラスとdata-uniqid
HTML解析を余儀なくされた場合、TemuのCSSクラス名はランダムハッシュ(例:_3f2a1b)で生成されているため、クラス名ベースのセレクタは脆いです。代わりにdata-uniqid属性やdata-goods-id属性をセレクタのアンカーとして使用する方が安定します。
# 脆いセレクタ(避けるべき)
driver.find_element(By.CSS_SELECTOR, '._3f2a1b ._priceWrap')
# 安定したセレクタ(推奨)
driver.find_element(By.CSS_SELECTOR, '[data-goods-id="123456"] [data-uniqid]')
レート制限と地域別価格の問題
Temuはリクエスト頻度に厳しい制限を設けています。実測ベースでは、同一IPから50リクエスト/分を超えると429 Too Many Requestsが返り始め、100リクエスト/分でIPが一時的にブロックされます。この制限を回避するには、IPローテーションが不可欠です。
さらに重要なのは、Temuの価格と送料が地域によって異なるという点です。米国のユーザーに表示される価格と、ドイツのユーザーに表示される価格は、通貨、送料、在庫状況の点で異なります。したがって、Temu商品データAPIを正確に取得するには、ターゲット地域に対応したIPからリクエストを送る必要があります。
ProxyHatのresidentialプロキシでは、ユーザー名に国と都市を指定できます。
# 米国のIPからリクエスト
http://user-country-US:pass@gate.proxyhat.com:8080
# ドイツ・ベルリンのIPからリクエスト
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080
この機能により、各地域の実際の価格と送料を正確に取得できます。データセンターIPでは地域Geoが機能しないため、residentialプロキシの使用が必須となります。ProxyHatのロケーション一覧で対応国を確認できます。
| プロキシタイプ | Temuでの成功率 | Geoターゲティング | 推奨用途 |
|---|---|---|---|
| Datacenter | <5% | 不可 | 非推奨 |
| Mobile | 90%+ | 国レベル | 高難度ターゲット |
| Residential | 85%+ | 国・都市レベル | 推奨(価格比較) |
実装例:curl_cffi + ProxyHatで商品ページを取得
以下に、curl_cffiでChromeのTLSフィンガープリントを模倣しつつ、ProxyHatのresidentialプロキシ経由でTemu商品ページを取得し、__NEXT_DATA__から商品データを抽出するPythonコードを示します。
import re, json, time
from curl_cffi import requests
# ProxyHat residentialプロキシ(米国IP)
proxy_url = "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
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",
}
url = "https://www.temu.com/some-product-id.html"
response = requests.get(
url,
headers=headers,
proxies=proxies,
impersonate="chrome120",
timeout=30,
)
html = response.text
# __NEXT_DATA__から商品データを抽出
pattern = r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>'
match = re.search(pattern, html, re.DOTALL)
if match:
raw = match.group(1)
data = json.loads(raw)
props = data.get("props", {}).get("pageProps", {})
# 商品基本情報
goods_id = props.get("goods_id")
title = props.get("goods_name")
price = props.get("min_normal_price", {}).get("amount")
currency = props.get("min_normal_price", {}).get("currency")
# SKUリスト
skus = props.get("skuList", [])[:3] # 先頭3件のみ表示
print(f"goods_id: {goods_id}")
print(f"title: {title}")
print(f"price: {price} {currency}")
for sku in skus:
print(f" SKU: {sku.get('sku_id')} "
f"- {sku.get('price', {}).get('amount')} "
f"{sku.get('price', {}).get('currency')}")
else:
print("__NEXT_DATA__ not found")
実行結果のサンプル(truncated):
goods_id: 60109951337721
title: Wireless Bluetooth Earbuds Pro
price: 12.98 USD
SKU: 901234 - 12.98 USD
SKU: 901235 - 14.99 USD
SKU: 901236 - 16.50 USD
この例ではimpersonate="chrome120"を指定することで、Chrome 120のTLSハンドシェイクとHTTP2設定を再現し、Cloudflareのフィンガープリント検査を通過します。プロキシには米国のresidential IPを使用しているため、米国向けの価格と送料が取得できます。
スティッキーセッション、リトライ、ページネーション
スティッキーセッションで一貫性を確保
カート追加や送料計算など、複数リクエスト間でセッション一貫性が必要な場合は、ProxyHatのスティッキーセッション機能を使用します。ユーザー名に-session-フラグを追加することで、同一IPを維持できます。
# スティッキーセッション(同一IPを10分間維持)
proxy_url = "http://user-country-US-session-myabc123:YOUR_PASSWORD@gate.proxyhat.com:8080"
セッションIDは任意の文字列で、同じIDを指定し続ける限り同じ出口IPが使用されます。これにより、商品ページ→カート→送料確認の一連のフローでIPが切り替わることによる不整合を防げます。
リトライ戦略
429や503エラーが発生した場合は、指数バックオフでリトライを実装します。以下のガイドラインを推奨します。
- 初回リトライ:3秒待機
- 2回目:9秒待機
- 3回目:27秒待機
- 最大リトライ回数:5回
- 429が連続する場合は、プロキシIPを強制的にローテーション(セッションIDを変更)
import random, time
def fetch_with_retry(url, max_retries=5):
for attempt in range(max_retries):
session_id = f"retry-{attempt}-{int(time.time())}"
proxy = f"http://user-country-US-session-{session_id}:PASS@gate.proxyhat.com:8080"
try:
resp = requests.get(url, proxies={"https": proxy},
impersonate="chrome120", timeout=30)
if resp.status_code == 200:
return resp
elif resp.status_code == 429:
wait = 3 ** (attempt + 1) + random.uniform(0, 2)
time.sleep(wait)
else:
time.sleep(2)
except Exception as e:
time.sleep(3 ** attempt)
return None
ページネーション
検索結果のページネーションは、URLパラメータまたはAPIリクエストのpageパラメータで制御します。1ページあたり通常40〜50商品が返されます。全ページを取得する場合は、各ページリクエスト間に2〜5秒のランダム待機を挟み、人間らしいアクセスパターンをシミュレートします。
倫理と利用規約の考慮
Temuのスクレイピングは技術的に可能ですが、法的・倫理的な境界を理解することが重要です。
- 公開カタログデータのみ:ログイン不要で閲覧できる商品名、価格、画像URL、仕様情報の収集は、一般的に公開情報の取得として扱われます。
- アカウントスクレイピングは回避:ユーザーアカウントに紐づくデータ(注文履歴、個人情報、レビュー投稿者情報)のスクレイピングは、CFAA(Computer Fraud and Abuse Act)やGDPRの対象となる可能性が高いです。
- robots.txtの尊重:Temuのrobots.txtを確認し、Disallowディレクティブに従うことを推奨します。
- 利用規約の確認:Temuの利用規約(Terms of Service)には自動化アクセスの制限が含まれている可能性があります。商用利用の前に法務チームと確認してください。
- 公式マーチャントフィードの検討:あなたがTemuの出店者(merchant)である場合、Temu Merchant API経由で自身の商品データにアクセスできる可能性があります。この場合はスクレイピング不要で、APIキーによる認証済みアクセスが可能です。
スクレイピングの目的が価格比較や市場分析である場合、Webスクレイピングのベストプラクティスに従い、公開データの範囲内で適切なレートでアクセスすることが長期的な安定性に繋がります。
ProxyHatのセットアップとベストプラクティス
ProxyHatを用いたTemuスクレイピング環境の構築は、公式ドキュメントに従って数分で完了します。プラン一覧からresidentialプロキシのプランを選択し、ダッシュボードで認証情報を取得してください。
推奨設定
- プロキシタイプ:Residential(住宅IP)。データセンターIPはTemu/Cloudflareによって即座にブロックされます。
- Geoターゲティング:
-country-USで米国価格を取得。欧州価格が必要な場合は-country-DEや-country-GBを使用。 - セッション管理:商品一覧のクロールにはリクエストごとにIPをローテーション(セッションIDなし)。カート/送料確認にはスティッキーセッション(
-session-付き)を使用。 - 同時接続数:初期は10〜20同時接続から開始し、成功率を監視しながら徐々に増加。ProxyHatでは100同時セッション以上もサポートしています。
- タイムアウト:30秒を推奨。Temuのページロードは重いため、短すぎるとタイムアウトが頻発します。
詳細なユースケースについては、SERPトラッキングのページも参照してください。価格モニタリングとSERPトラッキングは類似のプロキシ戦略を共有しています。
Key Takeaways
- HTML解析よりJSONエンドポイントを優先:
__NEXT_DATA__や内部APIから構造化データを取得する方が、ハッシュ化CSSクラスのDOM解析より圧倒的に安定。 - curl_cffiでTLSフィンガープリントを模倣:標準の
requestsライブラリではCloudflareにブロックされる。impersonate="chrome120"でブラウザと同等のTLSハンドシェイクを再現。 - Residentialプロキシが必須:データセンターIPは成功率5%未満。Residentialで85%以上の成功率。Geoターゲティングで地域別価格を正確に取得。
- スティッキーセッションで一貫性:
-session-フラグで同一IPを維持し、カート/送料フローの整合性を確保。 - 公開データのみ、適切なレート:アカウントデータは回避、robots.txtを尊重、1リクエスト/2〜5秒のペースで人間らしいアクセスを維持。
FAQ
Temuのスクレイピングとは何ですか?
Temuのスクレイピングとは、Temuプラットフォーム上の商品名、価格、SKU情報、送料などの公開カタログデータを自動化されたプログラムで取得する手法です。Temuには公開APIがないため、HTMLページの解析または内部JSONエンドポイントへのリクエストを通じてデータを収集します。価格比較サイト、市場分析ツール、在庫監視システムなどの構築に利用されます。
なぜTemuスクレイピングにプロキシが必要なのですか?
TemuはCloudflare WAFとTurnstileによるアンチボット保護を導入しており、データセンターIPからのリクエストを即座にブロックします。また、同一IPからの連続リクエストは50リクエスト/分で429エラー、100リクエスト/分でIPブロックが発生します。Residentialプロキシを使用することで、住宅IPからのアクセスとして検出を回避し、IPローテーションでレート制限を回避できます。
Temuスクレイピングに最適なプロキシタイプは何ですか?
Residential(住宅)プロキシが最適です。データセンターIPはCloudflareによって5%未満の成功率でブロックされますが、Residentialプロキシでは85%以上の成功率が期待できます。さらに、Temuの価格と送料は地域によって異なるため、-country-USや-country-DEのようなGeoターゲティング機能を持つプロキシが必要です。Mobileプロキシも高い成功率を示しますが、コストが高いため、通常はResidentialで十分です。
Temuスクレイピングでのブロックを回避するにはどうすればよいですか?
ブロックを回避するには4つの対策を組み合わせます:(1) curl_cffiでChromeのTLS/HTTP2フィンガープリントを模倣、(2) Residentialプロキシで住宅IPを使用、(3) 1リクエストあたり2〜5秒のランダム待機で人間らしいアクセスパターンを作成、(4) 429エラー時は指数バックオフでリトライし、セッションIDを変更してIPをローテーション。また、anti_contentトークンの取得にはブラウザ自動化ツールの併用が効果的です。






