2026年如何抓取Temu产品与价格数据:代理、反爬与隐藏JSON端点实战指南

Temu没有公开API,HTML结构频繁变动且受Cloudflare保护。本文讲解如何用curl_cffi配合ProxyHat住宅代理,通过内部JSON端点稳定抓取Temu商品与价格数据,涵盖反爬绕过、地理定位、粘性会话与合规要点。

How to Scrape Temu Product and Price Data in 2026: Proxies, Anti-Bot, and the Hidden JSON

如果你在2026年试图抓取Temu产品与价格数据,你会立刻面对一个根本性抉择:解析易变的HTML卡片,还是直接请求内部JSON端点。Temu没有提供公开的商品API,这意味着所有数据采集都必须逆向其前端架构。本文将带你从反爬机制到代码实现,完整走通一条可用的采集路径。

2026年如何抓取Temu产品与价格数据:先做架构选型

Temu的前端是一个高度动态的Next.js应用。商品列表页和详情页的DOM中,CSS类名经过哈希混淆(如 css-1a2b3c),每次发版都可能变化。直接用CSS选择器解析HTML虽然可行,但维护成本极高——一次前端更新就可能让你的抓取脚本全面失效。

更可靠的策略是找到页面背后的JSON数据源。Temu的搜索和详情页在加载时会请求内部API端点,例如 /api/poppy/v1/search 和商品详情接口。这些端点返回结构化JSON,字段稳定,远比HTML解析耐用。但代价是:这些端点有严格的反爬保护。

方案优点缺点适用场景
HTML解析(BeautifulSoup/lxml)入门简单,不依赖逆向类名哈希易变,维护成本高小规模一次性抓取
内部JSON端点字段稳定,数据完整需要逆向签名头,反爬严格持续价格监控、批量采集
嵌入式__NEXT_DATA__无需额外请求仅首屏数据,分页仍需API快速获取单页摘要

Temu的反爬技术栈:为什么数据中心IP会被秒封

Temu部署了多层反爬体系,理解每一层是成功抓取的前提:

1. Cloudflare Turnstile与JS挑战

Temu使用 Cloudflare Turnstile 作为前置防护。它会在浏览器中执行JS挑战,验证客户端是否为真实浏览器。对于无法执行JS的HTTP客户端,Cloudflare会返回403或挑战页面。Turnstile还会检查TLS指纹和HTTP/2设置帧,非浏览器客户端的指纹特征会被标记。

2. 签名头 anti_content

Temu的API请求需要携带一个 anti_content 头部(有时也以 anti_token 形式出现)。这是一个在前端JS中生成的签名令牌,包含时间戳、设备指纹和行为特征。没有有效签名的请求会被直接拒绝。逆向这个签名是抓取的核心难点——它由混淆JS生成,逻辑频繁更新。

3. TLS/HTTP2指纹检测

即使你伪造了User-Agent和Cookie,TLS握手阶段的ClientHello信息包和HTTP/2 SETTINGS帧仍然会暴露你的真实身份。Python requests 库的TLS指纹与Chrome完全不同,Cloudflare能在一秒内识别。根据 RFC 9113 对HTTP/2的规范,不同客户端的帧顺序和设置值存在可检测差异。

4. 数据中心IP即时标记

Cloudflare维护一个IP信誉数据库,数据中心IP段(AWS、GCP、DigitalOcean等)会被自动标记为高风险。来自这些IP的请求会触发更严格的挑战,甚至直接封禁。这就是为什么住宅代理是抓取Temu的必需品,而非可选项。

定位数据:隐藏JSON与端点结构

嵌入式状态数据

Temu的商品页面在HTML中嵌入了一个类似 __NEXT_DATA__ 的状态块。你可以在页面源码中搜索 <script id="__NEXT_DATA__" 或类似模式,提取其中的JSON。这个块包含了首屏渲染所需的商品基本信息:标题、价格、SKU列表、图片URL等。

<script id="__NEXT_DATA__" type="application/json">
{"props":{"pageProps":{"goods":{
  "goods_id": "2090999xxxx",
  "title": "Wireless Earbuds Pro",
  "salePrice": {"amount": 12.99, "currency": "USD"},
  "skuList": [
    {"skuId": "sku_001", "color": "Black", "stock": 120},
    {"skuId": "sku_002", "color": "White", "stock": 0}
  ]
}}}}
</script>

注意 data-uniqid 属性——Temu在商品卡片上使用这个属性标识每个商品,可以作为HTML解析时的稳定锚点,比哈希类名可靠得多。

内部JSON端点

搜索列表和分页数据通过 /api/poppy/v1/search 加载。关键参数包括:

  • searchKey — 搜索关键词
  • pageNum / pageSize — 分页控制
  • anti_content — 签名令牌(必需)
  • search_id — 搜索会话标识

商品详情则通过类似 /api/poppy/v1/goods/detail 的端点获取,传入 goods_id 即可返回完整SKU、价格和物流信息。端点URL和参数名可能随版本变化,建议用浏览器DevTools的Network面板抓包确认最新路径。

速率限制与住宅代理的地理定位

Temu对API请求有隐性速率限制。根据实测,同一IP在短时间内发送超过约20-30次/分钟的API请求就可能触发临时封禁(返回429或空响应)。更关键的是,Temu的价格和运费会根据用户地理位置动态变化——美国用户看到的价格和日本用户看到的价格可能不同,部分地区还有免运费门槛。

这意味着你的代理IP必须与目标市场匹配。如果你要监控美国市场定价,就需要使用美国住宅IP。ProxyHat支持国家级和城市级地理定位:

# 美国住宅代理(国家级)
http://user-country-US:pass@gate.proxyhat.com:8080

# 德国柏林住宅代理(城市级)
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080

# 日本住宅代理
http://user-country-JP:pass@gate.proxyhat.com:8080

城市级定位在需要精确模拟特定地区用户行为时非常有用。例如,Temu对偏远地区的运费计算可能与大城市不同,城市级IP能更准确反映真实展示价格。ProxyHat覆盖190+国家/地区的住宅IP池,你可以在 代理位置页面 查看完整列表。

Python实战:用curl_cffi + ProxyHat抓取Temu商品数据

要绕过TLS指纹检测,标准 requests 库不够用。推荐使用 curl_cffi,它基于libcurl的TLS模拟功能,能伪装成Chrome的TLS指纹。

安装依赖

pip install curl_cffi beautifulsoup4 lxml

抓取商品详情页并解析嵌入JSON

from curl_cffi import requests as cffi_requests
import json, re

# ProxyHat住宅代理 - 美国IP
proxy_url = "http://user-country-US:pass@gate.proxyhat.com:8080"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/131.0.0.0 Safari/537.36",
    "Accept-Language": "en-US,en;q=0.9",
}

# impersonate参数模拟Chrome的TLS/HTTP2指纹
response = cffi_requests.get(
    "https://www.temu.com/goods-detail-2090999xxxx.html",
    headers=headers,
    proxies={"http": proxy_url, "https": proxy_url},
    impersonate="chrome131",
    timeout=30,
)

# 提取嵌入式JSON状态块
match = re.search(
    r'<script[^>]*id="__NEXT_DATA__"[^>]*>(.*?)</script>',
    response.text,
    re.DOTALL,
)
if match:
    data = json.loads(match.group(1))
    goods = data["props"]["pageProps"]["goods"]
    print(f"商品ID: {goods['goods_id']}")
    print(f"标题: {goods['title']}")
    print(f"价格: ${goods['salePrice']['amount']}")
    for sku in goods.get("skuList", []):
        print(f"  SKU: {sku['skuId']} | {sku.get('color','')} | 库存: {sku['stock']}")
else:
    print("未找到嵌入数据,可能被Cloudflare拦截")

截断的示例JSON响应

{
  "goods_id": "2090999xxxx",
  "title": "Wireless Earbuds Pro, Bluetooth 5.3",
  "salePrice": {"amount": 12.99, "currency": "USD", "minAmount": 10.99},
  "originPrice": {"amount": 29.99, "currency": "USD"},
  "skuList": [
    {"skuId": "sku_001", "color": "Black", "stock": 120, "price": 12.99},
    {"skuId": "sku_002", "color": "White", "stock": 0, "price": 12.99}
  ],
  "shippingInfo": {"freeThreshold": 5.90, "estimatedDays": "5-10"},
  "rating": 4.6,
  "soldCount": 15234
}

调用内部搜索API端点

from curl_cffi import requests as cffi_requests
import json

proxy_url = "http://user-country-US:pass@gate.proxyhat.com:8080"

# 注意:anti_content需要从浏览器抓包获取或逆向生成
# 这里假设你已通过其他方式获取了有效签名
search_payload = {
    "searchKey": "wireless earbuds",
    "pageNum": 1,
    "pageSize": 50,
    "anti_content": "YOUR_ANTI_CONTENT_TOKEN_HERE",
    "search_id": "search_session_001",
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/131.0.0.0 Safari/537.36",
    "Content-Type": "application/json",
    "Origin": "https://www.temu.com",
    "Referer": "https://www.temu.com/search_result.html",
}

response = cffi_requests.post(
    "https://www.temu.com/api/poppy/v1/search",
    json=search_payload,
    headers=headers,
    proxies={"http": proxy_url, "https": proxy_url},
    impersonate="chrome131",
    timeout=30,
)

result = response.json()
items = result.get("data", {}).get("list", [])
for item in items[:5]:
    print(f"{item['goods_id']} | ${item['salePrice']} | {item['title'][:40]}")

粘性会话、重试与分页策略

粘性会话保证数据一致性

当你需要连续抓取一个商品的详情页、SKU变体和运费信息时,如果每次请求都切换IP,Temu可能返回不同地区的价格和库存。使用ProxyHat的粘性会话(sticky session)可以确保一系列请求来自同一个IP:

# 粘性会话 - 同一IP保持最多30分钟
sticky_proxy = "http://user-country-US-session-temu_abc123:pass@gate.proxyhat.com:8080"

# 第1次请求:商品详情
resp1 = cffi_requests.get(
    "https://www.temu.com/goods-detail-2090999xxxx.html",
    proxies={"http": sticky_proxy, "https": sticky_proxy},
    impersonate="chrome131",
    timeout=30,
)

# 第2次请求:SKU库存(同一IP,数据一致)
resp2 = cffi_requests.post(
    "https://www.temu.com/api/poppy/v1/goods/sku",
    json={"goods_id": "2090999xxxx"},
    proxies={"http": sticky_proxy, "https": sticky_proxy},
    impersonate="chrome131",
    timeout=30,
)

-session- 标志后跟自定义ID,ProxyHat会将该ID绑定到一个固定IP。这对于购物车模拟、运费计算和多步API调用至关重要。

重试与指数退避

import time, random

def fetch_with_retry(url, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        try:
            resp = cffi_requests.get(url, impersonate="chrome131", timeout=30, **kwargs)
            if resp.status_code == 200:
                return resp
            elif resp.status_code == 429:
                wait = (2 ** attempt) + random.uniform(1, 3)
                print(f"429限流,等待{wait:.1f}秒...")
                time.sleep(wait)
            elif resp.status_code == 403:
                print("Cloudflare拦截,更换会话ID")
                # 生成新session ID以切换IP
                kwargs["proxies"] = {k: v.replace("temu_abc123", f"temu_{attempt}") 
                                     for k, v in kwargs.get("proxies", {}).items()}
                time.sleep(5)
            else:
                print(f"HTTP {resp.status_code}")
                time.sleep(2)
        except Exception as e:
            print(f"请求异常: {e}")
            time.sleep(5)
    return None

分页控制

搜索结果分页时,每页请求间隔建议保持在3-5秒。同时,每次翻页应更换session ID以使用新IP,避免单IP请求过多触发限流:

for page in range(1, 11):  # 抓取前10页
    session_id = f"temu_page_{page}"
    proxy = f"http://user-country-US-session-{session_id}:pass@gate.proxyhat.com:8080"
    
    payload = {
        "searchKey": "wireless earbuds",
        "pageNum": page,
        "pageSize": 50,
        "anti_content": get_anti_content(),  # 你的签名生成函数
    }
    # ... 发送请求并解析 ...
    time.sleep(random.uniform(3, 5))

常见错误与边缘情况

  • anti_content过期:签名令牌通常有时效性(约5-10分钟),超时后请求会被拒绝。需要定期刷新。
  • 价格因地区不同:同一商品在不同国家的展示价格可能差异10%-30%。务必用目标市场的代理IP。
  • SKU库存为0不代表下架:Temu的库存数据可能因地区不同而异,某地区缺货不代表全球缺货。
  • Cloudflare挑战页:如果响应HTML中包含 cf-challengeturnstile 关键词,说明被拦截。检查TLS模拟参数是否匹配最新Chrome版本。
  • 移动端与桌面端数据差异:Temu移动端页面结构不同,价格和促销可能也不同。根据需求选择对应的User-Agent和页面URL。

合规与道德边界

抓取公开目录数据在多数司法管辖区属于合法行为,但必须遵守以下原则:

  • 仅采集公开可见的商品目录数据:商品名称、价格、图片、评分等公开展示的信息。
  • 不抓取用户账户数据:不登录、不采集个人信息、不绕过登录墙。这涉及 COPPA 和GDPR等隐私法规。
  • 遵守robots.txt:检查 https://www.temu.com/robots.txt,尊重其爬取规则。
  • 控制请求频率:合理限速,不对目标服务器造成压力。
  • 遵守服务条款:仔细阅读Temu的TOS。虽然公开数据抓取受CFAA判例保护(如 hiQ v. LinkedIn),但违反TOS仍有法律风险。
  • 优先使用官方渠道:如果你是Temu商家,应优先使用官方商家后台的数据导出功能或API,而非爬虫。

核心原则:采集公开商品数据用于价格情报和市场分析是合理使用场景,但不应大规模复制竞品完整目录用于转售。

ProxyHat配置与相关资源

ProxyHat提供住宅、移动和数据中心代理。对于Temu抓取,住宅代理是唯一推荐选择,因为数据中心IP会被Cloudflare即时标记。配置步骤:

  1. ProxyHat定价页 选择住宅代理套餐。
  2. 在控制面板中获取你的用户名和密码。
  3. 在用户名中添加地理定位和会话参数。
  4. 将代理配置集成到你的抓取脚本中。

更多抓取场景可以参考 网页抓取用例SERP追踪用例。完整的技术文档请访问 ProxyHat官方文档

关键要点总结

  • 优先用JSON端点而非HTML解析/api/poppy/v1/search 和商品详情端点返回结构化数据,比哈希类名HTML稳定得多。
  • TLS指纹是第一道门槛:用 curl_cffiimpersonate 参数模拟Chrome指纹,否则连Cloudflare第一关都过不了。
  • 住宅代理 + 地理定位是必需品:数据中心IP会被秒封,且价格因地区不同,必须用 -country-US 等参数匹配目标市场。
  • 粘性会话保证数据一致性:连续API调用使用 -session- 参数绑定同一IP。
  • anti_content签名是核心难点:需要定期从浏览器抓包或逆向JS获取。
  • 合规底线:仅采集公开目录数据,不登录、不抓取个人信息、控制请求频率。

准备好开始抓取Temu数据了吗?前往 ProxyHat定价页 选择适合你规模的住宅代理套餐,或查看 全球IP位置 确认覆盖范围。

准备开始了吗?

通过AI过滤访问148多个国家的5000多万个住宅IP。

查看价格住宅代理
← 返回博客