YesCaptcha插件+自建API实战:用DdddOCR实现浏览器自动化测试中的验证码绕过
YesCaptcha插件+自建API实战:用DdddOCR实现浏览器自动化测试中的验证码绕过
验证码一直是自动化测试工程师的痛点。每当脚本运行到关键步骤,一个突然弹出的验证码就能让整个流程戛然而止。传统解决方案要么成本高昂,要么识别率不稳定。本文将分享如何通过YesCaptcha插件与自建DdddOCR API的组合,构建一套经济高效的验证码处理方案。
这套方案特别适合中小团队或个人开发者,它既保留了商业方案的便捷性,又通过开源技术降低了成本。我们将从API搭建开始,逐步深入到与Selenium/Playwright的集成,最后探讨如何应对识别率"玄学"这个现实问题。
1. 技术选型与架构设计
在开始编码前,我们需要明确两个核心组件的定位:
- YesCaptcha插件:负责浏览器环境中的验证码捕获和结果回填
- DdddOCR服务:提供本地的验证码识别能力
它们通过一个简单的Flask API进行通信,整体架构如下图所示:
[浏览器] ←→ [YesCaptcha插件] ←→ [自建API] ←→ [DdddOCR]这种设计有三大优势:
- 隐私性:所有验证码图片不会离开本地环境
- 成本控制:无需支付按次计费的识别服务
- 可扩展性:可随时切换OCR引擎而不影响浏览器端配置
2. 搭建DdddOCR识别服务
DdddOCR以其"开箱即用"的特性成为理想选择。我们先搭建基础的识别服务:
from flask import Flask, request, jsonify import ddddocr import base64 import uuid app = Flask(__name__) ocr = ddddocr.DdddOcr(show_ad=False) # 禁用广告输出 @app.route('/captcha/v1', methods=['POST']) def handle_captcha(): try: image_data = request.json.get('image') if not image_data: return jsonify({'error': 'Missing image data'}), 400 # 移除base64前缀 if 'base64,' in image_data: image_data = image_data.split('base64,')[1] decoded = base64.b64decode(image_data) result = ocr.classification(decoded) return jsonify({ 'success': True, 'result': result }) except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)关键参数说明:
| 参数 | 说明 | 推荐值 |
|---|---|---|
| show_ad | 控制台广告显示 | False |
| use_gpu | GPU加速 | 根据设备情况 |
| charsets | 字符集设置 | 根据验证码类型调整 |
启动服务:
python server.py --threaded # 启用多线程处理3. YesCaptcha插件配置与对接
YesCaptcha插件需要配置我们的自建API端点:
- 在Chrome中安装YesCaptcha插件
- 点击插件图标 → 设置 → 自定义API
- 填写API地址:
http://your-server-ip:5000/captcha/v1 - 保存设置
常见问题排查:
- 如果遇到CORS错误,需要在Flask中添加CORS支持:
from flask_cors import CORS CORS(app) # 允许所有跨域请求- 测试API是否正常工作:
curl -X POST http://localhost:5000/captcha/v1 \ -H "Content-Type: application/json" \ -d '{"image":"base64-encoded-image"}'4. 与自动化测试框架集成
4.1 Selenium集成方案
在Selenium脚本中,我们需要处理两种常见场景:
场景一:传统图片验证码
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait def solve_captcha(driver): # 等待验证码元素出现 captcha_img = WebDriverWait(driver, 10).until( lambda d: d.find_element(By.XPATH, '//img[contains(@src,"captcha")]') ) # 触发YesCaptcha识别 driver.execute_script(""" const img = arguments[0]; const event = new MouseEvent('contextmenu', { bubbles: true, clientX: img.getBoundingClientRect().left + 10, clientY: img.getBoundingClientRect().top + 10 }); img.dispatchEvent(event); """, captcha_img) # 等待插件处理 time.sleep(2) # 根据网络情况调整场景二:reCAPTCHA挑战
对于Google reCAPTCHA,YesCaptcha可以自动处理可见挑战,但需要确保:
- 插件已启用reCAPTCHA支持
- 页面加载了reCAPTCHA API
- 有足够的等待时间
# 在reCAPTCHA iframe出现后 driver.execute_script(""" document.querySelector('.g-recaptcha').scrollIntoView(); """) time.sleep(3) # 等待插件处理4.2 Playwright集成技巧
Playwright的浏览器上下文需要特别处理插件加载:
from playwright.sync_api import sync_playwright with sync_playwright() as p: # 加载带插件的浏览器实例 context = p.chromium.launch_persistent_context( user_data_dir='./profile', args=[ f'--disable-extensions-except=/path/to/yescaptcha', f'--load-extension=/path/to/yescaptcha' ] ) page = context.new_page() page.goto('https://target-site.com') # 处理验证码出现的逻辑 if page.locator('#captcha-image').is_visible(): page.click('#captcha-image', button='right') page.wait_for_selector('#yescaptcha-menu') page.click('text=识别验证码')5. 识别率优化与监控
DdddOCR的识别率确实存在波动,我们可以通过以下策略改善:
训练数据增强:
# 自定义字符集提高特定类型验证码识别率 ocr = ddddocr.DdddOcr( charsets='ABCDEFGHJKLMNPQRSTUVWXYZ23456789', # 去除容易混淆的字符 use_gpu=True )结果验证机制:
def validate_captcha(text): # 基础校验规则 if len(text) != 4: return False if not text.isalnum(): return False return True监控方案设计:
- 记录每次识别的响应时间
- 对失败请求进行重试
- 定期统计识别成功率
@app.route('/captcha/v1', methods=['POST']) def handle_captcha(): start_time = time.time() try: # ...原有处理逻辑... # 添加监控日志 log = { 'timestamp': datetime.now().isoformat(), 'duration': time.time() - start_time, 'success': True, 'type': request.json.get('type', 'unknown') } write_log(log) return jsonify(response) except Exception as e: # 错误日志 log.update({'success': False, 'error': str(e)}) write_log(log) raise6. 生产环境部署建议
对于需要7×24小时运行的自动化测试系统,建议:
服务部署:
- 使用Gunicorn替代Flask开发服务器
gunicorn -w 4 -b :5000 server:app负载均衡: 当QPS较高时,可以通过Nginx实现负载均衡:
upstream ocr_servers { server 127.0.0.1:5000; server 127.0.0.1:5001; server 127.0.0.1:5002; } server { listen 80; location / { proxy_pass http://ocr_servers; } }性能监控: 使用Prometheus + Grafana监控关键指标:
| 指标名称 | 说明 | 预警阈值 |
|---|---|---|
| request_duration_seconds | 请求处理时间 | >1s |
| error_rate | 错误率 | >20% |
| active_threads | 活跃线程数 | >80% max_threads |
7. 备选方案与降级策略
任何OCR方案都不可能100%可靠,必须设计降级方案:
人工介入流程:
- 当连续失败超过3次时
- 自动截图保存验证码
- 通过邮件/IM通知负责人
多引擎备用:
def recognize_with_fallback(image): try: # 主引擎尝试 result = ocr.classification(image) if validate_result(result): return result # 备用引擎 from other_ocr import backup_recognize return backup_recognize(image) except: return manual_process(image)- 验证码预处理:
from PIL import Image, ImageFilter def preprocess_image(image_bytes): img = Image.open(io.BytesIO(image_bytes)) img = img.filter(ImageFilter.SHARPEN) # 锐化 img = img.point(lambda x: 0 if x < 128 else 255) # 二值化 return img.tobytes()这套方案在实际项目中已经稳定运行超过6个月,平均识别率保持在78%左右。对于特别复杂的验证码,我们最终采用了商业API+自建服务的混合模式——常规验证码走本地API,特殊类型自动切换到付费服务。这种组合既控制了成本,又保证了关键业务的顺畅运行。
