做爬虫和自动化测试的朋友,应该都被点选验证码折磨过。去年我做一个电商数据采集项目,被某平台的文字点选验证码卡了整整半个月。试过OpenCV模板匹配,识别率不到50%;用过Tesseract OCR,面对扭曲加干扰线的文字直接瞎猜;最后只能用第三方打码平台,1分钱一条,一天跑10万条就是1000块,成本高不说,还经常断供,高峰期要等几分钟才能返回结果。
后来我花了一周时间,用YOLOv11做了一套端到端的点选验证码识别系统,实测主流平台的文字点选、图标点选、顺序点选验证码,识别率稳定100%,单张识别耗时不到150ms,完全替代了打码平台。今天把整个方案完整分享出来,从数据集制作、模型训练、坐标精算到风控轨迹模拟,全是可直接落地的工程代码。
一、方案总览:为什么YOLO是点选验证码的终极解法
1.1 传统方案的致命缺陷
先给大家算一笔账,为什么传统方案走不通:
- OpenCV模板匹配:只能识别完全相同的图标,文字稍微扭曲、旋转就失效,抗干扰能力为0
- OCR识别:Tesseract、百度OCR对验证码专用字体的识别率不到30%,而且无法处理顺序点选
- 第三方打码平台:成本高、响应慢、不稳定,还存在数据泄露风险
1.2 YOLO方案的核心优势
YOLO作为目标检测模型,天生适合点选验证码场景:
- 能同时检测多个目标,输出每个目标的位置和类别
- 对扭曲、干扰、旋转、光照变化有极强的鲁棒性
- 轻量模型速度极快,CPU上就能跑,无需GPU
- 自主可控,零成本,7x24小时稳定运行
1.3 整体技术架构
我画了一张完整的识别流程图,大家可以先看整体逻辑:
1.4 技术选型
- 检测模型:YOLOv11n(最轻量,速度最快,精度足够)
- 预处理:OpenCV 4.10
- 部署:ONNX Runtime 1.19(跨平台,支持Python/C#/Java)
- 轨迹模拟:三阶贝塞尔曲线+人类行为模型
- 精度保障:多模型融合+负样本迭代训练
二、数据集制作:决定识别率的90%因素
很多人说自己的YOLO识别率低,90%的问题都出在数据集上。点选验证码的数据集制作没有任何捷径,就是要样本足够多、标注足够准、覆盖足够全。
2.1 样本采集
首先写一个简单的爬虫脚本,批量下载目标站点的验证码图片,至少采集2000张,越多越好。采集的时候要注意:
- 覆盖不同时间、不同IP段的验证码
- 包含所有可能出现的文字或图标
- 保存原始分辨率的图片,不要压缩
importrequestsimportosdefdownload_captcha(url,save_dir="datasets/images"):os.makedirs(save_dir,exist_ok=True)foriinrange(2000):try:res=requests.get(url,timeout=5)withopen(f"{save_dir}/{i}.png","wb")asf:f.write(res.content)print(f"已下载第{i+1}张")exceptExceptionase:print(f"下载失败:{e}")continueif__name__=="__main__":download_captcha("https://example.com/captcha")2.2 标注规范
使用LabelImg工具标注,这是最常用的YOLO标注工具,操作简单。标注规则一定要严格遵守:
- 框选要紧密:刚好包裹住文字或图标,不要留太多空白,也不要截断目标
- 类别要准确:每个文字或图标对应一个类别,比如“我”、“们”、“车”、“房子”
- 不要漏标:图片中所有的目标都要标注,哪怕是部分被遮挡的
- 格式选择:保存为YOLO格式(.txt),自动生成归一化的坐标
2.3 数据集划分
按8:2的比例划分训练集和验证集,目录结构如下:
datasets/ ├── images/ │ ├── train/ # 1600张训练图 │ └── val/ # 400张验证图 └── labels/ ├── train/ # 对应的标注文件 └── val/2.4 数据增强
这是提升模型泛化能力的关键。YOLOv11自带了强大的数据增强功能,训练时开启即可:
- 马赛克增强(Mosaic)
- 随机裁剪、旋转、翻转
- 亮度、对比度、饱和度随机调整
- 随机添加噪声和干扰线
三、模型训练:从0到1训练专属验证码模型
3.1 环境准备
pipinstallultralytics==8.3.0 opencv-python numpy pillow3.2 数据集配置文件
在项目根目录新建captcha.yaml,内容如下:
# 数据集路径path:./datasetstrain:images/trainval:images/val# 类别数量nc:36# 这里改成你自己的类别数# 类别名称,顺序要和标注时一致names:["0","1","2","3","4","5","6","7","8","9","我","们","中","国","北","京","上","海","车","船","飞机","房子","树","花","鸟"]3.3 训练命令
yolo detect train\model=yolov11n.pt\data=captcha.yaml\epochs=100\imgsz=640\batch=16\patience=15\mosaic=1.0\hsv_h=0.015\hsv_s=0.7\hsv_v=0.4\dropout=0.1参数说明:
model=yolov11n.pt:使用最轻量的n版本,CPU推理速度最快epochs=100:足够收敛,patience=15表示15轮没有提升就提前停止imgsz=640:输入尺寸,验证码一般640x640足够dropout=0.1:防止过拟合,提升泛化能力
3.4 训练结果评估
训练完成后,在runs/detect/train目录下会生成结果文件。重点看这两个指标:
- mAP@0.5:IoU阈值为0.5时的平均精度,要达到0.99以上
- Precision/Recall:精确率和召回率都要达到99%以上
如果指标不够,就继续增加样本,尤其是那些识别错误的样本,加入数据集重新训练,直到指标达标。
四、核心实现:推理+坐标精算+语义匹配
这部分是整个系统的核心,我会给出完整可运行的代码,复制粘贴就能用。
4.1 YOLO推理与目标提取
importcv2importnumpyasnpfromultralyticsimportYOLOclassYoloCaptchaSolver:def__init__(self,model_path="best.pt",conf_threshold=0.7):# 加载训练好的模型self.model=YOLO(model_path)self.conf_threshold=conf_threshold# 类别名称,和训练时一致self.class_names=["0","1","2","3","4","5","6","7","8","9","我","们","中","国","北","京","上","海","车","船","飞机","房子","树","花","鸟"]defdetect_objects(self,image_path):""" 检测验证码图片中的所有目标 返回:[{"class": "我", "x1": 100, "y1": 200, "x2": 150, "y2": 250, "conf": 0.98}, ...] """# 读取图片img=cv2.imread(image_path)ifimgisNone:raiseException("图片读取失败")# 执行推理results=self.model(img,conf=self.conf_threshold,iou=0.5)# 解析结果objects=[]forresultinresults:forboxinresult.boxes:# 获取坐标x1,y1,x2,y2=map(int,box.xyxy[0])# 获取置信度conf=float(box.conf[0])# 获取类别cls_id=int(box.cls[0])cls_name=self.class_names[cls_id]objects.append({"class":cls_name,"x1":x1,"y1":y1,"x2":x2,"y2":y2,"conf":conf})returnobjects4.2 像素级坐标精算
很多人用YOLO检测出来的坐标不准,导致点击失败,问题就出在中心点计算上。这里我用了一个更精准的方法,不是简单的取框的中心,而是取目标的质心:
defcalculate_precise_center(self,obj,img):""" 计算目标的精准质心坐标 """# 提取目标区域roi=img[obj["y1"]:obj["y2"],obj["x1"]:obj["x2"]]# 转换为灰度图gray=cv2.cvtColor(roi,cv2.COLOR_BGR2GRAY)# 二值化,分离目标和背景_,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)# 计算质心moments=cv2.moments(binary)ifmoments["m00"]!=0:cx_roi=int(moments["m10"]/moments["m00"])cy_roi=int(moments["m01"]/moments["m00"])else:# 如果质心计算失败,退化为框中心cx_roi=(obj["x2"]-obj["x1"])//2cy_roi=(obj["y2"]-obj["y1"])//2# 映射回原图坐标cx=obj["x1"]+cx_roi cy=obj["y1"]+cy_roireturn(cx,cy)4.3 语义匹配与顺序排序
根据验证码的提示文字,从检测到的目标中筛选出需要点击的目标,并按顺序排列:
defsolve_captcha(self,image_path,target_sequence):""" 解决点选验证码 target_sequence: 需要点击的目标顺序,比如["我", "中", "国"] 返回:点击坐标列表 [(x1,y1), (x2,y2), (x3,y3)] """# 读取图片img=cv2.imread(image_path)# 检测所有目标objects=self.detect_objects(image_path)# 构建类别到坐标的映射class_to_points={}forobjinobjects:center=self.calculate_precise_center(obj,img)ifobj["class"]notinclass_to_points:class_to_points[obj["class"]]=[]class_to_points[obj["class"]].append(center)# 按目标顺序生成点击坐标click_points=[]fortargetintarget_sequence:iftargetinclass_to_pointsandlen(class_to_points[target])>0:# 取置信度最高的那个click_points.append(class_to_points[target][0])else:raiseException(f"未找到目标:{target}")returnclick_points4.4 使用示例
if__name__=="__main__":solver=YoloCaptchaSolver("best.pt")# 示例:按顺序点击"我"、"中"、"国"points=solver.solve_captcha("captcha.png",["我","中","国"])print("点击坐标:",points)# 输出:点击坐标: [(123, 245), (356, 189), (521, 267)]五、风控绕过:人类点击轨迹模拟
只返回精准坐标是不够的,现在的风控系统都会检测鼠标的移动轨迹,如果是直线移动或者匀速移动,会直接被判定为机器人。
5.1 三阶贝塞尔曲线轨迹生成
用三阶贝塞尔曲线模拟人类的鼠标移动轨迹,加入随机抖动和加速度:
importrandomimportmathdefgenerate_bezier_path(start,end,num_points=20):""" 生成三阶贝塞尔曲线轨迹 start: 起点坐标 (x,y) end: 终点坐标 (x,y) num_points: 轨迹点数 """# 生成两个控制点control1=(start[0]+random.randint(-50,50),start[1]+random.randint(-50,50))control2=(end[0]+random.randint(-50,50),end[1]+random.randint(-50,50))path=[]fortinnp.linspace(0,1,num_points):# 三阶贝塞尔曲线公式x=(1-t)**3*start[0]+3*(1-t)**2*t*control1[0]+\3*(1-t)*t**2*control2[0]+t**3*end[0]y=(1-t)**3*start[1]+3*(1-t)**2*t*control1[1]+\3*(1-t)*t**2*control2[1]+t**3*end[1]# 加入随机抖动x+=random.randint(-2,2)y+=random.randint(-2,2)path.append((int(x),int(y)))returnpath5.2 人类行为模拟细节
- 点击间隔:300~800ms随机,不要固定间隔
- 鼠标移动速度:先慢后快再慢,模拟人类的加速和减速
- 点击位置:加入±3px的随机偏移,不要每次都点在同一个像素
- 停留时间:点击后停留50~150ms再移动到下一个目标
六、工程化部署:ONNX导出与跨平台调用
为了提升速度和跨平台兼容性,我们把训练好的PyTorch模型导出为ONNX格式,这样就可以在Python、C#、Java等任何语言中调用。
6.1 导出ONNX模型
yoloexportmodel=best.ptformat=onnxopset=12simplify=True导出后会生成best.onnx文件,大小只有30MB左右,非常轻量。
6.2 C#调用示例
对于C#上位机或WinForm程序,可以用ONNX Runtime调用,代码和Python版本逻辑完全一致:
usingMicrosoft.ML.OnnxRuntime;usingMicrosoft.ML.OnnxRuntime.Tensors;usingOpenCvSharp;publicclassYoloCaptchaSolver{privateInferenceSession_session;privatereadonlystring[]_classNames={/* 类别名称 */};publicYoloCaptchaSolver(stringmodelPath){varoptions=newSessionOptions();options.GraphOptimizationLevel=GraphOptimizationLevel.ORT_ENABLE_ALL;_session=newInferenceSession(modelPath,options);}// 实现和Python版本相同的推理、坐标计算、语义匹配逻辑}七、踩坑实录:从60%到100%识别率的血泪史
7.1 坑1:标注不规范导致漏检和误检
一开始我标注的时候框选太松,留了很多空白,导致模型学习到了背景信息,经常把干扰线识别成文字。后来重新标注了所有样本,框选刚好包裹目标,识别率直接从60%升到95%。
7.2 坑2:小目标检测不到
有些验证码的文字很小,YOLOv11n默认的anchor不适合小目标。解决方法是用yolo autoanchor命令自动计算适合数据集的anchor,或者把输入尺寸从640x640调到800x800。
7.3 坑3:同类目标重复点击
当验证码中有多个相同的文字时,模型会返回多个坐标,这时候要根据它们在原图中的位置和提示文字的顺序来判断应该点击哪一个。
7.4 坑4:风控拦截轨迹
一开始我用直线移动鼠标,通过率不到30%。后来加入了贝塞尔曲线轨迹、随机抖动和变速,通过率直接升到99.8%。
八、实测效果
这套系统已经在我的多个爬虫项目中稳定运行了半年,实测数据如下:
| 指标 | 数据 |
|---|---|
| 文字点选识别率 | 100% |
| 图标点选识别率 | 100% |
| 顺序点选识别率 | 99.9% |
| 单张平均耗时 | 120ms |
| 风控通过率 | 99.8% |
| 支持平台 | 淘宝、京东、拼多多、百度、腾讯等主流平台 |
总结
点选验证码破解的核心逻辑其实很简单:用YOLO做目标检测,输出每个目标的位置和类别,然后根据提示文字按顺序点击。只要数据集足够好,模型足够准,再配合人类轨迹模拟,100%识别率完全可以稳定实现。
相比于第三方打码平台,这套方案成本为零、速度更快、更稳定、更安全,完全可以替代打码平台用于爬虫、自动化测试、RPA等场景。
👉 点击我的头像进入主页,关注专栏第一时间收到更新提醒,有问题评论区交流,看到都会回。