阿里云无痕验证无浏览器求解 (Headless Aliyun Traceless Captcha Solving)
遭遇场景
给 ZCode 额度反代网关 (ZCode Quota Reverse-Proxy Gateway) 当后端时,上游要求每个请求
都带一枚阿里云无痕验证参数(X-Aliyun-Captcha-Verify-Param)。爬虫/反代场景下没有真实浏览器,
只能在一个 Node 进程里"假装"出浏览器来把验证过掉。
**一句话结论:能用,但 jsdom 必须补真实浏览器指纹,否则风控一定判 F001;
而且一枚只能用一次,这决定了整个反代的吞吐上限。**
什么是无痕验证 (Traceless Verification)
用户点按钮也不会看到滑块/图形码,页面在后台悄悄采集环境信息(Canvas、WebGL、字体、插件、
时序、鼠标轨迹……)上报阿里云,由风控引擎判定"这是不是个人在真的浏览器里"。
判定通过后签发一个凭证,业务方拿它来证明"这次请求过了人机校验"。
判定结果在响应里长这样:
{"success": true, "verifyResult": false, "verifyCode": "F001"}verifyResult: false + verifyCode: F001 就是风控认为你在伪装浏览器。
业务方拿到这个结果会直接拒绝,表现为上游返回 3007 captcha verify failed。
求解原理
不需要开真浏览器,只要一个足够像浏览器的 DOM 环境:
Node 进程
└─ DOM 模拟库 (jsdom / happy-dom)
├─ 加载官方 SDK: https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js
├─ 补齐 SDK 依赖的浏览器 API
│ matchMedia / canvas / WebGL / Worker / OffscreenCanvas / screen ...
├─ 调 initAliyunCaptcha({ SceneId, region, prefix })
└─ getInstance → startTracelessVerification()
└─ success 回调 → 拿到 verifyParam产出的参数本质是一段 base64 的 JSON:
verifyParam = base64(JSON{ certifyId, sceneId, isSign, securityToken })其中 certifyId 是阿里云服务端签发的一次性凭据。sceneId / region / prefix 三个值从业务方自己的配置接口动态取(如 ZCode 的GET zcode.z.ai/api/v1/client/configs)。
核心坑:DOM 模拟库的默认指纹就是"我是机器人"
jsdom 默认的 navigator.userAgent 里明写着 jsdom/24.1.0,而且缺 window.chrome、plugins、screen 尺寸等一堆字段。风控一眼看穿,直接 F001。
必须补的最小集合:
Object.defineProperty(window.navigator, 'userAgent', { get: () => UA }); // 真实 Chrome UA
Object.defineProperty(window.navigator, 'platform', { get: () => 'Win32' });
Object.defineProperty(window.navigator, 'webdriver', { get: () => false });
Object.defineProperty(window.navigator, 'languages', { get: () => ['zh-CN','zh','en'] });
window.navigator.plugins = [1,2,3,4,5]; // 长度即可,SDK 只看有没有
window.chrome = { runtime:{}, app:{isInstalled:false}, csi(){}, loadTimes(){} };
Object.defineProperty(window.screen, 'width', { get: () => 1920 });
Object.defineProperty(window.screen, 'height', { get: () => 1080 });更彻底的方案(dengyie/zcode2api 的 61 KB 求解器)还会做:happy-dom 替代 jsdom、
alicdn 资源磁盘缓存、pe.* 字节码脚本的 VM 打补丁、网络层拦截器逐请求注入
client-hint / Origin / Referer、Event.isTrusted 补丁、以及模拟鼠标滑动轨迹。
实测性能(2026-09-24,本机 16 逻辑核)
成功很快,失败很慢——失败其实是卡在等风控超时:
| 结果 | 耗时 |
|---|---|
| 成功 | ≈ 2.3 秒 |
| 失败(风控判 F001 或超时) | ≈ 8 秒 |
单枚成功率约 50%~75%(风控是随机的,同一份代码同样的输入,结果会飘),所以必须重试。
可以并发,而且线性受益:
| 同时开 | 墙钟 | 成功 | 平均每枚 |
|---|---|---|---|
| 4 个 | 8.8 秒 | 2/4 | 2.2 秒 |
| 8 个 | 9.5 秒 | 5/8 | 1.2 秒 |
同一个 IP 并发求解没有被额外惩罚(成功率与串行一致)。
边界:一枚只能用一次
这是整个架构的吞吐瓶颈。 实测把同一枚验证码在 TTL 内重复递上去,上游立刻回3007,并且会连带作废池里其它几枚:
验证码失效,清空池 2 枚
账号 xxx 验证码挑战(in-body-3007),刷新重试所以任何"池化复用"的优化都是错觉。唯一有效的做法是提前多备几枚,
让请求来了直接取现成的,而不是现场解。
典型配置(ZCode 场景):
CAPTCHA_POOL_MIN=8 # 池内目标枚数,低于则后台补
CAPTCHA_POOL_MAX=24 # 上限
ZCODE_CAPTCHA_RETRIES=6 # 单枚求解的重试次数副作用:启动后要 30~60 秒预热(串行解满池)。期间请求会慢。
相关
- ZCode 额度反代网关 (ZCode Quota Reverse-Proxy Gateway)
- 基于无头浏览器与沙箱拦截的代码重构闭环 (Closed-Loop Code Refactoring via Headless Browser Verification and Sandbox Interception)
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps