Scraper Temu en 2026 n'a rien à voir avec le web scraping classique. La marketplace de PDD Holdings a dépassé 900 millions d'utilisateurs actifs mensuels et son infrastructure anti-bot a mûri : Cloudflare Turnstile, tokens signés anti_content, fingerprinting TLS/HTTP2, et blocage quasi-instantané des IP datacenter. Pour récupérer des données produits et prix fiables, il faut choisir entre parser un HTML volatil ou cibler les endpoints JSON internes — puis router tout le trafic via des proxys résidentiels avec géo-ciblage ville. Ce guide montre comment implémenter un Temu price scraper de bout en bout avec ProxyHat.
Pourquoi scraper Temu est difficile : le compromis API vs HTML
Temu n'expose aucune API publique documentée pour son catalogue. Contrairement à Amazon ou eBay, il n'existe pas de Product Advertising API officielle accessible aux tiers. Vous avez donc deux options :
| Approche | Avantages | Inconvénients |
|---|---|---|
| Parser le HTML des pages produits/recherche | Simple à démarrer, couvre tout le catalogue visible | Classes CSS hachées (ex. _2q1n6c0), changements fréquents, données partielles dans le DOM |
Endpoints JSON internes (/api/poppy/v1/search, /api/poppy/v1/goods-detail) | Données structurées complètes, moins de parsing fragile | Headers signés requis, rate limits agressives, anti-bot strict |
En pratique, l'approche hybride fonctionne le mieux : utilisez les endpoints JSON quand vous le pouvez, et tombez sur le HTML parsé (avec extraction du blob __NEXT_DATA__ ou équivalent) comme fallback. Les données de prix, SKU, stock et shipping sont plus complètes dans le JSON que dans le rendu DOM.
Le blob d'état embarqué
Les pages produit Temu embarquent souvent un objet JSON sérialisé dans un tag <script id="__NEXT_DATA__" type="application/json"> ou un équivalent propriétaire. Ce blob contient l'état hydraté de la page : goods_id, price, sku_list, store_id, et parfois les variantes de prix régionales. Extraire ce JSON avec une expression régulière ou un parser HTML est plus robuste que de cibler des classes CSS hachées qui changent à chaque déploiement.
Les classes CSS de Temu sont générées dynamiquement (ex._2q1n6c0,_3d7e821) et changent sans préavis. Ne dépendez jamais d'elles pour vos sélecteurs. Privilégiez les attributsdata-uniqid,data-goods-id, ou le blob__NEXT_DATA__.
La stack anti-bot de Temu : ce qui vous bloque
Temu s'appuie sur plusieurs couches de protection, chacune ciblant un vecteur d'attaque différent :
1. Cloudflare Turnstile et challenge JavaScript
Temu utilise Cloudflare Turnstile pour filtrer les requêtes automatisées. Turnstile ne repose pas uniquement sur un CAPTCHA visuel : il analyse le comportement du navigateur, les propriétés navigator, les timings, et l'empreinte TLS. Les requêtes provenant de bibliothèques comme requests ou httpx standard échouent presque systématiquement car leur fingerprint TLS ne correspond pas à un vrai navigateur.
2. Le header anti_content et le token signé
Les endpoints JSON internes comme /api/poppy/v1/search exigent un header anti_content (ou un paramètre de query du même nom) contenant un token chiffré. Ce token est généré côté client par du code JavaScript obfusqué et inclut un horodatage, des paramètres de requête, et une signature. Sans ce token valide, l'API renvoie une erreur 400 ou 403. Reproduire l'algorithme de génération est possible mais fragile — la fonction change régulièrement. L'alternative pragmatique : utiliser un navigateur headless (Playwright/Puppeteer) pour générer le token, puis l'injecter dans des requêtes HTTP plus rapides.
3. Fingerprinting TLS/HTTP2 et blocage des IP datacenter
Même avec un token valide, le fingerprint TLS de votre client HTTP peut vous trahir. Selon les recherches de l'équipe JA3, l'empreinte TLS (cipher suites, extensions, ordre) identifie de manière fiable le client HTTP sous-jacent. requests utilise urllib3 qui a un fingerprint OpenSSL distinct de Chrome. Temu bloque les fingerprints non-navigateurs connus et les IP datacenter avec une latence de blocage souvent inférieure à 50 ms après la première requête.
C'est pourquoi les proxys résidentiels sont obligatoires. Les IP datacenter sont sur des listes noires commerciales (MaxMind, IP2Location) que Cloudflare consulte en temps réel. Un proxy résidentiel utilise une IP d'un FAI réel (Comcast, AT&T, Orange, Deutsche Telekom) qui n'est pas sur ces listes.
Rate limits réels et pourquoi le géo-ciblage ville compte
Temu n'a pas publiquement publié ses rate limits, mais les tests empiriques montrent les seuils suivants :
- Recherches : ~30 requêtes/minute par IP avant soft-block (code 429 ou page challenge).
- Pages produit : ~60-80 requêtes/minute par IP avant throttling.
- Endpoints JSON : plus restrictifs, ~15-20 requêtes/minute par IP.
- Ban permanente : après ~500 requêtes en 10 minutes depuis la même IP.
Au-delà des rate limits, il y a une raison critique d'utiliser le géo-ciblage : les prix et les options de livraison varient par région. Un produit à $9.99 à New York peut coûter $12.49 au Texas à cause des taxes locales et des frais logistiques. Les données de prix n'ont de sens que si elles reflètent la région cible.
Avec ProxyHat, vous spécifiez le pays et la ville directement dans le username :
http://user-country-US-city-new_york:pass@gate.proxyhat.com:8080
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080
http://user-country-FR-city-paris:pass@gate.proxyhat.com:8080
Pour le marché US (le plus surveillé), utilisez des sessions résidentielles US avec rotation par requête pour le scraping de catalogue, et des sessions sticky pour les flux panier/livraison. Consultez la liste des localisations ProxyHat pour vérifier la couverture par pays.
Implémentation pratique : scraper une page produit Temu en Python
Nous allons utiliser curl_cffi, une bibliothèque Python qui réplique le fingerprint TLS de Chrome via libcurl-impersonate. Cela résout le problème de fingerprinting sans avoir à lancer un navigateur complet. Combiné avec les proxys résidentiels ProxyHat, c'est l'approche la plus efficace pour un Temu product data API maison.
Installation
pip install curl_cffi selectolax
Code : fetch + parse d'une page produit
from curl_cffi import requests as cffi_requests
from selectolax.parser import HTMLParser
import json, re, time
# Configuration ProxyHat — proxy résidentiel US, rotation par requête
PROXY_URL = "http://user-country-US-city-new_york:VOTRE_MOT_DE_PASSE@gate.proxyhat.com:8080"
HEADERS = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"accept-language": "en-US,en;q=0.9",
"sec-ch-ua": '"Chromium";v="120", "Not(A:Brand";v="24", "Google Chrome";v="120"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"upgrade-insecure-requests": "1",
"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",
}
def fetch_temu_product(goods_id: str, session_id: str = None):
url = f"https://www.temu.com/goods.html?goods_id={goods_id}"
# Session sticky optionnelle pour cohérence panier/livraison
proxy = PROXY_URL
if session_id:
proxy = proxy.replace("user-", f"user-session-{session_id}-")
resp = cffi_requests.get(
url,
headers=HEADERS,
proxies={"http": proxy, "https": proxy},
impersonate="chrome120",
timeout=30,
)
if resp.status_code == 403:
raise Exception("Bloqué par Cloudflare — changez d'IP ou attendez")
if resp.status_code == 429:
time.sleep(10)
return fetch_temu_product(goods_id, session_id)
return resp.text
def parse_product_html(html: str) -> dict:
tree = HTMLParser(html)
# Extraction du blob __NEXT_DATA__ ou équivalent
script = tree.css_first('script#__NEXT_DATA__')
if script:
data = json.loads(script.text())
return extract_from_next_data(data)
# Fallback : regex sur le JSON embarqué
match = re.search(r'window\.__INITIAL_STATE__\s*=\s*({.*?});', html, re.DOTALL)
if match:
return json.loads(match.group(1))
# Dernier recours : attributs data-* dans le DOM
goods_node = tree.css_first('[data-goods-id]')
if goods_node:
return {
"goods_id": goods_node.attributes.get("data-goods-id"),
"price": goods_node.attributes.get("data-price"),
}
raise Exception("Données produit introuvables")
def extract_from_next_data(data: dict) -> dict:
# Structure typique (simplifiée) — à adapter selon la version
store = data.get("props", {}).get("pageProps", {}).get("store", {})
goods = store.get("goods", {})
return {
"goods_id": goods.get("goodsId"),
"title": goods.get("goodsName"),
"price": goods.get("price", {}).get("amount"),
"currency": goods.get("price", {}).get("currency"),
"sku_list": goods.get("skuList", []),
"store_id": goods.get("storeId"),
}
# Exécution
html = fetch_temu_product("601099053881")
product = parse_product_html(html)
print(json.dumps(product, indent=2, ensure_ascii=False))
Exemple de réponse JSON tronquée
{
"goods_id": "601099053881",
"title": "Wireless Bluetooth Earbuds, Noise Cancelling",
"price": "8.49",
"currency": "USD",
"sku_list": [
{"sku_id": "SKU_001", "color": "Black", "price": "8.49", "stock": 1240},
{"sku_id": "SKU_002", "color": "White", "price": "8.99", "stock": 856}
],
"store_id": "TEMU_ELECTRONICS_8821"
}
Cibler les endpoints JSON internes
Quand vous avez besoin de données plus riches (variantes complètes, infos logistiques, reviews structurées), les endpoints JSON internes sont plus fiables que le HTML. Voici les patterns principaux :
- Recherche :
GET https://www.temu.com/api/poppy/v1/search?search_key=KEYWORD&page=1&limit=50 - Détail produit :
GET https://www.temu.com/api/poppy/v1/goods-detail?goods_id=ID
Ces endpoints requièrent le header anti_content. Pour le générer sans reverse-engineering complet, utilisez Playwright pour charger une page, extrayez le token depuis les headers de requête interceptés, puis réutilisez-le avec curl_cffi pour les requêtes suivantes :
from curl_cffi import requests as cffi_requests
import time
PROXY = "http://user-country-US:VOTRE_MOT_DE_PASSE@gate.proxyhat.com:8080"
def search_temu(keyword: str, page: int = 1, anti_token: str = None):
url = "https://www.temu.com/api/poppy/v1/search"
params = {
"search_key": keyword,
"page": page,
"limit": 50,
"anti_content": anti_token, # Token obtenu via Playwright
}
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": "application/json, text/plain, */*",
"referer": f"https://www.temu.com/search_result.html?search_key={keyword}",
}
resp = cffi_requests.get(
url, params=params, headers=headers,
proxies={"http": PROXY, "https": PROXY},
impersonate="chrome120",
timeout=20,
)
return resp.json()
# Pagination avec rotation d'IP
for page in range(1, 6):
data = search_temu("bluetooth earbuds", page=page)
items = data.get("data", {}).get("items", [])
print(f"Page {page}: {len(items)} résultats")
time.sleep(2) # Respectez les rate limits
Sessions sticky : cohérence panier et livraison
Pour certains cas d'usage — vérifier les frais de livraison vers un code postal, simuler un parcours panier, ou suivre l'évolution du prix d'un SKU sur 24h — vous avez besoin que toutes les requêtes proviennent de la même IP. C'est là que les sessions sticky ProxyHat entrent en jeu.
# Session sticky : même IP pendant toute la durée de la session
STICKY_PROXY = "http://user-session-order_abc123-country-US-city-chicago:VOTRE_MOT_DE_PASSE@gate.proxyhat.com:8080"
# Toutes les requêtes avec ce proxy utilisent la même IP résidentielle
resp1 = cffi_requests.get("https://www.temu.com/goods.html?goods_id=601099053881",
proxies={"http": STICKY_PROXY, "https": STICKY_PROXY}, impersonate="chrome120")
resp2 = cffi_requests.get("https://www.temu.com/api/poppy/v1/shipping?goods_id=601099053881&zip=60601",
proxies={"http": STICKY_PROXY, "https": STICKY_PROXY}, impersonate="chrome120")
# resp1 et resp2 voient le même prix et les mêmes frais de livraison
La durée de vie d'une session sticky ProxyHat est configurable. Pour le suivi de prix en continu, créez une nouvelle session toutes les 10-15 minutes pour éviter que l'IP ne soit flaggée. Pour le scraping de catalogue en masse, utilisez la rotation par requête (pas de flag session-) pour maximiser la diversité d'IP.
Erreurs courantes et cas limites
1. Utiliser des proxys datacenter
C'est l'erreur n°1. Les IP datacenter (AWS, DigitalOcean, OVH) sont sur les listes noires de Cloudflare. Vous obtiendrez un code 403 dès la première requête. Utilisez uniquement des proxys résidentiels ou mobiles. Consultez la tarification ProxyHat pour le plan résidentiel.
2. Ignorer le géo-ciblage
Scraper avec une IP allemande pour obtenir des prix US donne des résultats faux. Les prix, taxes, frais de livraison et disponibilités SKU sont régionalisés. Spécifiez toujours country-US pour le marché américain.
3. Pas de retry avec backoff exponentiel
Les blocages temporaires (429, 503) sont inévitables à grande échelle. Implémentez un retry avec backoff :
import time, random
def fetch_with_retry(url, max_retries=5):
for attempt in range(max_retries):
try:
resp = cffi_requests.get(url, impersonate="chrome120",
proxies={"http": PROXY, "https": PROXY}, timeout=20)
if resp.status_code == 200:
return resp
elif resp.status_code in (429, 503):
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
else:
resp.raise_for_status()
except Exception as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
raise Exception("Max retries atteint")
4. Négliger les headers
Les headers sec-ch-ua, sec-fetch-*, accept-language doivent correspondre à un vrai navigateur Chrome. Un header User-Agent seul ne suffit plus en 2026.
5. Cibler des classes CSS hachées
Les sélecteurs comme div._2q1n6c0 > span._3d7e821 casseront au prochain déploiement. Utilisez data-goods-id, data-uniqid, ou le blob __NEXT_DATA__.
Considérations éthiques et légales
Le scraping de Temu soulève des questions légales sérieuses. Voici les principes directeurs :
- Données publiques uniquement : limitez-vous au catalogue visible sans connexion (prix, titre, images, SKU). Ne scrapez pas de données utilisateur, d'avis nominatifs, ou de données de compte.
- CFAA (US) et GDPR (UE) : aux États-Unis, le Computer Fraud and Abuse Act s'applique potentiellement au scraping si vous contournez des mesures d'accès. En UE, le RGPD protège les données personnelles — un prix produit n'est pas personnel, mais une adresse IP l'est. Consultez un avocat.
- Respectez le robots.txt et les ToS : lisez les conditions d'utilisation de Temu avant de scraper. Certaines clauses interdisent explicitement l'accès automatisé.
- Rate limiting éthique : ne envoyez pas plus de trafic que nécessaire. Un rythme de 5-10 requêtes/seconde avec rotation d'IP est raisonnable ; 100+/seconde est un abus.
- Préférez le flux marchand officiel : si vous êtes un vendeur Temu ou un partenaire autorisé, utilisez le Temu Merchant API / Seller Center pour accéder à vos propres données. C'est plus fiable et légalement sûr.
Pour des contextes d'usage plus larges du web scraping avec proxys, consultez notre guide sur le web scraping et notre page sur le suivi SERP. La documentation ProxyHat détaille toutes les options de configuration.
Points clés à retenir
- Pas d'API publique : choisissez entre parser le HTML (avec extraction du blob
__NEXT_DATA__) ou cibler les endpoints JSON internes/api/poppy/v1/avec tokenanti_content. - Proxys résidentiels obligatoires : les IP datacenter sont bloquées en moins de 50 ms par Cloudflare. Utilisez des IP résidentielles avec géo-ciblage pays et ville.
- Fingerprint TLS : utilisez
curl_cffiavecimpersonate="chrome120"pour répliquer le fingerprint TLS de Chrome. - Rate limits : ~30 req/min pour la recherche, ~60-80 req/min pour les pages produit, ban permanente après ~500 req en 10 min par IP.
- Sessions sticky pour la cohérence panier/livraison ; rotation par requête pour le scraping de catalogue en masse.
- Légalité : données publiques uniquement, respectez les ToS, consultez un avocat pour le CFAA/RGPD.
Avec cette architecture — curl_cffi + proxys résidentiels ProxyHat + géo-ciblage ville + extraction JSON — vous pouvez construire un Temu price scraper fiable qui collecte des données produits à l'échelle. Commencez par un petit volume (100 produits/jour), validez la qualité des données, puis montez en charge progressivement. La tarification ProxyHat s'adapte à tous les volumes.






