- method_l_shape: OK 候选尺度 ±0.0375 精修(步长 0.0125 + ±6px 窗口), GT live_165204 Δ 179→176(真人 177),离线 9/10 保持 - 插桩 JSON.stringify 捕获 SDK 日志明文;指针 probe 排除 coalesced/pressure - 拖动不跟手指标作废:post_pos 晚于 SDK 重置滑块,鼠标事件流始终完整 - 剩余 VerifyErr = 候选整体选错(视觉确证),方向:多候选质量评估 - docs: experiments/solution/retrospective 同步收尾
5.6 KiB
经验:验证码自动通过闭环(采集→求解→拖动)与行为风控现状
更新:2025-09-11 晚。此前 captcha-screenshot-capture.md 与 douyin-captcha-trigger-attempts.md 的方法 已被本篇取代/升级,本篇是当前最新状态。
1. 可复现触发法(已验证,最可靠)
密码登录法:主页点「登录」→ 弹窗点「密码登录」→ 手机号 18601010101 +
随机密码(如 26969877Ab!)→ 勾协议 → 点「登录」→ 拖动重叠验证码必弹。
(密码错误提示是预期的,验证码照样触发;真人手动拖可通过)
2. 网络层样本采集(取代 DOM/截图法,try/captcha_net_capture.py)
验证码图片不走 <img src>(wasm 解码进 GPU 层),但图片 URL 会正常发 HTTP 请求:
verify.zijieapi.com/captcha/get(POST)→ 响应 JSON 含question.url1(大图 552×344 jpeg)、url2(mark 110×110 RGBA png)、tip_y- 图片托管在
p3/p9-catpcha.byteimg.com,直接是原始字节
websocket 常驻监听(Target.setAutoAttach + Network 域 + getResponseBody)即可
把原图字节落盘 —— 与 captchas/ 离线样本完全同规格,无需缩放,solve 直接可用。
3. tip_y 服务端提示(重大发现)
tip_y × 2 = 缺口 y 坐标(mark 画布顶,原图 552×344 坐标系)。
10 组线上样本校准:solver 无约束 y 与 2×tip_y 差 ≤4px 的样本全部位置判定正确;
偏差大的(如 Δy=17)正是被服务端判 VerifyErr 的。
try/live_solve.py:用 tip_y 约束 y,在 score map 上重选 x —— 服务端等于
免费下发了 y 真值,只需求 x。
4. 拖动闭环(try/auto_solve_drag.py)
CDP websocket 直连全流程(不依赖 browser-use CLI):点刷新 → 轮询 get.json 落盘
→ live_solve → human_drag.gen_human_path 拟人路径 → Input.dispatchMouseEvent
拖滑块 → 读 verify 结果。get→verify 全程 <8s(challenge 有效期约 60-90s,
超时会 NotFoundChallengeId)。
实测确认:
- 滑块拖动 1:1 映射 mark 位移(拖 100px → mark 移 100px)
- 拖动距离 = solver x × 340/552(UI 缩放)
- 服务端响应三种:
验证通过/VerifyErr(轨迹过、位置差)/5014 操作过快(轨迹被拒)
5. 剩余 gap:5014 行为判定(未解决)
VerifyErr 与 5014 交替出现 = 位置与轨迹都接近临界。真人可过而脚本被 5014 拒,
原因在 captchaBody(~6.3KB 加密轨迹密文,含 w2/x86 设备指纹):
- 轨迹明文由 SDK wasm 加密,离线解不开
- 我的拟人路径(贝塞尔+噪声+过冲回拉)仍被判机器 —— SDK 采集的不止鼠标点, 可能含 pointermove 原生事件流、movementX、事件时间戳分布、设备指纹等
- 下一阶段方向:hook SDK 内部轨迹采集函数(wasm 前的 JS 层),直接注入 自定义轨迹数组,让 SDK 自己加密上报;或对比真人 captchaBody 字节结构定位 采集字段
6. 文件清单
| 文件 | 用途 |
|---|---|
try/captcha_net_capture.py |
网络层常驻监听,抓 get/verify 响应 + 图片字节 |
try/captcha_verify_watch.py |
专抓 verify 请求 POST 全文(captchaBody 对比用) |
try/live_solve.py |
tip_y 约束版求解(线上样本专用) |
try/human_drag.py |
拟人拖动路径生成器 |
try/auto_solve_drag.py |
一条龙闭环(刷新→solve→拖→读结果) |
try/captures/pair_* |
已采集线上样本对(17 组) |
7. 后续实验结论(2026-05-15/16,xdotool 硬件输入 + 换号循环)
上述"下一阶段方向"已被系统性实验否定——输入层不是根因:
- xdotool(OS 级 XTest 硬件鼠标) 拖 solve 位置 → 仍 VerifyErr; 事件属性(pressure/movementX/coalesced/isTrusted)与真人无差异;
- 真人轨迹重放(human_trace_pass.json,含 20-266ms 抖动间隔、4px y 振幅、 0.7s 终点停顿)→ 仍 VerifyErr;
- 会话预热(触发前 45-75s 页面游走)→ 不改变结果;
- 真人 PASS 与机器拖的差异只剩「行为分窗口」(5014 = 窗口内累计失败, 真人 PASS 夹在机器失败之间仍能过)与「位置小偏差」(±10px 级,视觉确证 live_191109 solve 正确但 live_135654 贴片在 solve 块内略偏)。
触发与限流工程结论
- 触发限流(不出题):清 cookie 即解除(
Network.clearBrowserCookies+Page.navigate回 douyin)——用户实测确认,无需等待; - 触发拒绝误报:错误检测正则勿包含"验证码登录"(tab 文字本身); 真正的号段拒绝文案是"仅支持验证码登录";
- reload 坑:清 cookie 后 session 失效,
Page.reload会把页面带到 空标签/跳转页,必须用Page.navigate显式回 douyin; - challenge 时效:约 1 分钟,采题后的任何预热/等待都会耗死题 (NotFoundChallengeId)——预热必须放在触发之前;
- 拖动时长保真:重放轨迹时 up 前最后一步的 dt(真人 ≈0.7s 终点对齐)
不能丢,否则 2.6s 被压成 1.9s;跳过 0 位移步时
t_plan仍要累加。
文件
| 文件 | 用途 |
|---|---|
try/rotate_loop.py |
清 cookie + 换号零等待循环(风控即换号) |
try/cold_start_attempt.py |
单次完整尝试(预热/采题/选优/xdotool 拖动) |
try/enrich_replay.py |
重放轨迹 60Hz 加密(实验结论:勿用,真人事件流稀疏) |
try/human_trace_pass.json |
真人 PASS 轨迹(live_165204,2.60s/17 move) |