前言

在进行网络爬虫数据采集、自动化测试(如 Selenium/Playwright)或者自动化脚本编写时,验证码(CAPTCHA) 往往是开发者面临的第一道、也是最头疼的屏障。

传统应对验证码的方案通常有两种:

  1. 接入第三方付费打码平台:按次收费,不仅产生持续的资金成本,且存在网络延迟和接口失效风险。
  2. 自己训练深度学习模型(CNN+CTC):需要采集数万张标注样本,配置 PyTorch 环境进行繁琐的训练,技术门槛极高。

直到开源项目 sml2h3/ddddocr(中文名:带带弟弟 OCR)的出现,彻底改变了这一切。它是一个离线运行、完全免费、零配置的通用验证码识别 SDK。你不需要懂得任何神经网络知识,只需一行代码,就能实现极高准确率的验证码识别。

本文将带你全面了解 ddddocr,并展示如何在真实的 Python 爬虫项目中实际运用它。


一、DdddOcr 的核心特点

  • 零配置,开箱即用:不需要下载额外的权重文件,也不需要复杂的特征工程,安装后直接加载即可运行。
  • 支持超多验证码类型
    • 英数混合验证码:最常见的扭曲、带干扰线、噪点的字母数字验证码。
    • 滑块验证码缺口检测:计算滑块背景图与缺口小图之间的精准 X 轴偏移距离。
    • 点选/点击验证码:识别出图片中文字或物体的具体坐标。
  • 100% 离线与高效:基于 ONNX Runtime 推理引擎,不需要联网,单张图片识别仅需 10~30 毫秒

二、开发环境搭建与避坑指南

1. 基础安装

在命令行中直接通过 pip 安装:

1
pip install ddddocr

2. 常见报错及解决方案

由于 ddddocr 发布时间较早,在较新的 Python 环境中可能会遇到以下兼容性问题:

⚠️ 报错一:AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'

  • 原因:较新版本的 Pillow 库(大于 10.0.0)移除了已经过时的 ANTIALIAS 属性。
  • 解决办法:回退 Pillow 库到 9.x 版本:
    1
    pip install "Pillow<10.0.0"

⚠️ 报错二:安装时报错,提示缺少 C++ 编译环境

  • 原因:ONNX Runtime 在某些旧版 Windows 系统下运行需要系统的 C++ 运行库支持。
  • 解决办法:下载并安装 Microsoft Visual C++ Redistributable 即可解决。

三、三大验证码核心识别代码

1. 英数混合验证码(普通打码)

这是最基础的用法,直接输入验证码图片的二进制字节流(bytes),即可返回识别出来的字符串结果:

1
2
3
4
5
6
7
8
9
10
11
12
import ddddocr

# 1. 初始化 OCR 引擎(会自动在后台加载内置的默认模型)
ocr = ddddocr.DdddOcr(show_ad=False) # show_ad=False 用于关闭启动时的广告输出

# 2. 读取验证码图片
with open("captcha.png", "rb") as f:
img_bytes = f.read()

# 3. 识别
result = ocr.classification(img_bytes)
print(f"验证码识别结果: {result}")

2. 滑块验证码(缺口距离检测)

在滑块验证码中,前端往往要求我们把滑块从左侧拖动到右侧的缺口处。要模拟这个动作,我们必须知道拖动的具体像素距离(Offset)

ddddocr 提供了非常强大的滑块缺口计算方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import ddddocr

det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)

# 1. 读取滑块背景大图和带有缺口的目标小图(通常小图带有一点透明通道)
with open("target.png", "rb") as f:
target_bytes = f.read() # 滑块小图

with open("background.png", "rb") as f:
background_bytes = f.read() # 背景大图

# 2. 运行缺口检测
# simple_target=True 代表滑块小图是不带边缘干扰的简单背景,通常能提升匹配精准度
res = det.slide_match(target_bytes, background_bytes, simple_target=True)

# 3. 输出结果
# res 返回格式通常为: {'target': [x1, y1, x2, y2], 'box': [x1, y1, x2, y2]}
# 其中 x1 (即 res['target'][0]) 就是滑块小图在水平方向上需要拖拽的像素偏移距离!
offset = res['target'][0]
print(f"滑块缺口的 X 轴偏移距离: {offset} 像素")

3. 点选/点击验证码

点选验证码通常会给出一张大图,上方提示“请依次点击:【中】【国】”。我们需要获取这些字符在大图上的坐标。

通过 ddddocr 的目标检测(Detection)模式可以完美解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import ddddocr
import cv2

# 初始化目标检测引擎
det = ddddocr.DdddOcr(det=True, show_ad=False)

with open("click_captcha.png", "rb") as f:
img_bytes = f.read()

# 进行目标框检测
# bboxes 将返回所有检测到的中文字符或物体的外包络框坐标 [[x1, y1, x2, y2], ...]
bboxes = det.detection(img_bytes)

print("检测到的文字/物体包围盒坐标:")
for box in bboxes:
x1, y1, x2, y2 = box
center_x = int((x1 + x2) / 2)
center_y = int((y1 + y2) / 2)
print(f"目标区域: ({x1}, {y1}) -> ({x2}, {y2}) | 中心点击坐标: ({center_x}, {center_y})")

四、项目实战:Selenium + DdddOcr 自动破解登录验证码

接下来,我们将这些知识点融会贯通,写一个完整的爬虫实战案例:使用 Selenium 自动访问某系统登录页面,抓拍验证码图片并使用 ddddocr 识别,自动填入完成登录。

实战代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
import ddddocr

def login_with_captcha():
# 1. 初始化浏览器驱动
options = webdriver.ChromeOptions()
options.add_argument("--start-maximized")
driver = webdriver.Chrome(options=options)

# 初始化 ddddocr 引擎
ocr = ddddocr.DdddOcr(show_ad=False)

try:
# 打开模拟测试登录页面
driver.get("https://example-login-site.com/login")
time.sleep(2) # 等待页面加载

# 2. 定位页面元素
username_input = driver.find_element(By.ID, "username")
password_input = driver.find_element(By.ID, "password")
captcha_img_element = driver.find_element(By.ID, "captcha_img") # 验证码图片元素
captcha_input = driver.find_element(By.ID, "captcha_code")
submit_btn = driver.find_element(By.ID, "login_submit")

# 3. 截取并获取验证码图片的二进制流
# Selenium 提供了直接对特定页面元素进行截图的方法
captcha_png = captcha_img_element.screenshot_as_png

# 将 PNG 字节流转为内存图片,并统一转换为 RGB 格式(去除干扰和透明度)
image = Image.open(BytesIO(captcha_png)).convert("RGB")

# 转换为 ddddocr 所需的 bytes
byte_io = BytesIO()
image.save(byte_io, format="PNG")
captcha_bytes = byte_io.getvalue()

# 4. 调用 ddddocr 进行识别
captcha_text = ocr.classification(captcha_bytes)
print(f"[AI Status] 验证码自动识别结果: {captcha_text}")

# 5. 自动填充表单
username_input.send_keys("admin")
password_input.send_keys("MyPassword123")
captcha_input.send_keys(captcha_text) # 填入 AI 识别出来的验证码

# 6. 模拟提交
time.sleep(1)
submit_btn.click()
time.sleep(3)

# 验证是否成功登录
if "dashboard" in driver.current_url.lower():
print("🎉 [Success] 登录成功!验证码识别无误。")
else:
print("⚠️ [Failed] 登录失败,可能是验证码识别错误,正在尝试重试...")

finally:
driver.quit()

if __name__ == "__main__":
login_with_captcha()

五、进阶篇:如何进一步提升 ddddocr 的识别精度?

虽然 ddddocr 的默认模型已经非常强悍,但在面对极个别噪点密集、对比度极低的魔鬼验证码时,仍然可能发生错判。你可以采用以下两项高级技巧来大幅提升准确率:

1. 图像预处理(二值化与去噪)

在将图片送入 ddddocr 之前,利用 PillowOpenCV 进行预处理,过滤干扰线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from PIL import Image
import numpy as np

def preprocess_image(image_path):
"""
对验证码进行降噪和二值化处理
"""
img = Image.open(image_path).convert("L") # 1. 转换为灰度图

# 2. 简易二值化:将灰度值小于阈值的像素设为黑色(0),其余设为白色(255)
threshold = 127
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)

img = img.point(table, "1")
return img

2. 避免重复实例化(性能优化)

在多线程爬虫或者高并发任务中,千万不要在每个函数循环里都写一遍 ocr = ddddocr.DdddOcr()

每次实例化都会重新在内存中加载几十兆的 ONNX 模型,导致 CPU 瞬时占满、运行极慢甚至引发内存泄漏。
正确做法:将 ocr 定义为全局单例,或者通过类属性进行共享复用。


结语

ddddocr 的开源,无疑是广大爬虫与自动化测试开发者的巨大福音。它以极低的部署门槛和优秀的本地执行效率,帮助我们优雅地绕过了大多数常规字符、滑块和点选验证码。

安全提示:本文所介绍的验证码识别技术仅用于自动化测试、合规数据分析及学术研究目的。请在开发爬虫时遵守目标网站的 robots.txt 规则及相关法律法规。