1. 项目回顾:为什么要碰Temu的验证码
先说结论:Temu验证码逆向这个事,说白了就是跨境卖家、数据采集工程师和做竞品分析的团队,在自动化流程里绕不开的一道坎。
Temu现在风头正劲,平台上的商品数据、价格策略、销量排行,都是无数卖家眼里的香饽饽。但你想用脚本批量拉数据,平台也不是吃素的——滑块验证码、图形点选、行为轨迹检测,一层套一层。我自己最早碰这个项目,就是帮一个做选品分析的团队解决商品数据抓取时的验证码拦截问题,当时手动过验证码一天能点废三个鼠标,效率低得让人抓狂。
这篇文章我不打算写那种“某某平台一键破解”的标题党内容,而是把自己从头到尾梳理Temu验证码体系的思路、实操步骤、踩过的坑,以及最后稳定运行的方案,完整拆给你看。适合三类人:
- 正在做跨境电商数据采集的技术同学
- 做竞品价格监控的运营团队
- 对JS逆向和验证码识别技术感兴趣的前端/爬虫工程师
不管你是刚入行的新手,还是已经写过不少采集脚本的老手,这篇文章里都有值得你参考的东西。下面我尽量按照“从认识到实操,再到排坑”的顺序来讲,保证你能一步步跟着走下来。
2. 验证码逆向的整体思路与方案选型
2.1 Temu验证码体系的真实构成
先说一个很多人容易误解的点:Temu的验证码不是只有一种,它是一个组合拳体系。我在实际逆向过程中,至少遇到三类:
一类是图形滑块验证码,最常见的场景是登录和商品接口高频访问时弹出。滑块样式和主流电商平台的类似,但细节上做了不少改动,后面我会详细讲。
另一类是行为验证码,比如要求你按顺序点击图中指定的文字或图案。这类验证码的难点不在识别图片本身,而在模拟行为轨迹——鼠标从哪个位置移动、停顿多久、点击的落点偏移,平台都有记录。
还有一类是静默验证码,这种最坑。你肉眼根本看不到任何验证界面,但它通过采集浏览器指纹、Canvas画布特征、WebGL渲染信息等数据,在后台完成人机判定。很多新手发现自己的脚本有时候能跑,有时候被拦截,其实大概率就是触发了静默验证。
搞清楚这个体系很重要,因为很多教程一上来就让你搞OCR识别,结果只解决了其中一小部分问题。正确的思路是:先搞清楚当前场景触发了哪种验证码,再针对性设计识别和绕过方案。
2.2 为什么不能只靠模拟点击硬过
我第一次尝试的方案很粗暴:用Selenium驱动浏览器,配合坐标定位直接拖滑块。结果发现两个问题:
第一,滑块拖动的轨迹特征太明显。真实用户拖动滑块时,加速度是有起伏的,甚至会有轻微的回退。而代码直接从一个坐标线性移动到另一个坐标,或者即使用了加速减速曲线,也很容易被检测到——因为平台会记录你整条轨迹的每一帧坐标点,然后和真人数据做聚类对比。
第二,验证码的出现频次会动态变化。同一个IP和同一套浏览器指纹下,连续通过几次验证码后,平台会调高风险等级,下一次可能就换成更复杂的图形点选,或者直接进入静默验证。这说明平台的风控是动态的,你只靠一套固定方案很难长期稳定。
后来我换了思路:验证码逆向的核心,不是“绕”,而是理解它的生成和校验逻辑。只要你能把验证码的生成参数、校验参数、加密过程摸清楚,就能在脚本里直接模拟请求,跳过用户交互这一步。这才是效率最高的方案。
2.3 逆向方案的三大路线对比
做Temu验证码逆向,行业内主流路线有三条,我简单对比下:
| 方案路线 | 实现难度 | 稳定性 | 适用场景 |
|---|---|---|---|
| 纯图像识别模拟操作 | 低 | 低(易被轨迹检测) | 低频个人使用 |
| JS逆向直接构造请求 | 高 | 高(不依赖UI) | 中高频商业采集 |
| 浏览器自动化+行为模拟 | 中 | 中(需持续调参) | 中小规模爬取 |
纯图像识别模拟操作适合刚入门用来练手,但说实话,生产环境用它风险太高。浏览器自动化加行为模拟比较折中,但如果平台风控升级,你也得跟着升级轨迹算法。JS逆向构造请求是性能最高、最稳定的方案,但对逆向功底要求高,而且需要定期维护。
我自己的工程实践是混合路线:用JS逆向拿到验证码和校验相关的接口参数,再配合浏览器自动化去执行复杂验证,两者互补。这样既避免了全程依赖UI操作的低效率,又能在遇到更复杂验证时兜底。
3. 核心细节解析:从抓包到加密参数定位
3.1 环境准备与抓包工具配置
开始逆向之前,先把环境准备好。我用的工具组合如下:
- Chrome浏览器,版本尽量保持最新,DevTools协议支持更完善
- Charles或Fiddler抓包工具,用于定位接口请求
- Node.js环境,用于运行和调试逆向出来的JS代码
- Python 3.8+,用于后续写调度脚本和识别算法
抓包配置上有个细节需要注意:Temu的很多接口走的是HTTPS,而且做了证书校验,你直接装Charles的SSL代理证书有时候会被检测到。我的做法是用Chrome的--ignore-certificate-errors参数启动,配合Charles的SSL代理,这样能避免大部分证书校验问题。但这里要提醒一句,这个方法只适合本地开发和调试,生产环境还是应该用更合规的技术方案。
启动命令大概是这样的:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --ignore-certificate-errors --proxy-server=127.0.0.1:8888Windows用户把路径换成你的Chrome安装路径就行。启动之后,你会发现Charles里开始刷出大量的请求记录。接下来就是要在这个信息流里找到验证码相关的那几个关键接口。
3.2 关键接口定位与参数分析
我在实际操作中,验证码相关的接口主要集中在两个域名下:一个是生成验证码的接口,另一个是校验验证码的接口。通过Charles过滤关键词,比如captcha、verify、slider等,能快速缩小范围。
以图形滑块验证码为例,生成接口返回的JSON数据大概长这样:
{ "captcha_id": "a3f8b9c2d1e04f5a8b7c6d5e4f3a2b1", "bg_image": "https://xxx.cdn.com/captcha/bg_12345.jpg", "slider_image": "https://xxx.cdn.com/captcha/slider_12345.png", "y_position": 156, "challenge": "abc123def456ghi789" }这里的captcha_id是本次验证的唯一会话标识,后续所有校验请求都要带上它。bg_image是背景图,slider_image是滑块图,y_position是滑块应该在的位置的纵坐标,challenge是服务端下发的挑战码。
校验接口一般长这样:
{ "captcha_id": "a3f8b9c2d1e04f5a8b7c6d5e4f3a2b1", "answer": "{\"x\": 286, \"y\": 156, \"trace\": [[0,0,0],[12,3,18],...]}", "challenge": "abc123def456ghi789", "sign": "8f4d1a2b3c4d5e6f7a8b9c0d1e2f3a4b" }关键就在于answer字段里的坐标和轨迹数据,以及sign字段的加密结果。坐标决定了滑块的目标位置,轨迹决定了你是否像真人,而sign则是对这些数据的完整性校验。这三个点,就是整个逆向工程的核心目标。
3.3 加密参数break point定位与调用栈分析
拿到接口参数后,下一步就是找到生成这些参数的JS代码。我的做法是在Chrome的Sources面板里搜索关键字sign,定位到加密函数的位置,然后下断点。
这里有个很实用的技巧:先不要全量格式化混淆的JS文件,而是直接搜索关键字,找到关键代码块再格式化。因为Temu的前端JS做了重度混淆,整个文件格式化后有几千行,全量找代码很浪费时间。
比如搜索sign关键字,我定位到一段类似这样的代码:
function generateSign(params) { var sortedParams = sortKeys(params); var queryString = Object.keys(sortedParams) .map(function(key) { return key + "=" + encodeURIComponent(sortedParams[key]); }) .join("&"); var signStr = queryString + "&secret_key=sfl3k2j1h0g9f8d7s6a5"; return md5(signStr); }secret_key是硬编码在前端代码里的,但不同接口可能用不同的key,而且平台会不定期更换。所以你不能把找到的key写死,需要在代码里做一个动态提取机制,每次从最新的JS文件里自动提取。这个我在后面章节细讲。
3.4 滑块轨迹生成的核心算法
滑块轨迹是整个验证码校验中最有讲究的部分。平台会对轨迹数据做频域分析和行为建模,只做简单的线性移动必定被识别。我调试过大量轨迹后,总结出的可行做法是:用三段式轨迹模型——先加速、再匀速、最后减速微调。
具体实现可以用Python模拟,也可以把算法用JS实现后注入到浏览器里执行。我用Python举个例子,这样方便你理解轨迹生成逻辑:
import random import time def generate_trace(distance): """生成模拟真人滑块轨迹""" trace = [] current_x = 0 current_y = random.randint(-3, 3) t = 0 # 第一阶段:加速启动 while current_x < distance * 0.6: current_x += random.randint(3, 7) current_y += random.uniform(-1, 1) t += random.randint(8, 15) trace.append([current_x, current_y, t]) # 第二阶段:匀速接近 while current_x < distance * 0.95: current_x += random.randint(2, 4) current_y += random.uniform(-0.8, 0.8) t += random.randint(10, 20) trace.append([current_x, current_y, t]) # 第三阶段:减速微调 while current_x < distance: current_x += random.randint(1, 2) current_y += random.uniform(-0.5, 0.5) t += random.randint(15, 30) trace.append([current_x, current_y, t]) return trace这个算法本身不复杂,但有几个关键参数需要根据实际情况调整:
- 起始y偏移:真人按住滑块时,鼠标y坐标不会完全对齐,会有几个像素的偏移
- 加速的随机范围:加速度太固定也容易被识别,需要加随机扰动
- 时间戳间隔:每次移动记录的时间间隔不是均匀的,真实场景中会有快慢变化
- overshoot行为:很多真人拖动滑块时会拖过头一点,然后往回微调,这个行为可以通过最后一段“回退”来模拟
我验过的组合里,加上一次“overshoot”(先超过目标2-3像素,再回正)的轨迹,通过率明显更高。但没有一个轨迹生成算法能永远有效,平台只要更新了行为检测模型,你可能就得重新调参。
4. 实操过程与核心环节实现
4.1 图形验证码的OCR识别方案
说完了滑块,再聊聊图形点选验证码。这类验证码要求你在背景图中依次点击某些物体,比如“请依次点击图中的凤凰、龙、麒麟”。逆向这种验证码,核心就是两部分:目标检测和点击顺序。
目标检测的实现也有两条路:
- 接入第三方OCR服务:比如云厂商的文字识别接口、打码平台等,优点是准确率高,缺点是付费且响应慢
- 自建目标检测模型:用现成的开源模型如YOLO系列微调,优点是免费且可控,缺点是你需要准备标注数据训练
我的实践是先用打码平台验证流程是否跑通,之后量大了再考虑自建模型。打码平台的核心用法是:把验证码背景图传过去,平台返回识别结果和目标坐标。但这里有个风险,验证码返回的背景图通常带有干扰线、噪点,有时候还有形变,直接传原图识别率不高。
所以我在传图前会先做一轮预处理:去噪、增强对比度、裁掉多余边框。用OpenCV几行代码就能搞定:
import cv2 def preprocess_captcha(image_path): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 去噪 img = cv2.GaussianBlur(img, (3, 3), 0) # 二值化 _, img = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY) return img预处理之后,验证码里的主要元素会更清晰,识别准确率能提升不少。但这里要注意,过度处理也可能把关键特征抹掉,所以各项参数要针对不同验证码风格做微调,不是一套参数走天下。
4.2 前端JS环境补全与加密函数调用
定位到加密函数后,接下来要做的就是把前端JS搬到Node.js里运行。但Temu这类网站的前端代码依赖浏览器环境,直接丢到Node里通常跑不起来,因为缺少window、document等对象。这个时候就需要环境补全。
环境补全最简单的方案是引入jsdom库,模拟出基本的浏览器对象:
npm install jsdom示例代码如下:
const { JSDOM } = require('jsdom'); const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', { url: 'https://www.temu.com/', userAgent: 'Mozilla/5.0 ...' }); global.window = dom.window; global.document = dom.window.document; global.navigator = dom.window.navigator;然后,把之前定位到的混淆JS文件加载进来,就可以在Node环境里调用它的加密函数了:
const fs = require('fs'); const code = fs.readFileSync('./captcha.js', 'utf-8'); eval(code); // 在补全后的环境中执行 // 调用加密函数 const sign = generateSign({ captcha_id: 'xxx', answer: 'xxx' }); console.log(sign);这里要特别提醒,尽量不要用eval执行远程代码,会有严重的安全风险。更加稳妥的做法是:把需要的JS代码抠出来,做成独立的Node模块,只暴露必要的方法。这个过程叫“JS代码剥离”,是逆向工程中比较耗时间的部分。因为混淆后的代码里有很多自执行函数、变量重定义,抠代码时很容易破坏它的内部状态依赖,需要反复调试。
4.3 验证码坐标偏移量的计算技巧
无论是滑块还是点选,最终提交给服务端的坐标都必须准确。很多人忽略了图片显示比例的问题:前端页面展示的验证码图是经过CSS缩放或者被容器裁剪过的,而你从接口拿到的原始图片尺寸可能完全不同。如果你直接把识别出来的坐标提交上去,校验大概率失败。
我处理这个问题的方法是,写一个简单的比例换算函数:
def convert_coordinate(recognized_x, recognized_y, original_width, original_height, display_width, display_height): scale_x = display_width / original_width scale_y = display_height / original_height return round(recognized_x * scale_x), round(recognized_y * scale_y)还有一个细节是滑块验证码的目标位置。滑块要移动的距离不是“缺口所在坐标”本身,而是缺口位置减去滑块初始位置的差值。这个差值才是你answer字段里需要填的值。我第一次做的时候直接把缺口的绝对坐标提交上去,结果服务端一直提示验证失败,后来才发现位移量算错了。
另外,有些平台的背景图上缺口会有一个动态偏移范围,同一个背景图在不同会话下,缺口位置可能是固定的,也可能是上下浮动几个像素的。这个需要通过多次请求对比来确定,一旦发现是浮动偏移,你需要在生成坐标时也加入对应的随机偏移,才能保证通过率稳定。
4.4 完整流程串联:一个最小可用脚本
跑通上面的所有环节后,我把整个流程串了起来。给你看一个简化版的Python脚本,这里面包含了从请求验证码到提交校验的完整链路:
import requests import json session = requests.Session() headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64) x64...', 'Referer': 'https://www.temu.com/' } # 1. 请求验证码 captcha_resp = session.get( 'https://xxx.temu.com/api/captcha/generate', headers=headers ) captcha_data = captcha_resp.json() captcha_id = captcha_data['captcha_id'] bg_image_url = captcha_data['bg_image'] slider_image_url = captcha_data['slider_image'] # 2. 下载图片并识别缺口位置 bg_image = download_image(bg_image_url) slider_image = download_image(slider_image_url) target_x = recognize_gap_position(bg_image, slider_image) # 3. 生成轨迹和签名 trace_data = generate_trace(target_x) sign = call_node_sign_function(captcha_id, target_x, trace_data) # 4. 提交校验 verify_payload = { 'captcha_id': captcha_id, 'answer': json.dumps({ 'x': target_x, 'trace': trace_data }), 'sign': sign } verify_resp = session.post( 'https://xxx.temu.com/api/captcha/verify', json=verify_payload, headers=headers ) print(verify_resp.json())这个脚本是简化版,真实环境中还需要处理代理IP轮换、Cookie同步、频率控制等问题。但它已经能帮你走通一遍完整的验证码逆向流程,让你对整条链路有一个直观的认知。
5. 常见问题与排查技巧实录
5.1 验证码识别正确但校验依然失败
这是我最常遇到的问题之一。如果你已经根据接口返回的图片成功识别出了目标位置,但提交校验仍然失败,排查思路可以从以下几个方向入手:
第一,检查位移量的计算是否正确。我前面说过,把缺口绝对坐标当成移动距离直接提交,是新手最容易犯的错误。你先确认一下,提交的x值是不是滑块从起点到目标点的实际位移。
第二,检查时间戳和轨迹数据是否合理。有些平台不仅校验目标位置,还会校验整个拖动过程的总时长、轨迹形状和数据量。如果生成轨迹的时间间隔过于均匀,或者总时长不合理(比如0.5秒拖完一个长距离),也会被判为机器操作。
第三,检查sign签名是否正确生成。签名参数往往包含了captcha_id、answer等内容的哈希值,一旦你的请求体格式和签名算法使用的字符串拼接方式不一致,服务端计算出来的签名就会不同。
第四,也是很多人忽略的:检查cookie和其他请求头是否完整。验证码校验接口通常会要求当前会话的cookie有效,如果你在请求过程中清理了cookie,或者没有带上关键的会话标识字段,校验必然失败。
5.2 IP与设备指纹被风控标记怎么办
跑Temu验证码逆向跑多了,你会发现一个现象:同一个IP下,即使你的验证码每次都能正确通过,但触发的频次会越来越频繁,甚至最后访问任何页面都会弹出验证码。这个大概率就是被风控系统标记了。
我试过有效的方法有这些:
- 动态代理IP池:每次请求或短时间轮换一次IP,避免单一IP高频访问。但要注意,代理IP质量参差不齐,有的数据中心IP反而更容易触发验证
- 浏览器指纹伪装:如果用的是浏览器自动化方案,需要修改Canvas指纹、WebGL信息、时区、语言等特征,保持指纹一致性而不是每次都变
- 控制请求节奏:给脚本加上随机延时,模拟真实用户的浏览节奏,避免短时间内的连续高频请求
这三种方法组合使用,能显著降低触发验证码的概率,但也不是永久有效的。平台风控团队会持续更新策略,发现你的行为模式后又可能重新拦截,到那时候又要重新调整适配。
5.3 JS文件更新导致签名算法失效
签名算法失效,是最让人头疼的维护问题。Temu的JS文件更新频率不算特别快,但一旦更新了混淆逻辑,原来的签名生成方式可能就失效了。表现为:脚本突然开始大量校验失败,或者在浏览器里手动操作正常,但用脚本模拟就是不行。
遇到这种情况,我的处理步骤是:
- 重新抓包,拿到最新的JS文件
- 对比新旧JS文件的差异,定位变化的函数
- 用diff工具或BEACON类的AST分析工具,找到加密函数的入口和输出
- 抠出新的加密逻辑,替换旧代码
- 用多组历史请求数据做回归测试,确认签名生成结果一致
这个过程听起来简单,实际做起来可能要花几个小时。尤其是平台改版后把固定的secret_key改成了动态获取方式,你可能还要额外模拟一次获取key的请求。
所以这里再强调一遍:写逆向代码时,不要把加密算法相关的参数写死,尽量做成可配置的,或者每次启动时动态获取。这样下次平台更新算法时,你改配置就能适配,不用重写整套代码。
5.4 常见问题速查表
整理一个速查表,方便你遇到问题时快速定位:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 滑块校验总是失败 | 位移量计算错误 | 确认提交的是位移量而非绝对坐标 |
| 偶尔成功偶尔失败 | 轨迹时间间隔过于规律 | 加入随机扰动,模拟减速与微调行为 |
| 全部请求都开始弹验证码 | IP被标记或访问频率过高 | 轮换代理IP,加入随机延时 |
| 脚本更新后突然全失败 | 前端JS算法更新 | 重新抓取JS文件,更新签名算法 |
| 校验接口返回参数错误 | 缺少必要cookie或header | 补齐会话标识和浏览器指纹请求头 |
| 图片识别定位不准 | 未做预处理或图片缩放比例有误 | 添加OpenCV预处理,校准显示比例 |
这张表基本覆盖了我在实际使用过程中遇到的主要问题。但需要记住的是,这类平台的验证码机制是动态演进的,今天的排查方案明天不一定适用,关键还是掌握排查思路,而不是死记某个具体的参数。
6. 经验杂谈与避坑心得
6.1 工具链选型建议
做Temu验证码逆向,工具选型直接决定投入产出比。我踩过不少工具链的坑,最想提醒的是不要一上来就迷信重型框架。
如果你只是跑通一个小流程,直接用requests加Node.js就够了,不需要上Scrapy这样的重型框架。爬虫框架确实提供了调度、去重、中间件等一堆功能,但也会引入额外的复杂性和性能开销。对于验证码逆向场景,核心复杂度已经很高,再用框架反而束手束脚。
Python加Node.js的组合是目前我验证过最顺手的:
- Python负责整体流程调度、图像处理
- Node.js负责执行前端JS加密逻辑
两者之间通过子进程或HTTP接口通信即可。如果你要求极致性能,也可以用Python的PyExecJS库直接调用JS,但要留意PyExecJS的维护状态和兼容性。
6.2 日常维护的几个习惯
验证码逆向不是“做完就完事”的项目,它是一个需要持续维护的技术方案。以下这几个习惯,是我在长期实践中沉淀下来的:
保持JS文件版本管理。每次抓到新版JS文件,就把它存到单独的目录里,并且记录变更时间。这样一旦平台更新导致脚本失效,你可以快速回滚调试,也能通过历史版本对比快速定位变化点。
写日志,尤其是失败日志。我的脚本里会把每次请求的验证码ID、识别结果、校验返回、响应时长等关键字段都记录到日志中。这样遇到问题时,可以通过回溯日志,快速定位是哪一环出了岔子。
定期做回归测试。不要等到线上挂了才检查,建议每周跑一遍全流程的回归测试。设定好一个监控提醒,一旦测试失败,自动通知开发者处理。这个习惯帮我避免了好几次大规模的故障。
6.3 合规注意事项
最后必须提一个非常重要的事:做验证码逆向和自动化采集时,一定要在平台的服务条款允许范围内操作。
Temu这类平台,其用户协议通常明确禁止未经授权的自动化访问和数据采集。如果你的行为影响到平台正常服务或者被认定为恶意攻击,不仅可能被封号,还可能面临法律风险。做技术研究、学习原理没问题,但一旦投入商业用途,请务必确保自己已经理解了相关的法律风险,必要时要咨询专业的法律顾问。
我是把验证码逆向当成一项技术挑战来研究的,在自己搭建的测试环境中反复调试,而不是直接拿去做大规模的商业采集。技术本身是中性的,用在哪里、怎么用,决定了它带来的价值还是风险。
最后分享两个实用的小技巧
第一个技巧是使用Chromium的远程调试协议来抽取JS运行时变量。有时候你光靠静态分析很难确定某个加密参数的值是怎么来的,但如果你用Selenium或Puppeteer启动一个浏览器页面,手动触发一次验证码流程,然后通过远程调试协议直接读取页面里的全局变量,很多疑问就能迎刃而解。比如某个加密用的secret key,可能只有在运行时才被赋值到window对象的某个属性上。
第二个技巧是写一个简单的验证码样本收集器。每次脚本遇到新的验证码类型时,自动把背景图、滑块图、目标结果和最终的校验结果保存到一个目录里。积累几百条样本后,你会发现不同时期验证码风格的变化规律,对未来预测平台更新方向很有帮助。这个技巧救过我很多次,因为有了历史样本,平台一旦更新验证码样式,我可以快速对比出哪些部分变了。
做逆向这件事,本质上是一个不断否定自己、又不断重构方案的过程。Temu验证码逆向折腾下来,我最深的感觉是:没有一劳永逸的方案,只有不断迭代的适配能力。希望这篇分享能给你提供一些有价值的参考,少走一些我已经踩过的弯路。