Files

136 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 经验:验证码图片的获取方法(大图 + mark 小图)
> 面向对接方:要把图片喂给 `captcha_solver`,第一步是怎么把两张图拿到手。
> 一句话结论:**不要从页面 DOM 拿图,从 `/captcha/get` 网络响应里取 URL 再下载**。
## 0. 背景:为什么不能从页面元素拿图
抖音系验证码(rmc-nocaptcha)的图片由 SDK 的 **wasm 解码后直接进 GPU 合成层**渲染:
| DOM 手段 | 结果 |
| --- | --- |
| `img.src` / `currentSrc` | 是 iframe 页面自身 URL(占位),fetch 回来是 HTML |
| `img.naturalWidth` | 0(图片从未进入 img 解码管线) |
| `canvas.toDataURL()` | 输出全黑透明(RGBA 全 0) |
| CSS background / shadow DOM | 全部 none |
但图片 URL 会**正常发 HTTP 请求**,响应 JSON 里就有完整的 CDN 直链 —— 这才是可靠入口。
## 1. 推荐方法:从 `/captcha/get` 响应取图(自动化/生产)
### 1.1 响应结构
POST `https://verify.zijieapi.com/captcha/get` 的响应 JSON:
```json
{
"data": {
"question": {
"url1": "https://p9-catpcha.byteimg.com/...~tplv-188rlo5p4y-2.jpeg",
"url2": "https://p9-catpcha.byteimg.com/...~tplv-188rlo5p4y-1.png",
"tip_y": 58
}
}
}
```
- `url1`:背景大图,jpeg,**552×344**
- `url2`:mark 小图,png **110×110**,带透明通道(RGBA)
- `tip_y`:缺口顶 y 提示(340 显示系),求解时**直接传原始值**,求解器内部已处理,别自己乘 2
### 1.2 下载与使用
图片托管在 `p3/p9-catpcha.byteimg.com`,**公开直链,无需登录态或签名**,GET 即得原始字节:
```python
import base64, time, requests
from captcha_solver import solve_slide
q = resp_json["data"]["question"]
big = requests.get(q["url1"], timeout=10).content # jpeg 原始字节
mark = requests.get(q["url2"], timeout=10).content # png 原始字节(保持 PNG!)
# 方式 A:直接喂求解器
r = solve_slide(base64.b64encode(big).decode(),
base64.b64encode(mark).decode(),
tip_y=q["tip_y"])
# 方式 B:落盘成对保存(前缀一致,方便核对)
prefix = f"captcha_{int(time.time())}"
open(prefix + ".jpeg", "wb").write(big)
open(prefix + "-mark.png", "wb").write(mark)
```
下载到的字节**天然就是 552×344 / 110×110 规格**,与离线样本完全一致,无需任何缩放/转码。
### 1.3 三种拿到该响应的姿势
| 姿势 | 做法 | 适用 |
| --- | --- | --- |
| **CDP 监听(现成工具)** | `Target.setAutoAttach` + `Network.enable` 常驻监听,响应到达后 `Network.getResponseBody`,mime 为 image/* 或 URL 含 captcha/verify 的落盘 | 浏览器自动化。直接用 `try/captcha_net_capture.py`(输出 `try/captures/net_*_get.json` + 图片字节) |
| **中间代理** | mitmproxy / Charles / Fiddler 抓 `captcha/get` 导出 JSON | 有代理环境、不想写监听代码 |
| **自建协议请求** | 自己走完整 SDK 协议调 get 接口 | 仅适合已逆向 SDK 协议的团队;对接方不建议(需 captchaBody 加密轨迹) |
## 2. 无代码快速验证:DevTools 手动采集
1. Chrome 打开目标站 → F12 → **Network** → 过滤框输入 `captcha`
2. 触发验证码(见 §3)→ 列表出现 `get` 请求 → 点开看 **Response**
3. 复制 `question.url1` / `url2` → 新标签页打开 → 右键另存为(或把 URL 直接发给求解方)
4. 自检尺寸(见 §6)
## 3. 如何触发验证码(采集的前置条件)
已验证最可靠的触发法 —— **密码登录法**:
> 主页点「登录」→ 弹窗点「密码登录」→ 输手机号 + **随机错误密码**(如 `26969877Ab!`)→ 勾协议 → 点「登录」→ 必弹拖动重叠验证码。
密码错误提示是预期的,验证码照样触发。
工程要点:
- **弹不出题(触发限流)→ 清 cookie 立即解除**:CDP `Network.clearBrowserCookies` 后用 `Page.navigate` 显式回站(清 cookie 后 session 失效,`reload` 会带到空页)。
- **判断验证码是否真的弹出**:`iframe[src*="captcha"]` 的 `offsetParent !== null`(页面加载时就预加载了隐藏 iframe,`display:none` 不算触发)。
## 4. 兜底:抓不到网络时的截图法(不推荐,有损)
仅当完全拿不到网络响应时应急。CDP `Page.captureScreenshot` **必须 `fromSurface=False`**——wasm/GPU 层不走默认 surface 截图通道,默认参数截出来是空白。
已知代价:
- `clip` 参数被忽略,返回全屏需自行裁剪;
- mark 裁出来是**灰底、无 alpha 通道**,需按背景色反推 alpha 才能喂求解器;
- UI 显示是 340×212(缩放系数 ≈0.616),尺寸与求解器输入规格不符,属于有损数据。
细节见 [captcha-screenshot-capture.md](captcha-screenshot-capture.md)。
## 5. 高频踩坑清单(每条都实测踩过)
| # | 坑 | 正确做法 |
| --- | --- | --- |
| 1 | 从 DOM / img.src / canvas 提图 | wasm 渲染层拿不到,走网络响应 |
| 2 | mark 转 JPEG 或从截图裁出(丢 alpha) | 保持 PNG 原始字节,求解器要求 alpha 通道 |
| 3 | 用 UI 显示尺寸(340 宽)的图去求解 | 用 CDN 原始字节,天然 552×344 / 110×110,无需缩放 |
| 4 | 手动重放/拼改 `captcha/get` 请求参数 | 会 `501 参数错误`;取响应里的 URL 直接 GET 即可 |
| 5 | 拿到题后慢慢处理 | challenge 有效期约 **60~90s**,超时 `NotFoundChallengeId`;预热/浏览必须放在**触发之前** |
| 6 | `tip_y` 自己乘 2 | 求解器内部已处理,直接传服务端原始值 |
| 7 | 存图命名随意 | 成对同前缀:`xxx.jpeg` + `xxx-mark.png` |
| 8 | 失败后高频重试 | 位置错换题即可;连续失败触发行风控 5014 需分钟级冷却或换环境(清 cookie) |
## 6. 采集后 30 秒自检
```python
import cv2
big = cv2.imread("xxx.jpeg")
mark = cv2.imread("xxx-mark.png", cv2.IMREAD_UNCHANGED)
assert big.shape[:2] == (344, 552), f"大图尺寸异常 {big.shape}"
assert mark.shape[:2] == (110, 110) and mark.shape[2] == 4, f"mark 缺 alpha 通道 {mark.shape}"
print("样本规格 OK")
```
通过后即可联调:
```bash
python captcha_solver.py xxx.jpeg xxx-mark.png --tip-y <get响应里的tip_y>
```