Files

6.1 KiB
Raw Permalink Blame History

经验:验证码图片的获取方法(大图 + 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:

{
  "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 即得原始字节:

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。

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 秒自检

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")

通过后即可联调:

python captcha_solver.py xxx.jpeg xxx-mark.png --tip-y <get响应里的tip_y>