Python爬虫实战:用ddddocr突破条形码查询网站验证码

条形码查询网站通常会在搜索环节设置验证码来阻挡自动化爬取,这对爬虫开发者来说是个常见痛点。最近在帮朋友开发一个商品比价工具时,我发现国内几个主流条形码查询平台的数字验证码识别率差异很大,经过反复测试,最终选择了ddddocr这个轻量级OCR库作为解决方案。

1. 验证码识别技术选型

在爬虫项目中遇到验证码时,开发者通常有几种选择:

  • 人工识别:适合验证码出现频率极低的场景,但效率低下
  • 第三方打码平台:按次收费,识别率高但成本随请求量增加
  • OCR库本地识别:前期投入时间成本,但长期使用经济高效

对于条形码查询这类需要高频调用的场景,本地OCR识别是最佳选择。以下是主流Python OCR库的对比:

库名称 识别类型 准确率 速度 模型大小 适用场景
pytesseract 多种字符 通用文本识别
easyocr 多种语言 较大 多语言文档识别
ddddocr 数字/字母验证码 极高 极快 极小 验证码专项识别
# 各库安装命令对比
# pytesseract
pip install pytesseract pillow

# easyocr 
pip install easyocr

# ddddocr
pip install ddddocr

从实际测试来看,ddddocr对4-6位数字验证码的识别准确率能达到98%以上,且推理速度在10ms级别,特别适合爬虫场景。

2. ddddocr环境配置与基础使用

2.1 安装注意事项

虽然ddddocr的安装很简单,但在不同环境中可能会遇到依赖冲突问题。建议使用虚拟环境:

# 创建并激活虚拟环境
python -m venv ocr_env
source ocr_env/bin/activate  # Linux/Mac
ocr_env\Scripts\activate      # Windows

# 安装ddddocr
pip install ddddocr --upgrade

注意:如果遇到OpenCV相关错误,可以尝试先安装opencv-python-headless版本

2.2 基础识别功能

ddddocr的使用非常简单,以下是最基础的验证码识别代码:

import ddddocr

def recognize_captcha(image_path):
    ocr = ddddocr.DdddOcr()
    with open(image_path, 'rb') as f:
        img_bytes = f.read()
    return ocr.classification(img_bytes)

# 使用示例
captcha_text = recognize_captcha('verification_code.png')
print(f"识别结果: {captcha_text}")

在实际项目中,我们通常直接从网络获取验证码图片而不是保存到本地。结合requests库的改进版本:

import requests
from io import BytesIO

def download_and_recognize(url, session=None):
    ocr = ddddocr.DdddOcr()
    s = session or requests.Session()
    response = s.get(url)
    return ocr.classification(response.content)

3. 条形码查询网站实战

以国内常见的条形码查询网站为例,完整的爬取流程需要处理以下几个关键点:

3.1 验证码获取与识别

大多数网站的验证码实现逻辑相似:

  1. 访问验证码生成接口获取图片
  2. 用户提交表单时验证码与session绑定
  3. 服务端校验session中存储的验证码

对应的Python实现:

def get_barcode_info(barcode):
    base_url = 'http://example-barcode-site.com'
    s = requests.Session()
    
    # 获取验证码
    captcha_url = f"{base_url}/captcha?t={int(time.time())}"
    captcha_text = download_and_recognize(captcha_url, s)
    
    # 提交查询
    payload = {
        'barcode': barcode,
        'captcha': captcha_text
    }
    response = s.post(f"{base_url}/search", data=payload)
    return response.json()

3.2 错误处理与重试机制

验证码识别不可能100%准确,必须实现合理的重试逻辑:

def query_with_retry(barcode, max_retries=3):
    for attempt in range(max_retries):
        try:
            result = get_barcode_info(barcode)
            if result.get('code') == 0:  # 假设0表示成功
                return result
            elif result.get('code') == 1001:  # 验证码错误
                continue
        except Exception as e:
            print(f"Attempt {attempt+1} failed: {str(e)}")
            time.sleep(1)  # 添加延迟避免被封
    
    raise Exception("Max retries exceeded")

为了提高识别率,可以添加一些预处理步骤:

from PIL import Image
import io

def preprocess_image(image_bytes):
    """简单的图像预处理"""
    img = Image.open(io.BytesIO(image_bytes))
    # 转换为灰度图
    img = img.convert('L')
    # 二值化处理
    img = img.point(lambda x: 255 if x > 180 else 0)
    output = io.BytesIO()
    img.save(output, format='PNG')
    return output.getvalue()

# 修改识别函数
def enhanced_recognize(url):
    ocr = ddddocr.DdddOcr()
    response = requests.get(url)
    processed = preprocess_image(response.content)
    return ocr.classification(processed)

4. 性能优化与反反爬策略

4.1 并发处理

当需要查询大量条形码时,可以使用多线程提高效率:

from concurrent.futures import ThreadPoolExecutor

def batch_query(barcodes, workers=4):
    results = {}
    with ThreadPoolExecutor(max_workers=workers) as executor:
        future_to_barcode = {
            executor.submit(query_with_retry, barcode): barcode
            for barcode in barcodes
        }
        for future in concurrent.futures.as_completed(future_to_barcode):
            barcode = future_to_barcode[future]
            try:
                results[barcode] = future.result()
            except Exception as e:
                results[barcode] = {'error': str(e)}
    return results

4.2 请求伪装与速率控制

为避免被网站封禁,需要做好请求伪装和速率控制:

  • 使用随机User-Agent
  • 添加合理的请求间隔
  • 使用代理IP池(合规前提下)
from fake_useragent import UserAgent

def get_random_headers():
    ua = UserAgent()
    return {
        'User-Agent': ua.random,
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.5',
    }

def safe_query(barcode):
    headers = get_random_headers()
    time.sleep(random.uniform(1, 3))  # 随机延迟
    return get_barcode_info(barcode, headers=headers)

在实际项目中,我发现ddddocr对数字验证码的识别效果最好,当遇到字母数字混合验证码时,可以调整初始化参数:

# 启用字母数字识别
ocr = ddddocr.DdddOcr(use_angle_cls=True, charsets='charset_en')

对于特别复杂的验证码,可能需要结合多种识别方法。最近一个项目中,我采用了以下策略组合:

  1. 首先尝试ddddocr自动识别
  2. 识别失败后对图像进行降噪处理再次尝试
  3. 仍然失败则自动标记该验证码用于后续模型训练
def advanced_recognize(image_bytes):
    # 第一次尝试
    ocr = ddddocr.DdddOcr()
    first_try = ocr.classification(image_bytes)
    if len(first_try) == 4:  # 假设验证码长度应为4
        return first_try
    
    # 预处理后第二次尝试
    processed = preprocess_image(image_bytes)
    second_try = ocr.classification(processed)
    if len(second_try) == 4:
        return second_try
    
    # 保存难样本
    with open(f'hard_cases/{int(time.time())}.png', 'wb') as f:
        f.write(image_bytes)
    raise ValueError("验证码识别失败")
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐