第 108 章 验证码专项深度(图像处理与轨迹解法)
第 106/107 章讲了验证码的定位与原理;本章是验证码求解的完整实战手册:滑块/点选/旋转/拼图/无感型逐类展开,重点是图像处理代码(缺口定位、旋转角度、目标识别)与轨迹的配合。核心认知:解法正确只占一半,轨迹像人才是另一半。
📍 知识点地图 | 主题:验证码专项深度 | 前置:第106/107章 | 后续:— | 核心概念:滑块缺口定位、点选识别、旋转角度、轨迹配合
108.1 验证码分类学(先识别类型)
交互型(有界面交互):
□ 滑块(拖动缺口到目标)—— 最常见
□ 点选(选出指定物体/文字)
□ 旋转(旋转图片到正位)
□ 拼图(滑动拼块到缺口)
□ 文字输入(OCR 型,越来越少)
□ 算术/逻辑(简单题,罕见)
无感型(无交互,后台判定):
□ reCAPTCHA v3(评分制)
□ Turnstile(无感挑战)
□ hCaptcha enterprise(有时无感)
本质: 验证码 = 判定"是不是人"的挑战
→ 交互型: 解法正确 + 行为像人
→ 无感型: 行为+指纹真实(第 107 章)
108.2 滑块验证码(最常实战)
流程
1. 定位: 滑块按钮坐标 + 目标缺口坐标
2. 计算距离: 缺口中心 - 滑块中心 = 拖动距离
3. 生成轨迹: 贝塞尔曲线(第 107.5 算法)+ 抖动
4. 执行: CDP 原生事件拖拽(第 107.5)
5. 验证: 是否通过(失败重试)
滑块缺口定位(图像处理)
# 用 OpenCV 找滑块缺口(经典方法)
import cv2
import numpy as np
def find_gap(bg_img, slider_img):
"""
背景图 + 滑块小图 → 返回缺口 x 坐标
原理: 缺口区域的边缘特征与背景不同(亮度差异)
"""
# 转灰度
bg_gray = cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY)
slider_gray = cv2.cvtColor(slider_img, cv2.COLOR_BGR2GRAY)
# 边缘检测(缺口有明显的边缘响应)
bg_edge = cv2.Canny(bg_gray, 100, 200)
slider_edge = cv2.Canny(slider_gray, 100, 200)
# 模板匹配(滑块轮廓在背景中找最相似位置)
result = cv2.matchTemplate(bg_edge, slider_edge, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
# max_loc[0] = 缺口左边缘 x 坐标
return max_loc[0] + slider_img.shape[1] // 2 # 返回缺口中心
注意: 真实滑块背景的缺口"无滑块轮廓"(滑块已经被移走)
→ 常见做法: 对比"有缺口背景" vs "无缺口背景"(两张图差异)
→ 或: 缺口区域 = 背景中"最不像背景纹理"的矩形
更稳的方法(两张图对比):
diff = abs(bg_with_gap - bg_without_gap) # 差异图
→ 差异最大的列 = 缺口位置
距离校准(关键细节)
坑: 页面坐标 vs 图片坐标(缩放/偏移)
□ 截图比例(devicePixelRatio 缩放)
□ 滑块初始位置偏移(不是 0)
□ 缺口位置在"可拖动范围"内的偏移
校准: 用已知滑动距离 vs 实际效果反推比例(试一次)
108.3 点选验证码(选中指定物体)
流程
1. 识别任务: OCR/目标检测解析"请选出所有 X"(中文/英文提示)
2. 定位目标: 在图片中找目标物体(每格坐标)
3. 点击: 人类化点击(第 107.5 轨迹)
4. 多选: 逐一点击 + 提交
目标识别(两种路线)
# 路线 1: 视觉模板(简单场景)
# 提示词在图片里(如"包含汽车")→ 模板匹配/颜色分割找目标
# 路线 2: 深度学习目标检测(复杂场景)
# YOLO 类模型识别"汽车/红绿灯/行人"等
# 部署: ultralytics YOLOv8 加载模型 → 预测 → 输出框坐标
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model(captcha_image)
for r in results:
for box in r.boxes:
cls = int(box.cls[0])
if r.names[cls] in target_classes: # 目标类别
x1, y1, x2, y2 = map(int, box.xyxy[0])
click_point = ((x1+x2)//2, (y1+y2)//2)
实战建议:
简单验证码(图标固定)→ 模板匹配够用
复杂验证码(真实照片)→ YOLO 预训练模型
中文提示解析 → OCR(PaddleOCR)+ 语义映射
108.4 旋转验证码
原理
图片被旋转了角度,需要转回正位(拖动滑杆)
检测: 旋转角度计算 + 轨迹执行
角度计算(图像处理)
# 方法: 找"正位特征"(文字/对称结构)偏离角度
# 经典: 图像旋转不变性分析(方向梯度直方图 HOG / 傅里叶变换)
import cv2
import numpy as np
def find_rotation_angle(img):
"""用霍夫变换/边缘方向估计旋转角度(简化示例)"""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
# 检测直线 → 取主导角度
lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100)
angles = []
for line in lines:
x1, y1, x2, y2 = line[0]
angle = np.degrees(np.arctan2(y2-y1, x2-x1))
angles.append(angle)
# 统计主导角度(中位数)
return np.median(angles) if angles else 0
# 实际中: 多次小步旋转 + 评分(图像"正"的程度)更稳
# 旋转后检测文字/形状对齐度 → 找到对齐度最高的角度
108.5 拼图验证码
原理: 拼块 + 缺口(类似滑块但形状不规则)
解法: 缺口定位(同 108.2)+ 拼块拖动(同滑块)
差异: 距离 = 缺口位置 - 拼块初始位置(精确校准更重要)
108.6 无感验证码(reCAPTCHA v3 / Turnstile)
原理: 后台评分(不交互)→ 分数低于阈值才出交互挑战
→ 绕过核心 = 让评分高(指纹+行为真实,第 107 章)
关键点:
□ 评分输入: 行为/指纹/上下文/历史
□ 无"解法"可言 → 只能"优化分数"
□ 失败表现: 从无感降级为交互(然后走 108.2-108.5)
Turnstile 特别说明:
□ Cloudflare 的无感验证码
□ token 与页面上下文绑定(同页多次/跨页失效)
□ 方案: 路线 A(真实浏览器)成功率最高
108.7 轨迹-解法配合(成败关键)
错误: 答对了但轨迹像机器(直线/匀速/精确网格)
正确: 解法正确 + 人类化轨迹(第 107.5)
完整执行链:
1. 计算目标位置(图像处理)
2. 生成贝塞尔轨迹(弧线+抖动+加速减速)
3. CDP 原生事件逐点执行
4. 自然停顿(到达后停顿 100-300ms 再松手)
5. 失败重试(轨迹随机化,不重放同一轨迹!)
重试注意:
□ 每次重试生成新轨迹(旧轨迹可被关联检测)
□ 重试间隔随机(不是固定 2s)
108.8 检查清单
□ 验证码类型识别
□ 图像处理(缺口/角度/目标定位)
□ 坐标校准(页面 vs 图片)
□ 轨迹生成(贝塞尔+抖动)
□ CDP 原生事件执行
□ 自然停顿
□ 重试(新轨迹+随机间隔)
□ 成功率记录(报告素材)
动手练习
- 用 OpenCV 实现滑块缺口定位(找练习验证码截图,标注缺口中心)。
- 写一个"两张图差异法"找缺口(对比有/无缺口背景)。
- 用 YOLO 对点选验证码做目标识别(下载预训练模型跑一次)。
- 组合练习:滑块 + 贝塞尔轨迹 + CDP 原生拖拽全流程(测试靶场)。
深入阅读
- 本教材:第 106 章(验证码在 Bot Protection 中的定位)
- 本教材:第 107 章(轨迹算法/CDP 原生事件)
- 本教材:第 105 章(案例 1 的 CDP 拖拽实战)
- 外部: OpenCV 文档、ultralytics YOLO、验证码攻防研究