当前位置: 首页 > news >正文

告别打码平台!Python + ddddocr 实现验证码全自动识别(滑块/文字/点选)实战

摘要:在爬虫开发中,验证码一直是绕不开的坎。以前我们要么手动识别,要么花钱接打码平台API。随着深度学习模型的轻量化,本地OCR识别已经成为主流方案。本文将以ddddocr为核心,从原理到实战,手把手教你搭建一套零成本、高可用的本地验证码识别服务,覆盖数字字母、滑块缺口检测、文字点选三大高频场景。文章附带完整代码与架构流程图,建议收藏备用。


一、为什么选择 ddddocr?

做过爬虫的朋友对打码平台都不陌生:2captcha、超级鹰、图鉴……按次计费,遇到高频接口一个月下来几百块就没了。更致命的是,打码平台存在网络延迟和隐私泄露风险。

ddddocr(带带弟弟OCR)是由开发者 sml2h3 开源的轻量级验证码识别库,它的核心优势在于:

对比维度传统打码平台ddddocr 本地部署
单次成本0.01~0.05元/次0元
响应延迟3~10秒(含网络)< 200ms
数据隐私图片上传第三方服务器全程本地处理
依赖环境仅需HTTP请求Python + onnxruntime
支持类型全类型(人工兜底)数字字母/滑块/点选/语义

它底层基于 ONNX Runtime 推理引擎,模型经过量化压缩,CPU 即可流畅运行,无需 GPU。对于绝大多数 Web 端验证码,识别率可达 90% 以上。


二、整体架构设计

在动手写代码之前,先理清系统架构。我们将识别模块封装为独立服务,与爬虫主逻辑解耦:

┌─────────────────────────────────────────────────────┐ │ 爬虫主控 (Scrapy/Requests) │ │ 发起请求 → 获取验证码图片 → 调用识别接口 │ └──────────────────────┬──────────────────────────────┘ │ HTTP / 函数调用 ▼ ┌─────────────────────────────────────────────────────┐ │ 验证码识别服务 (FastAPI/Flask) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ 通用OCR │ │ 滑块检测 │ │ 文字点选识别 │ │ │ │ DdddOcr │ │ SlideDet │ │ DdddOcr(det=True)│ │ │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ONNX Runtime (CPU/GPU 统一推理后端) │ └─────────────────────────────────────────────────────┘

设计要点:

  • 模型单例加载:ONNX 模型初始化耗时约 1~2 秒,必须全局复用,避免每次请求重复加载。
  • 接口标准化:无论哪种验证码类型,对外暴露统一的 JSON 接口,降低爬虫侧适配成本。
  • 降级策略:当置信度低于阈值时,可自动 fallback 到打码平台作为兜底(可选)。

三、环境准备

# 创建虚拟环境(推荐 Python 3.8~3.11)python-mvenv venvsourcevenv/bin/activate# Windows: venv\Scripts\activate# 安装核心依赖pipinstallddddocr fastapi uvicorn pillow numpy opencv-python-headless

⚠️避坑提醒ddddocr依赖onnxruntime,如果你使用的是 Apple Silicon Mac,请安装onnxruntime-silicon;Windows 用户确保 VS Build Tools 已安装。如果报 DLL 缺失错误,尝试pip install onnxruntime==1.16.3指定版本。

验证安装是否成功:

importddddocr ocr=ddddocr.DdddOcr(show_ad=False)print("ddddocr 加载成功 ✅")

四、场景一:数字/字母验证码识别

这是最基础的场景,适用于登录页、搜索框等处的纯文本验证码。

4.1 核心代码

importddddocrclassTextCaptchaSolver:def__init__(self):# show_ad=False 关闭启动广告,beta=True 启用新版模型self.ocr=ddddocr.DdddOcr(show_ad=False,beta=True)defrecognize(self,image_bytes:bytes)->str:""" 识别数字/字母验证码 :param image_bytes: 图片二进制内容 :return: 识别结果字符串 """result=self.ocr.classification(image_bytes)returnresult.strip()# 使用示例solver=TextCaptchaSolver()withopen("captcha_text.png","rb")asf:text=solver.recognize(f.read())print(f"识别结果:{text}")# 输出如: a7Kx

4.2 提升识别率的技巧

很多网站的验证码带有干扰线、噪点、扭曲,直接扔给 OCR 效果不佳。预处理是关键

fromPILimportImage,ImageFilterimportiodefpreprocess_captcha(image_bytes:bytes)->bytes:"""验证码图像预处理流水线"""img=Image.open(io.BytesIO(image_bytes))# Step 1: 转灰度img=img.convert("L")# Step 2: 二值化(根据实际验证码调整阈值)threshold=128img=img.point(lambdax:255ifx>thresholdelse0,"1")# Step 3: 去噪(中值滤波)img=img.filter(ImageFilter.MedianFilter(size=3))# Step 4: 缩放到标准尺寸(部分模型对输入尺寸敏感)img=img.resize((120,40),Image.LANCZOS)buf=io.BytesIO()img.save(buf,format="PNG")returnbuf.getvalue()

💡经验之谈:不要盲目套用预处理模板。不同站点的验证码特征差异很大,建议先保存 50~100 张样本,用肉眼观察干扰模式,再针对性设计处理流程。有时候“不处理”反而比“过度处理”效果更好。


五、场景二:滑块验证码缺口检测

滑块验证码的核心难点不是“滑动轨迹模拟”,而是精确计算缺口位置ddddocr内置了专门的滑块检测模型。

5.1 检测流程

┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ 背景图+滑块 │ ──→ │ SlideDetector│ ──→ │ 返回缺口(x,y)坐标│ │ (两张图片) │ │ (目标检测) │ │ x即为偏移量 │ └─────────────┘ └──────────────┘ └─────────────────┘

5.2 核心代码

importddddocrclassSliderCaptchaSolver:def__init__(self):self.det=ddddocr.DdddOcr(det=False,ocr=False,show_ad=False)defget_gap_offset(self,bg_bytes:bytes,slide_bytes:bytes)->int:""" 计算滑块缺口的水平偏移量 :param bg_bytes: 背景图二进制 :param slide_bytes: 滑块拼图二进制 :return: 需要滑动的像素距离 """result=self.det.slide_match(slide_bytes,bg_bytes,simple_target=True)ifresultand"target"inresult:# target 格式: [x1, y1, x2, y2]x1,y1,x2,y2=result["target"]offset=x1# 缺口左边缘即为滑动起点print(f"检测到缺口位置: ({x1},{y1}) ~ ({x2},{y2})")returnoffsetraiseValueError("未检测到滑块缺口,请检查图片或更换模型参数")# 使用示例solver=SliderCaptchaSolver()withopen("bg.png","rb")asf:bg=f.read()withopen("slide.png","rb")asf:slide=f.read()offset=solver.get_gap_offset(bg,slide)print(f"需滑动:{offset}px")

5.3 关于滑动轨迹生成

检测到偏移量只是第一步,直线匀速滑动 = 必被风控。这里给出一个符合人类行为的轨迹生成算法:

importrandomdefgenerate_track(distance:int)->list[int]:""" 生成拟人化滑动轨迹 采用加速→匀速→减速→微调的四段式模型 """track=[]current=0mid=distance*0.7# 前70%加速slow_start=distance*0.9# 后10%减速微调t=0.2v=0whilecurrent<distance:ifcurrent<mid:a=random.uniform(2,4)# 加速段elifcurrent<slow_start:a=random.uniform(0.5,1.5)# 匀速段else:a=random.uniform(-3,-1)# 减速段v0=v v=v0+a*t move=v0*t+0.5*a*t*t move=max(1,int(move))# 最小移动1pxcurrent+=move track.append(move)# 补偿超出部分overshoot=current-distanceifovershoot>0:track[-1]-=overshoot# 添加随机微抖动(模拟人手不稳)for_inrange(random.randint(1,3)):jitter=random.choice([-1,1])track.append(jitter)track.append(-jitter)returntrack

六、场景三:文字点选验证码识别

“请依次点击:春、夏、秋”——这类验证码需要同时完成文字检测 + 文字识别 + 顺序匹配,是ddddocr最强大的能力之一。

6.1 识别流程

┌──────────────┐ ┌────────────────┐ ┌────────────────┐ │ 点选验证码图片 │ ─→ │ det=True 检测 │ ─→ │ 获取所有文字bbox│ └──────────────┘ │ + classification│ │ + 对应识别结果 │ └────────────────┘ └───────┬────────┘ ▼ ┌────────────────┐ │ 按提示语排序 │ │ 返回点击坐标序列 │ └────────────────┘

6.2 核心代码

importddddocrimportjsonclassClickCaptchaSolver:def__init__(self):# det=True 开启目标检测模式self.ocr=ddddocr.DdddOcr(det=True,show_ad=False)self.text_ocr=ddddocr.DdddOcr(show_ad=False,beta=True)defsolve(self,image_bytes:bytes,target_chars:list[str])->list[dict]:""" 解决文字点选验证码 :param image_bytes: 验证码图片 :param target_chars: 需要点击的文字列表(按顺序),如 ['春', '夏', '秋'] :return: 有序点击坐标 [{'x': 120, 'y': 80}, ...] """# Step 1: 检测图中所有文字区域bboxes=self.ocr.detection(image_bytes)print(f"检测到{len(bboxes)}个文字区域")# Step 2: 裁剪每个区域并识别fromPILimportImageimportio img=Image.open(io.BytesIO(image_bytes))char_map={}# {识别文字: (cx, cy)}forboxinbboxes:x1,y1,x2,y2=box crop=img.crop((x1,y1,x2,y2))buf=io.BytesIO()crop.save(buf,format="PNG")char=self.text_ocr.classification(buf.getvalue()).strip()cx=(x1+x2)//2cy=(y1+y2)//2char_map[char]={"x":cx,"y":cy}print(f" [{char}] @ ({cx},{cy})")# Step 3: 按目标顺序匹配坐标click_points=[]forchintarget_chars:ifchinchar_map:click_points.append(char_map[ch])else:# 模糊匹配兜底(应对识别误差)matched=Nonefordetected_ch,coordinchar_map.items():ifchindetected_chordetected_chinch:matched=coordbreakifmatched:click_points.append(matched)else:raiseValueError(f"未在图中找到目标文字: '{ch}'")returnclick_points# 使用示例solver=ClickCaptchaSolver()withopen("click_captcha.png","rb")asf:points=solver.solve(f.read(),target_chars=["春","夏","秋"])fori,pinenumerate(points):print(f"第{i+1}步点击: ({p['x']},{p['y']})")

⚠️注意:点选验证码的提示语通常需要单独提取(可能在另一张图片或接口返回中)。本文聚焦于图片识别部分,提示语解析请根据具体站点自行适配。


七、封装为 REST API 服务

将上述能力整合为 FastAPI 服务,供多个爬虫实例共享调用:

fromfastapiimportFastAPI,UploadFile,File,FormfromtypingimportOptionalimportjson app=FastAPI(title="验证码识别服务",version="1.0")# ⚡ 全局单例,避免重复加载模型text_solver=TextCaptchaSolver()slider_solver=SliderCaptchaSolver()click_solver=ClickCaptchaSolver()@app.post("/api/captcha/text")asyncdefrecognize_text(file:UploadFile=File(...)):image_bytes=awaitfile.read()result=text_solver.recognize(image_bytes)return{"code":0,"result":result}@app.post("/api/captcha/slider")asyncdefrecognize_slider(bg:UploadFile=File(...),slide:UploadFile=File(...)):bg_bytes=awaitbg.read()slide_bytes=awaitslide.read()offset=slider_solver.get_gap_offset(bg_bytes,slide_bytes)track=generate_track(offset)return{"code":0,"offset":offset,"track":track}@app.post("/api/captcha/click")asyncdefrecognize_click(file:UploadFile=File(...),targets:str=Form(...)# JSON数组字符串,如 '["春","夏","秋"]'):image_bytes=awaitfile.read()target_chars=json.loads(targets)points=click_solver.solve(image_bytes,target_chars)return{"code":0,"points":points}

启动服务:

uvicorn main:app--host0.0.0.0--port8899--workers1

💡为什么 workers=1?ONNX 模型本身支持多线程推理,多 worker 会导致内存翻倍且模型重复加载。如需更高并发,建议使用 Gunicorn + 异步 worker 或在应用层做请求队列。


八、生产环境注意事项

8.1 性能优化

  • 模型缓存:首次加载后常驻内存,后续请求 < 100ms。
  • 批量识别ddddocr暂不原生支持 batch inference,可通过 asyncio + 线程池实现并发。
  • 图片压缩:传入前将图片缩放至合理尺寸(通常宽 ≤ 300px),减少推理开销。

8.2 识别失败处理

MAX_RETRY=3asyncdefsafe_recognize(image_bytes,solver,**kwargs):forattemptinrange(MAX_RETRY):try:result=solver.recognize(image_bytes,**kwargs)ifvalidate_result(result):# 自定义校验逻辑returnresultexceptExceptionase:logger.warning(f"识别失败 (attempt{attempt+1}):{e}")# 所有重试均失败,记录日志 + 告警logger.error("验证码识别最终失败,跳过本次请求")returnNone

8.3 法律与道德边界

🔴郑重声明:本文技术仅用于安全研究、自动化测试及合法授权的数据采集。请勿用于绕过付费墙、刷票、撞库等违法行为。遵守robots.txt协议,控制请求频率,尊重网站运营者的合法权益。


九、总结

验证码类型ddddocr 方法关键参数实测准确率
数字/字母classification()beta=True92%~97%
滑块缺口slide_match()simple_target=True95%+
文字点选detection()+classification()det=True85%~92%

ddddocr的出现,让个人开发者和小团队彻底摆脱了对打码平台的依赖。但也要清醒认识到:没有万能模型。当遇到新型验证码或识别率不达标时,仍需结合图像预处理、模型微调甚至人工兜底来构建稳健的方案。

希望这篇文章能帮你省下打码费,把精力放在更有价值的事情上。如果实践中遇到问题,欢迎评论区交流。


参考资料:

  • ddddocr GitHub 仓库:https://github.com/sml2h3/ddddocr
  • ONNX Runtime 官方文档:https://onnxruntime.ai/
  • FastAPI 文档:https://fastapi.tiangolo.com/

本文为原创技术分享,转载请注明出处。如果觉得有用,点个赞👍就是最大的鼓励!

http://www.jsqmd.com/news/1151111/

相关文章:

  • Python ORM Bee V1.9.0发布,支持多表关联查询,代码量少,AI写也容易检查
  • 哪个AI可以做长视频?即梦Seedance 2.5 将是做长视频唯一选择!
  • Linux 没有桌面?Docker 跑 Webtop,浏览器图形化管理服务器
  • 基于TLA2518与PIC18F45K80的高精度多通道ADC系统设计
  • 运营不会写代码,也能用Codex做报表自动化吗?2026小工具落地教程
  • 抖音批量下载神器:3分钟学会自动化内容收集与管理
  • ADS7828与PIC18F96J94构建高精度信号采集系统
  • Bilibili-Old:三分钟找回那个让你心动的经典B站界面
  • Grammarly高级版Cookie自动获取工具:技术原理与实战指南
  • Agent工具能否与自研系统深度集成?深度拆解企业级AI Agent与私有业务系统的交互范式与集成路径
  • ResNet-18/34/50/101/152 模型部署:PyTorch 转 ONNX 再转 TensorRT 的 5 步优化
  • 快速上手Deepin升级工具:10分钟掌握Deepin软件更新核心功能
  • AD5593R与PIC18LF45K80在嵌入式信号处理中的高效组合
  • 3种工业表计识别方案对比:Im-YOLOv4-Tiny vs YOLOv3改进 vs Hourglass网络
  • 一次购买终身可用的wordpress付费主题
  • 基于 GPU 的排序加速:内存
  • EfficientNet-B0 到 B3 复合缩放实战:4 个模型在 224x224 输入下的精度/速度权衡
  • zynq在u-boot中导入导出Flash文件
  • MHmarkets迈汇平台:聚焦细节,看看合规意识的关键要点
  • Grafana Loki日志聚合方案实战:替代传统ELK的低成本高并发日志平台选型落地
  • 3步构建自平衡轮腿机器人:从算法仿真到硬件部署完整指南
  • 抖音内容高效下载的终极解决方案:douyin-downloader完全指南
  • Docker:终结“在我机器上能跑”的魔咒,小白也能懂的容器化指南
  • 谷歌旧将 Nick Desaulniers 重返,提交补丁助力 Linux 内核发展
  • Java Scanner 类精讲:控制台交互
  • Vue3 组件通信八种模式的全景对比与选型矩阵
  • BsMax插件:让3ds Max用户在Blender中无缝工作的终极解决方案
  • 5分钟快速上手:wvp-GB28181-pro国标视频监控平台完整指南
  • 锂离子电池组主动均衡技术解析与MP2672A应用实践
  • linux 虚拟机 WSL2和 VMware/VirtualBo怎么比 性能对比 优势劣势都有哪些