- 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 同步收尾
158 lines
9.2 KiB
Markdown
158 lines
9.2 KiB
Markdown
# 项目复盘:抖音「拖动重叠」验证码离线求解与自动化通过
|
||
|
||
> 本文是整个项目的经验总复盘:任务、四个阶段的演进、决定性发现、
|
||
> 踩坑清单、方法论沉淀与未竟事项。细节以各专题文档为准
|
||
>(索引见 [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 渲染让 `<img src>` 与截图法失效,但**图片 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 经验)。
|