摘要在爬虫开发中验证码一直是绕不开的坎。以前我们要么手动识别要么花钱接打码平台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(detTrue)│ │ │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ONNX Runtime (CPU/GPU 统一推理后端) │ └─────────────────────────────────────────────────────┘设计要点模型单例加载ONNX 模型初始化耗时约 1~2 秒必须全局复用避免每次请求重复加载。接口标准化无论哪种验证码类型对外暴露统一的 JSON 接口降低爬虫侧适配成本。降级策略当置信度低于阈值时可自动 fallback 到打码平台作为兜底可选。三、环境准备# 创建虚拟环境推荐 Python 3.8~3.11python-mvenv venvsourcevenv/bin/activate# Windows: venv\Scripts\activate# 安装核心依赖pipinstallddddocr fastapi uvicorn pillow numpy opencv-python-headless⚠️避坑提醒ddddocr依赖onnxruntime如果你使用的是 Apple Silicon Mac请安装onnxruntime-siliconWindows 用户确保 VS Build Tools 已安装。如果报 DLL 缺失错误尝试pip install onnxruntime1.16.3指定版本。验证安装是否成功importddddocr ocrddddocr.DdddOcr(show_adFalse)print(ddddocr 加载成功 ✅)四、场景一数字/字母验证码识别这是最基础的场景适用于登录页、搜索框等处的纯文本验证码。4.1 核心代码importddddocrclassTextCaptchaSolver:def__init__(self):# show_adFalse 关闭启动广告betaTrue 启用新版模型self.ocrddddocr.DdddOcr(show_adFalse,betaTrue)defrecognize(self,image_bytes:bytes)-str: 识别数字/字母验证码 :param image_bytes: 图片二进制内容 :return: 识别结果字符串 resultself.ocr.classification(image_bytes)returnresult.strip()# 使用示例solverTextCaptchaSolver()withopen(captcha_text.png,rb)asf:textsolver.recognize(f.read())print(f识别结果:{text})# 输出如: a7Kx4.2 提升识别率的技巧很多网站的验证码带有干扰线、噪点、扭曲直接扔给 OCR 效果不佳。预处理是关键fromPILimportImage,ImageFilterimportiodefpreprocess_captcha(image_bytes:bytes)-bytes:验证码图像预处理流水线imgImage.open(io.BytesIO(image_bytes))# Step 1: 转灰度imgimg.convert(L)# Step 2: 二值化根据实际验证码调整阈值threshold128imgimg.point(lambdax:255ifxthresholdelse0,1)# Step 3: 去噪中值滤波imgimg.filter(ImageFilter.MedianFilter(size3))# Step 4: 缩放到标准尺寸部分模型对输入尺寸敏感imgimg.resize((120,40),Image.LANCZOS)bufio.BytesIO()img.save(buf,formatPNG)returnbuf.getvalue()经验之谈不要盲目套用预处理模板。不同站点的验证码特征差异很大建议先保存 50~100 张样本用肉眼观察干扰模式再针对性设计处理流程。有时候“不处理”反而比“过度处理”效果更好。五、场景二滑块验证码缺口检测滑块验证码的核心难点不是“滑动轨迹模拟”而是精确计算缺口位置。ddddocr内置了专门的滑块检测模型。5.1 检测流程┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ 背景图滑块 │ ──→ │ SlideDetector│ ──→ │ 返回缺口(x,y)坐标│ │ (两张图片) │ │ (目标检测) │ │ x即为偏移量 │ └─────────────┘ └──────────────┘ └─────────────────┘5.2 核心代码importddddocrclassSliderCaptchaSolver:def__init__(self):self.detddddocr.DdddOcr(detFalse,ocrFalse,show_adFalse)defget_gap_offset(self,bg_bytes:bytes,slide_bytes:bytes)-int: 计算滑块缺口的水平偏移量 :param bg_bytes: 背景图二进制 :param slide_bytes: 滑块拼图二进制 :return: 需要滑动的像素距离 resultself.det.slide_match(slide_bytes,bg_bytes,simple_targetTrue)ifresultandtargetinresult:# target 格式: [x1, y1, x2, y2]x1,y1,x2,y2result[target]offsetx1# 缺口左边缘即为滑动起点print(f检测到缺口位置: ({x1},{y1}) ~ ({x2},{y2}))returnoffsetraiseValueError(未检测到滑块缺口请检查图片或更换模型参数)# 使用示例solverSliderCaptchaSolver()withopen(bg.png,rb)asf:bgf.read()withopen(slide.png,rb)asf:slidef.read()offsetsolver.get_gap_offset(bg,slide)print(f需滑动:{offset}px)5.3 关于滑动轨迹生成检测到偏移量只是第一步直线匀速滑动 必被风控。这里给出一个符合人类行为的轨迹生成算法importrandomdefgenerate_track(distance:int)-list[int]: 生成拟人化滑动轨迹 采用加速→匀速→减速→微调的四段式模型 track[]current0middistance*0.7# 前70%加速slow_startdistance*0.9# 后10%减速微调t0.2v0whilecurrentdistance:ifcurrentmid:arandom.uniform(2,4)# 加速段elifcurrentslow_start:arandom.uniform(0.5,1.5)# 匀速段else:arandom.uniform(-3,-1)# 减速段v0v vv0a*t movev0*t0.5*a*t*t movemax(1,int(move))# 最小移动1pxcurrentmove track.append(move)# 补偿超出部分overshootcurrent-distanceifovershoot0:track[-1]-overshoot# 添加随机微抖动模拟人手不稳for_inrange(random.randint(1,3)):jitterrandom.choice([-1,1])track.append(jitter)track.append(-jitter)returntrack六、场景三文字点选验证码识别“请依次点击春、夏、秋”——这类验证码需要同时完成文字检测 文字识别 顺序匹配是ddddocr最强大的能力之一。6.1 识别流程┌──────────────┐ ┌────────────────┐ ┌────────────────┐ │ 点选验证码图片 │ ─→ │ detTrue 检测 │ ─→ │ 获取所有文字bbox│ └──────────────┘ │ classification│ │ 对应识别结果 │ └────────────────┘ └───────┬────────┘ ▼ ┌────────────────┐ │ 按提示语排序 │ │ 返回点击坐标序列 │ └────────────────┘6.2 核心代码importddddocrimportjsonclassClickCaptchaSolver:def__init__(self):# detTrue 开启目标检测模式self.ocrddddocr.DdddOcr(detTrue,show_adFalse)self.text_ocrddddocr.DdddOcr(show_adFalse,betaTrue)defsolve(self,image_bytes:bytes,target_chars:list[str])-list[dict]: 解决文字点选验证码 :param image_bytes: 验证码图片 :param target_chars: 需要点击的文字列表按顺序如 [春, 夏, 秋] :return: 有序点击坐标 [{x: 120, y: 80}, ...] # Step 1: 检测图中所有文字区域bboxesself.ocr.detection(image_bytes)print(f检测到{len(bboxes)}个文字区域)# Step 2: 裁剪每个区域并识别fromPILimportImageimportio imgImage.open(io.BytesIO(image_bytes))char_map{}# {识别文字: (cx, cy)}forboxinbboxes:x1,y1,x2,y2box cropimg.crop((x1,y1,x2,y2))bufio.BytesIO()crop.save(buf,formatPNG)charself.text_ocr.classification(buf.getvalue()).strip()cx(x1x2)//2cy(y1y2)//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:# 模糊匹配兜底应对识别误差matchedNonefordetected_ch,coordinchar_map.items():ifchindetected_chordetected_chinch:matchedcoordbreakifmatched:click_points.append(matched)else:raiseValueError(f未在图中找到目标文字: {ch})returnclick_points# 使用示例solverClickCaptchaSolver()withopen(click_captcha.png,rb)asf:pointssolver.solve(f.read(),target_chars[春,夏,秋])fori,pinenumerate(points):print(f第{i1}步点击: ({p[x]},{p[y]}))⚠️注意点选验证码的提示语通常需要单独提取可能在另一张图片或接口返回中。本文聚焦于图片识别部分提示语解析请根据具体站点自行适配。七、封装为 REST API 服务将上述能力整合为 FastAPI 服务供多个爬虫实例共享调用fromfastapiimportFastAPI,UploadFile,File,FormfromtypingimportOptionalimportjson appFastAPI(title验证码识别服务,version1.0)# ⚡ 全局单例避免重复加载模型text_solverTextCaptchaSolver()slider_solverSliderCaptchaSolver()click_solverClickCaptchaSolver()app.post(/api/captcha/text)asyncdefrecognize_text(file:UploadFileFile(...)):image_bytesawaitfile.read()resulttext_solver.recognize(image_bytes)return{code:0,result:result}app.post(/api/captcha/slider)asyncdefrecognize_slider(bg:UploadFileFile(...),slide:UploadFileFile(...)):bg_bytesawaitbg.read()slide_bytesawaitslide.read()offsetslider_solver.get_gap_offset(bg_bytes,slide_bytes)trackgenerate_track(offset)return{code:0,offset:offset,track:track}app.post(/api/captcha/click)asyncdefrecognize_click(file:UploadFileFile(...),targets:strForm(...)# JSON数组字符串如 [春,夏,秋]):image_bytesawaitfile.read()target_charsjson.loads(targets)pointsclick_solver.solve(image_bytes,target_chars)return{code:0,points:points}启动服务uvicorn main:app--host0.0.0.0--port8899--workers1为什么 workers1ONNX 模型本身支持多线程推理多 worker 会导致内存翻倍且模型重复加载。如需更高并发建议使用 Gunicorn 异步 worker 或在应用层做请求队列。八、生产环境注意事项8.1 性能优化模型缓存首次加载后常驻内存后续请求 100ms。批量识别ddddocr暂不原生支持 batch inference可通过 asyncio 线程池实现并发。图片压缩传入前将图片缩放至合理尺寸通常宽 ≤ 300px减少推理开销。8.2 识别失败处理MAX_RETRY3asyncdefsafe_recognize(image_bytes,solver,**kwargs):forattemptinrange(MAX_RETRY):try:resultsolver.recognize(image_bytes,**kwargs)ifvalidate_result(result):# 自定义校验逻辑returnresultexceptExceptionase:logger.warning(f识别失败 (attempt{attempt1}):{e})# 所有重试均失败记录日志 告警logger.error(验证码识别最终失败跳过本次请求)returnNone8.3 法律与道德边界郑重声明本文技术仅用于安全研究、自动化测试及合法授权的数据采集。请勿用于绕过付费墙、刷票、撞库等违法行为。遵守robots.txt协议控制请求频率尊重网站运营者的合法权益。九、总结验证码类型ddddocr 方法关键参数实测准确率数字/字母classification()betaTrue92%~97%滑块缺口slide_match()simple_targetTrue95%文字点选detection()classification()detTrue85%~92%ddddocr的出现让个人开发者和小团队彻底摆脱了对打码平台的依赖。但也要清醒认识到没有万能模型。当遇到新型验证码或识别率不达标时仍需结合图像预处理、模型微调甚至人工兜底来构建稳健的方案。希望这篇文章能帮你省下打码费把精力放在更有价值的事情上。如果实践中遇到问题欢迎评论区交流。参考资料ddddocr GitHub 仓库https://github.com/sml2h3/ddddocrONNX Runtime 官方文档https://onnxruntime.ai/FastAPI 文档https://fastapi.tiangolo.com/本文为原创技术分享转载请注明出处。如果觉得有用点个赞就是最大的鼓励
告别打码平台!Python + ddddocr 实现验证码全自动识别(滑块/文字/点选)实战
发布时间:2026/7/9 0:57:41
摘要在爬虫开发中验证码一直是绕不开的坎。以前我们要么手动识别要么花钱接打码平台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(detTrue)│ │ │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ONNX Runtime (CPU/GPU 统一推理后端) │ └─────────────────────────────────────────────────────┘设计要点模型单例加载ONNX 模型初始化耗时约 1~2 秒必须全局复用避免每次请求重复加载。接口标准化无论哪种验证码类型对外暴露统一的 JSON 接口降低爬虫侧适配成本。降级策略当置信度低于阈值时可自动 fallback 到打码平台作为兜底可选。三、环境准备# 创建虚拟环境推荐 Python 3.8~3.11python-mvenv venvsourcevenv/bin/activate# Windows: venv\Scripts\activate# 安装核心依赖pipinstallddddocr fastapi uvicorn pillow numpy opencv-python-headless⚠️避坑提醒ddddocr依赖onnxruntime如果你使用的是 Apple Silicon Mac请安装onnxruntime-siliconWindows 用户确保 VS Build Tools 已安装。如果报 DLL 缺失错误尝试pip install onnxruntime1.16.3指定版本。验证安装是否成功importddddocr ocrddddocr.DdddOcr(show_adFalse)print(ddddocr 加载成功 ✅)四、场景一数字/字母验证码识别这是最基础的场景适用于登录页、搜索框等处的纯文本验证码。4.1 核心代码importddddocrclassTextCaptchaSolver:def__init__(self):# show_adFalse 关闭启动广告betaTrue 启用新版模型self.ocrddddocr.DdddOcr(show_adFalse,betaTrue)defrecognize(self,image_bytes:bytes)-str: 识别数字/字母验证码 :param image_bytes: 图片二进制内容 :return: 识别结果字符串 resultself.ocr.classification(image_bytes)returnresult.strip()# 使用示例solverTextCaptchaSolver()withopen(captcha_text.png,rb)asf:textsolver.recognize(f.read())print(f识别结果:{text})# 输出如: a7Kx4.2 提升识别率的技巧很多网站的验证码带有干扰线、噪点、扭曲直接扔给 OCR 效果不佳。预处理是关键fromPILimportImage,ImageFilterimportiodefpreprocess_captcha(image_bytes:bytes)-bytes:验证码图像预处理流水线imgImage.open(io.BytesIO(image_bytes))# Step 1: 转灰度imgimg.convert(L)# Step 2: 二值化根据实际验证码调整阈值threshold128imgimg.point(lambdax:255ifxthresholdelse0,1)# Step 3: 去噪中值滤波imgimg.filter(ImageFilter.MedianFilter(size3))# Step 4: 缩放到标准尺寸部分模型对输入尺寸敏感imgimg.resize((120,40),Image.LANCZOS)bufio.BytesIO()img.save(buf,formatPNG)returnbuf.getvalue()经验之谈不要盲目套用预处理模板。不同站点的验证码特征差异很大建议先保存 50~100 张样本用肉眼观察干扰模式再针对性设计处理流程。有时候“不处理”反而比“过度处理”效果更好。五、场景二滑块验证码缺口检测滑块验证码的核心难点不是“滑动轨迹模拟”而是精确计算缺口位置。ddddocr内置了专门的滑块检测模型。5.1 检测流程┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ 背景图滑块 │ ──→ │ SlideDetector│ ──→ │ 返回缺口(x,y)坐标│ │ (两张图片) │ │ (目标检测) │ │ x即为偏移量 │ └─────────────┘ └──────────────┘ └─────────────────┘5.2 核心代码importddddocrclassSliderCaptchaSolver:def__init__(self):self.detddddocr.DdddOcr(detFalse,ocrFalse,show_adFalse)defget_gap_offset(self,bg_bytes:bytes,slide_bytes:bytes)-int: 计算滑块缺口的水平偏移量 :param bg_bytes: 背景图二进制 :param slide_bytes: 滑块拼图二进制 :return: 需要滑动的像素距离 resultself.det.slide_match(slide_bytes,bg_bytes,simple_targetTrue)ifresultandtargetinresult:# target 格式: [x1, y1, x2, y2]x1,y1,x2,y2result[target]offsetx1# 缺口左边缘即为滑动起点print(f检测到缺口位置: ({x1},{y1}) ~ ({x2},{y2}))returnoffsetraiseValueError(未检测到滑块缺口请检查图片或更换模型参数)# 使用示例solverSliderCaptchaSolver()withopen(bg.png,rb)asf:bgf.read()withopen(slide.png,rb)asf:slidef.read()offsetsolver.get_gap_offset(bg,slide)print(f需滑动:{offset}px)5.3 关于滑动轨迹生成检测到偏移量只是第一步直线匀速滑动 必被风控。这里给出一个符合人类行为的轨迹生成算法importrandomdefgenerate_track(distance:int)-list[int]: 生成拟人化滑动轨迹 采用加速→匀速→减速→微调的四段式模型 track[]current0middistance*0.7# 前70%加速slow_startdistance*0.9# 后10%减速微调t0.2v0whilecurrentdistance:ifcurrentmid:arandom.uniform(2,4)# 加速段elifcurrentslow_start:arandom.uniform(0.5,1.5)# 匀速段else:arandom.uniform(-3,-1)# 减速段v0v vv0a*t movev0*t0.5*a*t*t movemax(1,int(move))# 最小移动1pxcurrentmove track.append(move)# 补偿超出部分overshootcurrent-distanceifovershoot0:track[-1]-overshoot# 添加随机微抖动模拟人手不稳for_inrange(random.randint(1,3)):jitterrandom.choice([-1,1])track.append(jitter)track.append(-jitter)returntrack六、场景三文字点选验证码识别“请依次点击春、夏、秋”——这类验证码需要同时完成文字检测 文字识别 顺序匹配是ddddocr最强大的能力之一。6.1 识别流程┌──────────────┐ ┌────────────────┐ ┌────────────────┐ │ 点选验证码图片 │ ─→ │ detTrue 检测 │ ─→ │ 获取所有文字bbox│ └──────────────┘ │ classification│ │ 对应识别结果 │ └────────────────┘ └───────┬────────┘ ▼ ┌────────────────┐ │ 按提示语排序 │ │ 返回点击坐标序列 │ └────────────────┘6.2 核心代码importddddocrimportjsonclassClickCaptchaSolver:def__init__(self):# detTrue 开启目标检测模式self.ocrddddocr.DdddOcr(detTrue,show_adFalse)self.text_ocrddddocr.DdddOcr(show_adFalse,betaTrue)defsolve(self,image_bytes:bytes,target_chars:list[str])-list[dict]: 解决文字点选验证码 :param image_bytes: 验证码图片 :param target_chars: 需要点击的文字列表按顺序如 [春, 夏, 秋] :return: 有序点击坐标 [{x: 120, y: 80}, ...] # Step 1: 检测图中所有文字区域bboxesself.ocr.detection(image_bytes)print(f检测到{len(bboxes)}个文字区域)# Step 2: 裁剪每个区域并识别fromPILimportImageimportio imgImage.open(io.BytesIO(image_bytes))char_map{}# {识别文字: (cx, cy)}forboxinbboxes:x1,y1,x2,y2box cropimg.crop((x1,y1,x2,y2))bufio.BytesIO()crop.save(buf,formatPNG)charself.text_ocr.classification(buf.getvalue()).strip()cx(x1x2)//2cy(y1y2)//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:# 模糊匹配兜底应对识别误差matchedNonefordetected_ch,coordinchar_map.items():ifchindetected_chordetected_chinch:matchedcoordbreakifmatched:click_points.append(matched)else:raiseValueError(f未在图中找到目标文字: {ch})returnclick_points# 使用示例solverClickCaptchaSolver()withopen(click_captcha.png,rb)asf:pointssolver.solve(f.read(),target_chars[春,夏,秋])fori,pinenumerate(points):print(f第{i1}步点击: ({p[x]},{p[y]}))⚠️注意点选验证码的提示语通常需要单独提取可能在另一张图片或接口返回中。本文聚焦于图片识别部分提示语解析请根据具体站点自行适配。七、封装为 REST API 服务将上述能力整合为 FastAPI 服务供多个爬虫实例共享调用fromfastapiimportFastAPI,UploadFile,File,FormfromtypingimportOptionalimportjson appFastAPI(title验证码识别服务,version1.0)# ⚡ 全局单例避免重复加载模型text_solverTextCaptchaSolver()slider_solverSliderCaptchaSolver()click_solverClickCaptchaSolver()app.post(/api/captcha/text)asyncdefrecognize_text(file:UploadFileFile(...)):image_bytesawaitfile.read()resulttext_solver.recognize(image_bytes)return{code:0,result:result}app.post(/api/captcha/slider)asyncdefrecognize_slider(bg:UploadFileFile(...),slide:UploadFileFile(...)):bg_bytesawaitbg.read()slide_bytesawaitslide.read()offsetslider_solver.get_gap_offset(bg_bytes,slide_bytes)trackgenerate_track(offset)return{code:0,offset:offset,track:track}app.post(/api/captcha/click)asyncdefrecognize_click(file:UploadFileFile(...),targets:strForm(...)# JSON数组字符串如 [春,夏,秋]):image_bytesawaitfile.read()target_charsjson.loads(targets)pointsclick_solver.solve(image_bytes,target_chars)return{code:0,points:points}启动服务uvicorn main:app--host0.0.0.0--port8899--workers1为什么 workers1ONNX 模型本身支持多线程推理多 worker 会导致内存翻倍且模型重复加载。如需更高并发建议使用 Gunicorn 异步 worker 或在应用层做请求队列。八、生产环境注意事项8.1 性能优化模型缓存首次加载后常驻内存后续请求 100ms。批量识别ddddocr暂不原生支持 batch inference可通过 asyncio 线程池实现并发。图片压缩传入前将图片缩放至合理尺寸通常宽 ≤ 300px减少推理开销。8.2 识别失败处理MAX_RETRY3asyncdefsafe_recognize(image_bytes,solver,**kwargs):forattemptinrange(MAX_RETRY):try:resultsolver.recognize(image_bytes,**kwargs)ifvalidate_result(result):# 自定义校验逻辑returnresultexceptExceptionase:logger.warning(f识别失败 (attempt{attempt1}):{e})# 所有重试均失败记录日志 告警logger.error(验证码识别最终失败跳过本次请求)returnNone8.3 法律与道德边界郑重声明本文技术仅用于安全研究、自动化测试及合法授权的数据采集。请勿用于绕过付费墙、刷票、撞库等违法行为。遵守robots.txt协议控制请求频率尊重网站运营者的合法权益。九、总结验证码类型ddddocr 方法关键参数实测准确率数字/字母classification()betaTrue92%~97%滑块缺口slide_match()simple_targetTrue95%文字点选detection()classification()detTrue85%~92%ddddocr的出现让个人开发者和小团队彻底摆脱了对打码平台的依赖。但也要清醒认识到没有万能模型。当遇到新型验证码或识别率不达标时仍需结合图像预处理、模型微调甚至人工兜底来构建稳健的方案。希望这篇文章能帮你省下打码费把精力放在更有价值的事情上。如果实践中遇到问题欢迎评论区交流。参考资料ddddocr GitHub 仓库https://github.com/sml2h3/ddddocrONNX Runtime 官方文档https://onnxruntime.ai/FastAPI 文档https://fastapi.tiangolo.com/本文为原创技术分享转载请注明出处。如果觉得有用点个赞就是最大的鼓励