Selenium自动化测试中验证码识别实战:ddddocr离线解决方案 1. 项目概述当自动化遇上验证码这堵墙做自动化测试或者爬虫的朋友肯定都遇到过验证码这个“拦路虎”。无论是登录、注册还是关键操作前的二次确认验证码的设计初衷就是为了区分人和机器。对于纯数字字母的简单验证码早些年用PILTesseract OCR还能勉强应付但随着验证码复杂度的提升各种扭曲、粘连、干扰线的出现传统方案基本就歇菜了。更别提现在主流的滑动拼图、文字点选、图标点选等交互式验证码直接让基于像素比对的Selenium脚本“望码兴叹”。这时候验证码识别就成了自动化流程中必须打通的关键一环。以前这个环节要么靠人工打码效率低下要么接入付费的第三方识别API增加成本且可能有稳定性风险。直到我遇到了ddddocr这个神器局面才彻底改变。它是一款基于深度学习的开源OCR库由国内开发者训练对中文场景下的各种验证码尤其是数字、字母和简单中文的识别率非常高而且最大的优点是离线、免费、易用。这个项目的核心就是解决“Selenium自动化流程中的验证码识别”这个具体痛点。想象一下你写了一个自动登录脚本每到输入验证码那一步就得手动介入那还叫“自动化”吗我们的目标是让Selenium脚本像人一样“看到”验证码图片然后“认出”里面的字符并自动填写到输入框里实现端到端的无人值守操作。整个过程借助ddddocr我们可以在5分钟内快速集成并验证效果。这不仅仅是省了几秒钟的手动输入时间更是让整个自动化流程得以闭环真正发挥价值。2. 核心工具选型与原理浅析2.1 为什么是ddddocr面对验证码识别我们有几个选择自研深度学习模型、使用Tesseract、调用云API或者使用ddddocr这类开源专精库。自研模型成本太高不适合快速解决问题。Tesseract对规整的印刷体文档效果不错但对专门对抗机器的验证码效果往往不尽人意。云API需要网络请求有延迟、有费用还可能涉及数据隐私。ddddocr的优势在于它的“专精”和“便捷”针对性强它的模型是使用海量中文验证码图片进行训练的对于数字、大小写字母以及常见中文字符的变形、扭曲、干扰有很好的鲁棒性。这比通用的OCR引擎更对症下药。离线运行所有识别过程都在本地完成无需网络速度极快毫秒级且不存在服务不稳定的风险。近乎零配置安装即用无需复杂的模型下载或环境配置对新手极其友好。免费开源不用担心调用次数和费用问题可以集成到任何商业或非商业项目中。注意ddddocr主要擅长处理字符型验证码如数字、字母、简单汉字。对于复杂的滑动拼图、点选等验证码它可能无法直接解决这类问题通常需要结合图像处理找缺口位置或目标检测模型。但就覆盖范围而言它已经能解决市面上超过70%的图形验证码问题了。2.2 Selenium自动化的骨架Selenium是我们实现Web自动化的核心工具。它通过WebDriver协议可以驱动真实的浏览器如Chrome、Firefox执行点击、输入、跳转等操作模拟真实用户行为。在这个项目中Selenium负责打开登录页面。定位用户名、密码输入框并填入信息。定位验证码图片元素并截图。定位验证码输入框并填入识别结果。点击登录按钮。Selenium提供了稳定的、跨浏览器的操作接口是我们自动化流程的“执行者”。而ddddocr则是为这个执行者装上了“眼睛”和“大脑”让它能看懂验证码。2.3 协同工作原理整个自动登录的流程可以看作一个简单的“感知-决策-执行”循环感知Selenium截图Selenium脚本导航到登录页找到验证码图片的HTML元素然后将其截图保存为一张本地图片文件通常是PNG格式。这一步相当于机器“看到了”验证码。决策ddddocr识别Python脚本调用ddddocr库读取上一步保存的图片文件送入其内置的深度学习模型进行推理。模型输出图片中最可能的字符序列也就是识别出的验证码文本。这一步相当于机器“认出了”验证码。执行Selenium输入并提交Selenium脚本将识别得到的文本填入网页的验证码输入框然后模拟点击登录按钮。至此一个完整的自动化登录动作完成。这个流程的关键在于图片的准确获取和识别库的准确调用两者无缝衔接才能保证成功率。3. 环境准备与核心库安装3.1 Python环境搭建确保你已安装Python建议3.7及以上版本。可以通过命令行输入python --version或python3 --version来检查。如果没有安装去Python官网下载安装包安装时务必勾选“Add Python to PATH”。3.2 安装ddddocr安装ddddocr非常简单使用pip即可。但由于它依赖一些科学计算库建议使用国内镜像源加速下载。pip install ddddocr -i https://pypi.tuna.tsinghua.edu.cn/simple安装避坑指南网络问题如果安装缓慢或失败一定要加上-i参数指定镜像源清华、阿里云的源都可以。依赖冲突ddddocr依赖于onnxruntime、numpy、Pillow等。如果安装过程中提示某些包版本冲突可以尝试先升级pippip install --upgrade pip然后再安装。或者创建一个新的虚拟环境使用venv或conda来隔离项目依赖这是最推荐的做法。关于numpy版本网上有些旧教程会提到numpy版本适配问题。目前ddddocr较新版本与onnxruntime的版本依赖已经比较稳定直接安装一般无问题。如果遇到可以尝试固定版本安装pip install ddddocr numpy1.21.0 onnxruntime但通常不需要。3.3 安装Selenium及WebDriver首先安装Selenium库pip install selenium最关键的一步下载浏览器驱动。Selenium需要通过一个叫“WebDriver”的组件来操控浏览器。你需要下载与你电脑上Chrome浏览器版本匹配的chromedriver。查看Chrome版本打开Chrome点击右上角三个点 - 帮助 - 关于Google Chrome记下版本号例如124.0.6367.91。下载驱动访问ChromeDriver官网或国内镜像站找到与你的Chrome主版本号124完全一致的chromedriver版本下载。放置驱动将下载的chromedriver.exe文件放在一个你知道的目录例如C:\WebDriver\。然后需要将这个目录路径添加到系统的环境变量PATH中。这样Python代码中就可以直接调用webdriver.Chrome()而无需指定驱动路径。或者你也可以在代码中显式指定驱动路径。实操心得浏览器自动更新会导致驱动版本不匹配这是最常见的坑。建议在自动化脚本开头加入版本检查逻辑或者考虑使用webdriver-manager这个第三方库它可以自动下载和管理匹配的驱动省去手动维护的麻烦。安装pip install webdriver-manager使用时代码会更简洁。4. 分步实战5分钟实现自动登录我们以一个假设的简单登录页面为例假设其验证码图片的HTML元素id为captcha_image验证码输入框的id为captcha_code。4.1 第一步编写Selenium脚本框架我们先搭建一个基本的Selenium脚本完成打开网页、定位元素、输入用户名密码等操作。from selenium import webdriver from selenium.webdriver.common.by import By import time # 初始化浏览器驱动这里假设chromedriver已在PATH中 driver webdriver.Chrome() driver.implicitly_wait(10) # 设置隐式等待让脚本在找不到元素时等待一段时间 driver.maximize_window() # 最大化窗口确保元素可见 try: # 1. 打开目标登录页 login_url https://example.com/login # 替换成你的目标网址 driver.get(login_url) time.sleep(2) # 等待页面加载可根据网络情况调整 # 2. 定位并输入用户名、密码 username_input driver.find_element(By.ID, username) # 根据实际页面元素修改定位方式 password_input driver.find_element(By.ID, password) username_input.send_keys(your_username) password_input.send_keys(your_password) print(用户名密码已输入) # 接下来我们将在这里处理验证码... except Exception as e: print(f脚本执行出错{e}) finally: # 暂时不关闭浏览器方便调试 # driver.quit() pass4.2 第二步集成ddddocr识别验证码现在我们来解决最核心的问题获取验证码图片并识别。from selenium import webdriver from selenium.webdriver.common.by import By import ddddocr # 导入ddddocr import time import os # 初始化ddddocr识别器一次初始化可重复使用 ocr ddddocr.DdddOcr() # ... 之前的浏览器初始化代码不变 try: # ... 之前的打开网页、输入用户名密码代码不变 # 3. 定位验证码图片元素并截图 captcha_element driver.find_element(By.ID, captcha_image) # 方法一直接截图元素推荐能避免页面其他部分干扰 # 注意此方法需要验证码图片是独立的img标签且能被正确渲染 captcha_screenshot_path captcha.png captcha_element.screenshot(captcha_screenshot_path) # 将元素截图保存为文件 print(f验证码截图已保存至{captcha_screenshot_path}) # 方法二如果方法一截图失败例如验证码是背景图或canvas绘制则截取整个屏幕再裁剪 # 这需要你先获取验证码元素的位置和大小相对复杂此处先不展开。 # 4. 使用ddddocr识别截图 with open(captcha_screenshot_path, rb) as f: image_bytes f.read() # 核心识别代码就这一行 captcha_text ocr.classification(image_bytes) print(f识别出的验证码为{captcha_text}) # 5. 将识别结果输入验证码框 captcha_input driver.find_element(By.ID, captcha_code) captcha_input.clear() # 先清空避免原有内容 captcha_input.send_keys(captcha_text) print(验证码已自动填入) # 6. 点击登录按钮假设按钮id是submit login_button driver.find_element(By.ID, submit) login_button.click() print(已点击登录按钮) # 7. 等待并检查登录结果例如检查是否跳转到首页或出现欢迎语 time.sleep(3) # 这里可以添加断言例如检查页面标题或某个特定元素 if 首页 in driver.title or driver.find_elements(By.CLASS_NAME, welcome): print(登录成功) else: print(登录可能失败请检查。) except Exception as e: print(f自动化登录过程出错{e}) finally: # 脚本结束后可以手动关闭浏览器或添加一个输入等待 input(按回车键关闭浏览器...) driver.quit() # 清理临时截图文件 if os.path.exists(captcha_screenshot_path): os.remove(captcha_screenshot_path)代码关键点解析ddddocr.DdddOcr()初始化识别器。这个对象可以重复使用识别多张图片无需每次创建。.classification(image_bytes)这是核心识别方法传入图片的二进制数据返回识别出的字符串。element.screenshot()这是Selenium 4及以上版本提供的非常方便的方法可以直接对某个WebElement进行截图完美避开页面其他区域的干扰比截全屏再裁剪精准得多。识别完成后记得清理临时截图文件保持脚本整洁。4.3 第三步处理验证码刷新与重试机制一个健壮的自动化脚本必须考虑识别失败的情况。验证码可能模糊不清导致ddddocr识别错误。常见的应对策略是“识别-重试”机制。# ... 初始化部分不变 def recognize_captcha(driver, ocr, element_id, max_retries3): 识别验证码支持重试 for attempt in range(max_retries): try: captcha_element driver.find_element(By.ID, element_id) captcha_path fcaptcha_attempt_{attempt}.png captcha_element.screenshot(captcha_path) with open(captcha_path, rb) as f: captcha_text ocr.classification(f.read()) print(f第{attempt1}次识别结果{captcha_text}) # 简单的验证识别结果是否为空或长度异常根据实际情况调整 if captcha_text and 4 len(captcha_text) 6: # 假设验证码长度为4-6位 os.remove(captcha_path) return captcha_text else: print(f识别结果{captcha_text}不符合预期准备重试...) except Exception as e: print(f第{attempt1}次识别失败{e}) # 触发验证码刷新假设刷新按钮id是refresh_captcha try: refresh_btn driver.find_element(By.ID, refresh_captcha) refresh_btn.click() time.sleep(1) # 等待新验证码加载 except: # 如果没有刷新按钮可能需要重新加载页面或等待自动刷新 print(未找到刷新按钮无法更换验证码) break # 清理本次失败的截图 if os.path.exists(captcha_path): os.remove(captcha_path) print(f经过{max_retries}次尝试验证码识别均失败。) return None # 在主流程中使用 try: # ... 打开页面输入用户名密码 ... # 识别验证码最多重试3次 captcha_text recognize_captcha(driver, ocr, captcha_image, max_retries3) if captcha_text: # ... 输入验证码并登录 ... captcha_input driver.find_element(By.ID, captcha_code) captcha_input.send_keys(captcha_text) # ... 点击登录 ... else: print(验证码识别失败流程终止。) # 可以在这里加入告警通知如发送邮件、钉钉消息等 except Exception as e: # ... 异常处理 ... finally: # ... 清理工作 ...这个recognize_captcha函数增加了重试逻辑并在每次失败后尝试刷新验证码。同时加入了对识别结果的基本校验如非空和长度判断这能过滤掉一些明显错误的识别结果提高脚本的鲁棒性。5. 高级技巧与避坑指南5.1 验证码图片的多种获取方式element.screenshot()是最佳方式但并非万能。以下是一些替代方案获取图片src属性并下载如果验证码是img src...形式且链接是直接指向图片的可以提取src属性然后用requests库下载。import requests img_src captcha_element.get_attribute(src) response requests.get(img_src) captcha_bytes response.content # 直接将bytes传给ddddocr captcha_text ocr.classification(captcha_bytes)优点无需截图更快。缺点很多网站的验证码图片链接是动态的、带token或一次性的无法直接下载。截取全屏后裁剪当验证码是CSS背景图或Canvas绘制时element.screenshot()可能无效。此时需要截取整个浏览器窗口再根据元素的位置和大小进行裁剪使用PIL库。from PIL import Image # 获取元素位置和大小 location captcha_element.location size captcha_element.size # 截取全屏 driver.save_screenshot(full_page.png) full_img Image.open(full_page.png) # 计算裁剪区域 left location[x] top location[y] right left size[width] bottom top size[height] # 裁剪 captcha_img full_img.crop((left, top, right, bottom)) captcha_img.save(captcha_cropped.png)注意这种方法受页面滚动、浏览器缩放比例影响计算坐标时需要格外小心。5.2 提升ddddocr识别准确率虽然ddddocr开箱即用效果就不错但在一些复杂场景下对图片进行简单的预处理能显著提升识别率。import ddddocr from PIL import Image import io def preprocess_and_recognize(image_bytes): 预处理图片后识别 # 1. 转换为PIL Image对象进行处理 img Image.open(io.BytesIO(image_bytes)) # 2. 转换为灰度图减少颜色干扰 img img.convert(L) # 3. 二值化处理根据阈值将图片转为黑白 # 需要根据验证码特点调整阈值例如150 threshold 150 img img.point(lambda x: 255 if x threshold else 0) # 4. 降噪可选去除孤立的小点 # 这里可以使用PIL的filter或者更复杂的算法如中值滤波 # 5. 将处理后的图片转回bytes img_byte_arr io.BytesIO() img.save(img_byte_arr, formatPNG) processed_bytes img_byte_arr.getvalue() # 6. 识别 ocr ddddocr.DdddOcr() result ocr.classification(processed_bytes) return result # 使用方式 # captcha_text preprocess_and_recognize(image_bytes)预处理心得灰度化绝大多数验证码识别不需要颜色信息灰度化能简化问题。二值化这是最关键的一步。目标是让字符部分通常是深色变成黑色0背景变成白色255。阈值的选择需要根据具体验证码的对比度来调整可以多试几个值如120, 150, 180。降噪如果验证码有雪花点或干扰线可以尝试降噪。但要注意过度处理可能会损坏字符本身。顺序预处理步骤不是必须的ddddocr内置模型已经有一定的抗干扰能力。建议先直接用原始图片识别如果失败率较高再尝试加入预处理步骤并观察每个步骤对识别结果的影响。5.3 应对动态加载与智能等待现代网页大量使用Ajax动态加载内容验证码图片可能在页面加载完成后才通过JavaScript请求回来。如果Selenium在图片加载完成前就去截图会截到一个空白或者破损的图片。解决方案使用显式等待Explicit Wait。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待验证码图片元素不仅存在而且其“自然宽度”大于0表示图片已加载 try: wait WebDriverWait(driver, 10) # 最多等10秒 captcha_element wait.until(EC.presence_of_element_located((By.ID, captcha_image))) # 进一步等待图片实际加载完成 wait.until(lambda d: d.find_element(By.ID, captcha_image).get_attribute(naturalWidth) ! 0) except TimeoutException: print(验证码图片加载超时)这个技巧利用了naturalWidth这个DOM属性当图片成功加载后它的值会大于0。这比单纯等待元素出现更可靠。5.4 封装与集成到自动化框架在实际项目中我们不会把所有的代码都写在一个脚本里。更好的做法是将验证码识别功能封装成一个独立的模块或类。# captcha_solver.py import ddddocr from selenium.webdriver.remote.webelement import WebElement import time import os from typing import Optional class CaptchaSolver: def __init__(self): self.ocr ddddocr.DdddOcr() def solve_from_element(self, driver, element: WebElement, max_retries: int 3) - Optional[str]: 从Selenium元素对象识别验证码 for i in range(max_retries): try: screenshot_path ftemp_captcha_{int(time.time())}.png element.screenshot(screenshot_path) with open(screenshot_path, rb) as f: captcha_text self.ocr.classification(f.read()) # 基础校验 if self._is_valid_captcha(captcha_text): os.remove(screenshot_path) return captcha_text else: print(f第{i1}次识别结果{captcha_text}无效。) except Exception as e: print(f第{i1}次识别失败{e}) finally: if os.path.exists(screenshot_path): os.remove(screenshot_path) if i max_retries - 1: print(尝试刷新验证码...) self._refresh_captcha(driver) time.sleep(1.5) # 等待新验证码渲染 return None def _is_valid_captcha(self, text: str) - bool: 简单的验证码有效性校验可根据实际情况重写 # 示例非空且长度为4或5 return bool(text and text.strip() and len(text.strip()) in (4, 5)) def _refresh_captcha(self, driver): 尝试刷新验证码需要根据目标网站具体实现 # 方法1点击刷新按钮 try: refresh_btn driver.find_element(By.CSS_SELECTOR, .captcha-refresh, [onclick*refreshCaptcha]) refresh_btn.click() return except: pass # 方法2重新加载图片src针对img标签 try: img_element driver.find_element(By.ID, captcha_image) original_src img_element.get_attribute(src) # 通过添加时间戳参数强制刷新不一定所有网站都有效 driver.execute_script(farguments[0].src {original_src.split(?)[0]}?t new Date().getTime();, img_element) except: print(无法自动刷新验证码可能需要手动处理逻辑。) # 在主要自动化脚本中调用 from captcha_solver import CaptchaSolver solver CaptchaSolver() # ... 定位到验证码图片元素 captcha_img_element ... code solver.solve_from_element(driver, captcha_img_element) if code: # 输入code并继续 else: # 识别失败的处理逻辑这样封装后验证码识别逻辑与主业务流程解耦代码更清晰也便于维护和复用。6. 常见问题排查与优化实录在实际使用中你肯定会遇到各种各样的问题。下面是我踩过的一些坑和解决方案。6.1 识别准确率低怎么办现象ddddocr返回的结果经常是错的或者干脆是空字符串。排查与解决检查截图质量这是最常见的原因。先用代码把截图保存下来用眼睛看看截到的图片是否清晰、完整。有时候因为页面布局或CSS样式如overflow: hiddenelement.screenshot()可能只截到了图片的一部分。解决方案尝试用driver.save_screenshot截全屏然后手动裁剪验证码区域进行测试对比两种截图方式的差异。验证码类型不匹配ddddocr主要针对字符验证码。如果遇到计算题如“12”、滑动拼图、汉字点选等它无能为力。解决方案确认验证码类型。对于计算题可以尝试用简单的图像分割模板匹配或者更复杂的CNN模型。对于交互式验证码则需要完全不同的方案如计算滑动距离、识别点选目标。图片预处理如前所述尝试对截图进行灰度化、二值化、降噪等预处理可以显著提升复杂背景或低对比度验证码的识别率。建立一个本地测试集用不同的预处理参数进行批量测试找到最优组合。模型版本确保你安装的是最新版的ddddocr。开发者会持续更新模型。可以通过pip install ddddocr --upgrade升级。备用方案如果经过上述优化识别率仍然无法满足业务要求例如要求99%以上就需要考虑备用方案。例如可以集成一个付费的云OCR API作为降级方案当ddddocr连续失败N次后调用云API。6.2 Selenium截图失败或截到空白现象element.screenshot()抛出异常或者保存的图片是空白/灰色的。排查与解决元素不可见Selenium无法对不在当前视窗viewport内的元素进行截图。解决方案在截图前将元素滚动到视窗中。driver.execute_script(arguments[0].scrollIntoView(true);, captcha_element) time.sleep(0.5) # 等待滚动完成 captcha_element.screenshot(...)元素是CSS背景或Canvaselement.screenshot()对img标签最有效。如果验证码是div的背景图或者用canvas绘制的此方法可能失效。解决方案采用“截全屏裁剪”的方案或者尝试通过JavaScript直接获取Canvas的图像数据但这更复杂。等待不足图片还没加载出来就去截图。解决方案使用前面提到的“显式等待”确保图片的naturalWidth或complete属性为true。6.3 验证码刷新逻辑失效现象识别失败后脚本点击了刷新按钮但新的验证码图片没有变化或者还是识别错误。排查与解决点击未生效有些网站的刷新按钮是通过JavaScript监听事件触发的简单的click()可能无效。解决方案尝试用ActionChains模拟点击或者直接执行JavaScript来触发点击事件。from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) actions.move_to_element(refresh_btn).click().perform() # 或者 driver.execute_script(arguments[0].click();, refresh_btn)等待时间不足点击刷新后新的验证码图片从服务器加载到客户端需要时间。解决方案在点击刷新后增加一个等待并最好结合显式等待等待新图片加载完成。缓存问题浏览器或网站可能缓存了验证码图片。解决方案在刷新后可以尝试先清除该图片元素的src再重新设置或者强制浏览器跳过缓存重新请求。6.4 性能与稳定性考量OCR对象复用务必在全局或类级别只初始化一次ddddocr.DdddOcr()对象。反复初始化会浪费资源虽然不明显但在高频调用的场景下会有影响。资源清理脚本中创建的临时截图文件在识别完成后一定要及时删除使用os.remove避免磁盘空间被慢慢占满。异常处理与日志完善的try...except和日志记录至关重要。记录下每次识别的原始图片、识别结果、是否成功这对于后期分析识别率、优化预处理参数有巨大帮助。超时控制为网络请求、元素查找、显式等待都设置合理的超时时间避免脚本因某个环节卡死而无限期挂起。将ddddocr与Selenium结合实现验证码的自动识别是打通Web自动化“最后一公里”的利器。它把我们从繁琐、低效的人工打码中解放出来让自动化脚本真正实现了7x24小时无人值守运行。从环境搭建到核心代码再到高级技巧和问题排查整个过程的核心思路就是“模拟人的操作”让人工智能ddddocr代替人眼去“看”让自动化工具Selenium代替人手去“点”和“输”。虽然无法保证100%的成功率但通过合理的重试、预处理和降级策略我们完全可以将成功率提升到可接受的水平足以应对大多数自动化场景的需求。