Temu 图片验证码
Temu 图片验证码 是 Temu 的整页拦截式挑战 + 图片点选 / 图标匹配(主图 + 若干候选小图,按顺序在主图上点出对应位置),另有拼图滑块与弧线滑动变体。页面被一整屏「安全验证」挡住,中间一张大图,题干或下方给出几个小图标 / 形状,要求按给定顺序在大图里点出它们;另一种形态是把一块碎片沿轨迹拖到缺口(碎片会在拖动后重新定位,直觉上的落点常常是错的)。
接口规格
| 项目 | 值 |
|---|---|
| type | temu |
| 必填参数 | bodyparts |
| 可选参数 | 无 |
| 返回 | 一组坐标点(solution.coordinates) |
| 怎么用这个解 | 在主图上要点的坐标,形如 `[{"x": 36, "y": 26}, …]`,顺序与 `parts` 对应 |
| 价格 | $1.53 / 1K 次 —— 按成功计费,没解出来退款 |
| 厂商文档 | Temu 官方文档 |
参数去页面哪里取
下面全部是可以直接照做的定位方法 —— DOM 属性名、JS 全局变量名、script 上的 query 参数。
这条不用找 sitekey,要的是图:body = 主图的 base64,parts = 候选小图 base64 的数组,返回的 coordinates 与 parts 顺序一一对应。
第一步先确认这确实是 Temu 的挑战页:
[ typeof window.challenge, // 'function' 就是它
[...document.scripts].map(s=>s.src)
.filter(s=>/kwcdn\.com\/upload-static\/assets\/chl\//.test(s)),
document.cookie.match(/__cf_bm=[^;]+/) ] // 有它只说明前面挂着 CDN,不是挑战本体第二步把图取出来。走 `fetch` 而不是 canvas,避免跨域污染:
const toB64 = u => fetch(u).then(r=>r.blob()).then(b=>new Promise(s=>{
const f=new FileReader(); f.onload=()=>s(String(f.result).split(',')[1]); f.readAsDataURL(b);}));
const imgs=[...document.querySelectorAll('img')]
.filter(i=>i.naturalWidth>20&&i.naturalHeight>20)
.sort((a,b)=>b.naturalWidth*b.naturalHeight-a.naturalWidth*a.naturalHeight); // 最大的那张是主图
Promise.all(imgs.map(i=>toB64(i.currentSrc||i.src))).then(a=>{
window.__body=a[0]; window.__parts=a.slice(1);
console.log('body',a[0].length,'parts',a.length-1);});题面画在 canvas 上时改用 c.toDataURL('image/png').split(',')[1];题面在 iframe 里时先把 DevTools 的执行上下文切到那个 frame 再跑同一段。
调用示例
一个端点解全部验证码类型 —— 换一种验证码只改 type 这一个字段。
curl -X POST https://api.everyinfra.com/api/v1/captcha \ -H "Authorization: Bearer omg_你的KEY" \ -H "Content-Type: application/json" \ -d '{"type":"temu","body":"<body>","parts":"<parts>"}'
{ "solution": { "coordinates": "…" }, "billing": { "charged": true, "credits": 110 }}
怎么确认目标站用的就是它
被拦截时服务器返回的不是商品页,而是一段几 KB 的混淆引导脚本。解混淆后逻辑很清楚:它从 `static.kwcdn.com` 加载 `/upload-static/assets/chl/js/<uuid>.js`(失败时依次回退到 `static-1.kwcdn.com`、`static-2.kwcdn.com`,`crossOrigin='anonymous'`,7 秒超时),加载成功后调用全局函数 `window.challenge(<挑战ID>, <base64 载荷>, <16位ID>, <回跳路径>, <第二个挑战ID>)`。挑战 ID 形如 `tc49a5bce333d00fd128864e2b00c393aa`(`t` + 32 位十六进制),base64 载荷约 300 字节、每次访问都不同。真正的题面由那段 `chl` 脚本渲染,图片以 `<img>` / `<canvas>` 挂到页面上。站点整体还在 Cloudflare 后面(响应带 `__cf_bm`),业务接口另有 `anti-content` 签名头那一层。
容易搞混的
响应里带 __cf_bm 很容易让人当成 Cloudflare 挑战 —— 不是,Cloudflare 只是它的边缘,真正拦你的是 Temu 自己的 chl 挑战。也别和拼多多系的 anti-content 请求头混:那是接口签名参数,不是验证码,解了验证码它照样要算。
常见部署场景
- **首次访问 `www.temu.com`** —— 数据中心 / VPN 出口几乎必然直接吃到整页挑战(2026-08-30 实测:一次普通 curl 拿回的就是挑战页而不是首页)
- 登录、注册、下单结算
- 商品列表频繁翻页、搜索、详情页高频访问
- `agentseller.temu.com` 卖家后台
已知的坑
主图与候选图必须分开传:主图放 body,候选图放 parts 数组。把候选图逐个当独立参数传是行不通的。
返回的坐标与 parts 顺序一一对应,别打乱顺序再去点。
base64 不能带 data:image/png;base64, 前缀、不能带换行。
图片常被 canvas 跨域污染,toDataURL 会抛 SecurityError —— 用 fetch 取原图再转就绕开了。
window.challenge 的挑战 ID 与那段 base64 载荷每次访问都变,不能缓存复用。
题型会轮换(点选、拼图滑块、弧线滑动都见过)—— 拿到页面先判断形态,别假设永远是同一种。
主图与候选图分开传:主图放 body,候选图放 parts 数组 —— 把候选图逐个当独立参数传是行不通的
返回的坐标与 parts 顺序一一对应,别打乱
常见问题
Temu 图片验证码 要传哪些参数?
必填 body、parts,没有可选参数。请求里 type 传 temu。
解出来的东西怎么用?
返回一组坐标点,取 solution.coordinates。在主图上要点的坐标,形如 [{"x": 36, "y": 26}, …],顺序与 parts 对应
parts 要去页面的哪里找?
重点找 parts、fetch、window.challenge。本页「参数去页面哪里取」一节写了全部位置与取法,含可直接粘进控制台的一行命令。
怎么确认目标站用的就是 Temu 图片验证码?
响应里带 __cf_bm 很容易让人当成 Cloudflare 挑战 —— 不是,Cloudflare 只是它的边缘,真正拦你的是 Temu 自己的 chl 挑战。也别和拼多多系的 anti-content 请求头混:那是接口签名参数,不是验证码,解了验证码它照样要算。
有什么容易踩的坑?
主图与候选图必须分开传:主图放 body,候选图放 parts 数组。把候选图逐个当独立参数传是行不通的。
多少钱一次,失败扣不扣?
$1.53 / 1K 次。按成功计费 —— 没解出来一律退款,所以失败不花钱。账单按次记,标价按每千次是为了让量级读得出来。