遭遇场景

给 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/42.2 秒
8 个9.5 秒5/81.2 秒

同一个 IP 并发求解没有被额外惩罚(成功率与串行一致)。

边界:一枚只能用一次

这是整个架构的吞吐瓶颈。 实测把同一枚验证码在 TTL 内重复递上去,上游立刻回
3007,并且会连带作废池里其它几枚:

验证码失效,清空池 2 枚
账号 xxx 验证码挑战(in-body-3007),刷新重试

所以任何"池化复用"的优化都是错觉。唯一有效的做法是提前多备几枚,
让请求来了直接取现成的,而不是现场解。

典型配置(ZCode 场景):

CAPTCHA_POOL_MIN=8      # 池内目标枚数,低于则后台补
CAPTCHA_POOL_MAX=24     # 上限
ZCODE_CAPTCHA_RETRIES=6 # 单枚求解的重试次数

副作用:启动后要 30~60 秒预热(串行解满池)。期间请求会慢。

相关


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps