# 项目复盘:抖音「拖动重叠」验证码离线求解与自动化通过 > 本文是整个项目的经验总复盘:任务、四个阶段的演进、决定性发现、 > 踩坑清单、方法论沉淀与未竟事项。细节以各专题文档为准 >(索引见 [README.md](README.md))。 ## 1. 任务与最终状态 **任务**:给定验证码大图(552×344)与 mark 小图(110×110 RGBA), 离线求出 mark 应拖动重叠的目标区域偏移;进一步在浏览器里自动完成 「触发 → 采题 → 求解 → 拖动 → 通过」闭环。 **最终状态**: | 层面 | 状态 | | --- | --- | | 离线求解(captchas/ 17 组样本) | ✅ 方向感知倒角法 10/10 精确命中,`src/solve.py` 交付 | | 线上求解(URL 采题) | ✅ tip_y 锚定后 8 题 7 中(1 题差 1px 出容差) | | 拖动位置换算 | ✅ 实测校准(按钮 1:1 带 mark,`x × 340/552`) | | 拟人轨迹生成 | ✅ 三要素路径(非直线/非匀速/过冲回拉),`try/human_drag.py` | | 行为风控(5014) | ✅ 定位关键指纹(CDP pressure=0)并修复(force=0.5) | | 端到端一次通过 | ⏸ 被本机 IP 深度频控(5009)阻塞,需换环境冷启动验证 | ## 2. 四个阶段的演进 ### 阶段一:离线求解方法调研(A → L) 从 RGB 模板匹配(A/A2)出发,历经 SIFT(B)、旋转 NCC(C)、倒角(D/E)、 轮廓匹配(F)、各类分割(G–K),共 12 条路线全部失败或受限后, 才确认两条**决定性事实**(详见 [findings](../try/findings.md)、[experiments](experiments.md)): 1. **mark 的 RGB 是"捐赠补丁"**——内部是场景纹理,与洞内内容无像素关系 (掩码 NCC 全图 ≤0.57),一切 RGB/灰度模板匹配原理上不可行; 2. **唯一可靠信号是 alpha 轮廓 + 尺度 + 旋转**——洞是叠加在照片上的实心 暗色覆盖物,形状=mark 的 alpha 轮廓,尺度≈1.0、旋转≈0;干扰项是同形的 缩放/旋转实例。 最终方案 L:方向感知倒角(9 法向桶 × 多尺度)+ 洞内部特征(边缘密度、 内-环亮度差)+ 局部旋转扫描,17 组样本 10/10 精确命中。 **教训**:12 条失败路线里没有一条是"实现 bug",全是**信号假设错误**。 早期就该做"掩码 NCC 上限"这类决定性实验先证伪 RGB 路线,而不是逐个换实现。 ### 阶段二:浏览器触发与样本采集 - 抖音页面**预加载**验证码 iframe(display:none),「iframe 存在 ≠ 触发」, 判定要看 `offsetWidth > 0`; - wasm/GPU 渲染让 `` 与截图法失效,但**图片 URL 会正常发 HTTP 请求** → 网络层抓 `captcha/get` 响应 + 下载 `url1/url2` 即得与离线同规格的样本对; - 最可靠的触发法:密码登录弹窗(错误密码也必弹验证码); - **tip_y 服务端真值**:`question.tip_y × 2 = 缺口顶 y`(mark 画布顶坐标系)。 ### 阶段三:拖动闭环与行为风控(HOOK 路线) 闭环管线(`try/auto_solve_drag.py`):点刷新 → 采 get → solve → 拟人路径 → CDP 拖动 → 读 verify,get→verify 全程 <8s(challenge 有效期约 60–90s)。 拟人轨迹三要素(`try/human_drag.py`):贝塞尔弓形骨架+高斯抖动(非直线)、 三段速度结构(非匀速)、过冲后回拉瞄准。但最初 CDP 拖动仍全部 5014。 SDK 逆向(webmssdk/bdms/captcha.js 反混淆)确认:mousemove 入列规则、 X-Bogus 行为位(kMoveFast=16 等)、`isTrusted` 采集、captchaBody 在 XHR.send 前已 wasm 加密。**CDP 事件确实进入了 SDK 缓冲且 isTrusted=true**——问题不在 事件送达,在事件属性。 ### 阶段四:决定性突破与 solve 增强 1. **pressure 指纹**:CDP 缺省产生的 MouseEvent `pressure=0`,W3C 规范里 真鼠标恒 0.5。三个鼠标事件全加 `force:0.5` 后,错误码从 5014(行为拒) 稳定变为 VerifyErr(位置判定被执行)——行为闸门通过。 2. **错误码语义分层**(多次连线实验归纳): `5014 行为拒` → `VerifyErr 位置判了但错` → `5009 频控(压住一切)` → `NotFoundChallengeId challenge 过期`。**任何改动的效果判定都必须在频控 冷却后做单次干净尝试**,连环重试的 5014/5009 是无效信号。 3. **拖动映射实测**:按钮 1:1 带动 mark 预览;mark 初始相对大图 `(0, tip_y×2)`(每题垂直自动对齐,拖动只控 x);拖动距离 = `solve.x × 340/552`。 4. **线上分布漂移与 tip_y 锚定**:线上 URL 题的覆盖物是**亮白半透明贴片** (离线样本是暗色洞),方法 L 分数从 1–3 恶化到 3.6–7.7。实验发现亮度项 在亮贴片题上是反作用,而服务端免费下发的 `tip_y` 是硬真值——把它做成 y 锚(±8px)在候选中选精修分最低者:离线 10/10 不回归,线上 8 题 7 中, 3 个原来的错误/AMBIGUOUS 全部修正。已落地 `solve(big, mark, tip_y=...)`。 ## 3. 踩坑清单(最有复用价值) **图像算法**: - mark RGB 与洞内容无关,别在 RGB 匹配上浪费时间——先用上限实验证伪信号; - `TM_CCORR_NORMED` 带 mask 在边界产生 inf,要手写 filter2D 掩码 NCC; - 倒角会被密纹理击败,必须加方向约束 + 洞内部特征; - `np.arange` 浮点累积误差参与区间比较前先 `round(s,3)`。 **CDP / 浏览器自动化**: - `Input.dispatchMouseEvent` 缺省 `pressure=0` 是硬指纹,拖动类风控必补 `force:0.5`; - `button:"left"` 与 `buttons:0` 同时出现(矛盾状态)会毒化 SDK 拖动注册, verify 根本不发出——无按键时用 `button:"none"`; - 按下前不要发"带着 left 键的 hover",按下前后 `buttons` 状态要严格一致; - 验证码「点刷新」不重建 iframe → iframe 内注入的 XHR hook 持续有效, 比 netcap 的 Network 域 attach(时机不稳)可靠得多; - 验证通过后 iframe 自毁,判断通过与否最硬的信号是 `/json/list` 里 rmc iframe 消失。 **风控对抗**: - 5009 频控阈值苛刻(几十次尝试/小时后窗口 >20 分钟),实验要"省着打", 低质量题(solve 分差)宁可跳过也不出手; - 错误码会互相掩盖:频控期看到 5014 不代表行为判定就是这个结果。 ## 4. 方法论沉淀 1. **先证伪信号,再写实现**:RGB 路线若有早期的"掩码 NCC 上限"实验, 方法 A–K 的大半工作量可以省掉; 2. **无真值时用交叉验证建锚**:SIFT 可用样本、bbox 与 alpha 尺寸一致性、 人工目检候选可视化;线上则直接用服务端 tip_y 当真值; 3. **服务端下发的一切字段都是潜在真值**(tip_y 是免费的 y 答案); 4. **错误响应是免费的判层探针**:5014/VerifyErr/5009 的分层让"行为关 过没过、位置关过没过"可独立观测; 5. **离线评估 ≠ 线上分布**:覆盖物样式一变(暗→亮),离线 10/10 的方法 也会跑飞,线上必须保留否决器(tip_y 一致性检查)。 ## 5. 未竟事项 1. **端到端一次通过未闭环**:行为关(force=0.5)与 solve(tip_y 锚定) 都已各自验证,但本机 IP 已进深度频控,"冷启动单次拖动通过"需换环境 (新 IP / 无痕 profile)跑 `try/solo_attempt.py` 验证; 2. **AMBIGUOUS 题处理**:solve 置信度低的题目前只能跳过,可积累样本 校准"可信分数"阈值或引入第二方法仲裁; 3. **captchaBody 明文结构**:wasm 加密未破解,若 pressure 修复后仍被 行为层拒绝,需回退到 hook SDK 内部轨迹采集(wasm 前的 JS 层)注入轨迹。 ## 6. 交付物与文档索引 | 交付物 | 位置 | | --- | --- | | 求解 API(含 tip_y 锚定) | `src/solve.py` | | 核心算法(方法 L) | `src/method_l_shape.py` | | 结果验证工具 | `src/verify_result.py` | | 拟人路径生成 | `try/human_drag.py` | | 自动闭环 / 单次尝试 | `try/auto_solve_drag.py` / `try/solo_attempt.py` | | 网络层采集 | `try/captcha_net_capture.py` | ### 线上闭环最终状态(2026-05-16) - **位置层**:flatness 方法经真人 GT 实证系统性偏差后禁用(`FLAT_DISABLED`), tip_y 锚定 chamfer 为唯一选点路径(3 个可信 GT 全中);离线 9/10 保持; - **输入层**:CDP 合成事件、事件属性指纹、xdotool OS 硬件级鼠标、真人轨迹 重放(含 0.7s 终点停顿修复)全部排除——不是根因; - **触发层**:清 cookie 即解限流(无需等待)、错误正则去"验证码登录"误报、 清 cookie 后必须 Page.navigate 回站; - **VerifyErr 收敛(2026-09-15 深夜)**:尺度精修上线后**首次机器 PASS** (live_160548,s=1.0375 精修值,code=200「验证成功,正在为您登录」)。 社区资料确认 VerifyErr 语义 = 缺口距离错;剩余 VerifyErr 案例视觉确证为 候选整体选错(非小偏差)→ 后续方向是多候选质量评估 / 换题,而非拖动调参。 「拖动不跟手」指标作废(post_pos 读取晚于 SDK 重置滑块)。 专题文档:[solution.md](solution.md)(算法与 API)、 [experiments.md](experiments.md)(方法演进)、 [browser-automation/](browser-automation/)(触发、采集、拖动、CDP 经验)。