直接用 asyncio 调 ddddocr.classification() 会卡死,因其底层为 CPU 密集型同步操作,阻塞事件循环;应全局复用 DdddOcr 实例并用 asyncio.to_thread() 托管至线程池执行。

为什么直接用 asyncio 调 ddddocr.classification() 会卡死
因为 ddddocr 底层依赖 OpenCV 和 NumPy 的同步图像处理,所有操作都是 CPU 密集型且阻塞的,直接丢进 asyncio 事件循环会导致整个协程挂起,后续请求全部排队等待——这不是异步,是“假装异步”。
真正可行的做法是把 OCR 调用扔进线程池,让 asyncio.to_thread() 托管执行:
- 不要用
loop.run_in_executor()手动管理线程池,asyncio.to_thread()更简洁、错误传播更自然 - 避免在每次请求里重复初始化
DdddOcr()实例,它内部加载模型较重,应全局单例复用 - 如果 OCR 服务部署在远端(如 FastAPI 接口),那就直接发异步 HTTP 请求,不碰本地模型
如何把本地 ddddocr 接入 aiohttp 爬虫流水线
核心是分离「下载验证码图」和「识别」两个阶段,避免阻塞网络 I/O。常见错误是拿到 response.content 后立刻调 ocr.classification(),这会让整个 session 卡住。
正确做法示例:
立即学习“Python免费学习笔记(深入)”;
import asyncio
import aiohttp
from ddddocr import DdddOcr
<p>ocr = DdddOcr() # 全局复用</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill8393" title="Forge — Python Developer"><img
src="https://img.php.cn/upload/skill/000/000/081/179203503734063.jpg" alt="Forge — Python Developer" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill8393" title="Forge — Python Developer">Forge — Python Developer</a>
<p>Python开发助手 — 自动化脚本、API封装、命令行工具、数据管道、网页爬虫。经过测试的代码,包含错误处理和文档。当你需要...</p>
</div>
<a href="/xiazai/skill8393" title="Forge — Python Developer" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p>async def recognize_captcha(session: aiohttp.ClientSession, img_url: str) -> str:
async with session.get(img_url) as resp:
img_bytes = await resp.read()</p><h1>这一步必须进线程池</h1><pre class='brush:python;toolbar:false;'>return await asyncio.to_thread(ocr.classification, img_bytes)async def main(): async with aiohttp.ClientSession() as session: tasks = [ recognize_captcha(session, "https://www.php.cn/link/6bd64b8669cf79ea63ff5a68f1baf5e3"), recognize_captcha(session, "https://www.php.cn/link/245142a8282a24362c6a1762f55dab27"), ] results = await asyncio.gather(*tasks) print(results)
-
img_bytes必须是原始二进制(bytes),不是 base64 字符串;若后端返回的是 base64,先用base64.b64decode() - 如果验证码图带 Referer 或 Cookie 限制,
session.get()会自动携带,无需额外处理 - 并发数别盲目拉太高,
ddddocr单实例在 CPU 上实际吞吐有限,5–10 并发通常就是瓶颈
什么时候该切到远程 OCR 服务而非本地跑模型
当你遇到这些情况时,本地 ddddocr 就不该硬扛:
- 验证码含干扰线+扭曲+粘连字符,
ddddocr准确率掉到 60% 以下 - 需要支持手写体、印章、多语言混合(如中英数字混排)
- 爬虫部署在无 GPU 的轻量服务器(如阿里云共享型ECS),CPU 资源吃紧
- 业务要求识别结果带坐标或置信度,而
ddddocr.classification()只返回字符串
此时推荐对接已封装好的异步 OCR 服务,比如基于 CRNN 的轻量服务或 GLM-OCR 的 Gradio API。调用方式统一为:
async with session.post("http://ocr-service:8000/recognize", json={"image_base64": b64}) as resp:
data = await resp.json()
code = data["result"]注意:远程服务的延迟比本地高 50–200ms,但稳定性、可维护性和扩展性碾压本地硬编码。
绕过验证码 ≠ 忽略反爬逻辑,OCR 只是其中一环
很多开发者以为“能识别验证码就等于能过登录”,实际漏掉了三个关键点:
- 验证码只是第一道门,后面可能还有滑块轨迹校验、设备指纹、行为时序分析(比如鼠标移动速度、点击间隔)
- 识别出的验证码若在 2 分钟内未提交,服务端 token 已失效,需重新拉图
- 高频请求触发风控后,即使验证码识别正确,也会返回 412 或跳转到人机验证页,此时要配合代理 IP 池轮换
OCR 解决的是“看懂图”,但整个流程是否走得通,取决于你是否把 Session、Cookie、Headers、Referer、时间戳这些上下文完整串联起来——少一个,识别得再准也没用。

















