1. 项目概述当自动化遇上验证码这道“门”做Web自动化测试或者数据采集的朋友对登录环节的验证码绝对是又爱又恨。爱的是它确实能有效防止恶意登录和爬虫恨的是它成了自动化流程中一道难以逾越的“门”。尤其是图片验证码它不像短信验证码那样可以调用接口获取也不像简单的算术验证码那样容易解析。它考验的是程序“看”和“认”的能力。今天我们就来深入聊聊如何用Selenium这套强大的浏览器自动化工具来“敲开”这道由图片验证码把守的门。简单来说这个项目的核心就是在Selenium驱动的自动化流程中当遇到需要输入图片验证码才能登录的网页时我们如何让程序自动或半自动地识别并填写这个验证码从而实现登录流程的完整自动化。这不仅仅是写几行代码点击和输入它涉及到验证码的定位、截图、预处理、识别以及结果回填等一系列环节。无论你是做自动化测试需要模拟真实用户登录来执行后续用例还是做数据采集需要登录后才能获取目标数据亦或是开发RPA流程自动化处理某些Web任务掌握这套方法都能让你的自动化脚本“功力大增”从只能处理简单表单升级到能应对更真实、更复杂的Web环境。2. 核心思路与方案选型识别验证码的“道”与“术”面对图片验证码我们的自动化脚本需要模拟人类用户的“眼睛”和“大脑”。整个处理流程可以抽象为四个核心步骤定位捕获 - 图像处理 - 识别解析 - 结果回填。但在具体实现上根据验证码的复杂度和项目要求我们有几种不同的“打法”。2.1 方案一人工半自动介入——稳定可靠的“保底”方案这是最直接、也最稳定的方法。当脚本运行到验证码出现时我们让程序暂停弹出验证码图片给人工查看由人工输入识别结果后脚本再继续执行。为什么首选这个方案因为对于绝大多数商业网站尤其是那些专门为防止自动化而设计的复杂验证码如扭曲字符、干扰线、背景噪点、点击图中特定物体等目前完全自动化的识别方案准确率无法达到100%。在自动化测试场景下一次识别失败就可能导致整个测试用例中断得不偿失。人工介入虽然牺牲了“全自动”但保证了流程的绝对可靠性和成功率。技术实现要点截图与保存使用Selenium定位到验证码图片元素然后将其截图保存到本地。交互暂停脚本通过input()函数或弹窗暂停并提示用户。人工输入用户查看本地图片在终端或弹窗中输入看到的验证码。流程继续脚本获取用户输入填入网页输入框并点击登录。这个方案的核心优势在于其普适性和零成本。它不依赖于任何额外的识别服务或模型对任何类型的图片验证码都有效。在项目初期或者对稳定性要求极高的生产环境中我强烈建议先采用此方案作为基础框架。2.2 方案二调用第三方OCR API——平衡效率与成本的“折中”方案当需要处理一定批量且验证码样式相对固定、复杂度中等时调用成熟的第三方OCR光学字符识别或专门的验证码识别API是一个不错的选择。常见服务商通用OCR服务如百度OCR、腾讯云OCR、阿里云OCR等。它们提供了高精度的文字识别能力对于清晰、规整的字符验证码效果很好。专业打码平台如超级鹰、图鉴等。这些平台背后有真人打码或专门训练的模型对于复杂、扭曲的验证码有更高的破解率通常按次计费。集成流程获取图片同方案一使用Selenium截图。图片预处理可选但重要对截图进行二值化、降噪、裁剪等处理可以显著提升识别API的准确率。例如使用Python的PIL或OpenCV库。from PIL import Image import io # 假设 element 是验证码图片的WebElement png_data element.screenshot_as_png image Image.open(io.BytesIO(png_data)) # 转换为灰度图 image image.convert(‘L’) # 二值化处理 threshold 150 image image.point(lambda p: p threshold and 255) # 保存预处理后的图片 image.save(‘captcha_processed.png’)调用API将处理后的图片或直接上传字节流发送给第三方API。解析结果获取API返回的识别文本。选择考量成本通用OCR可能有免费额度专业打码平台需要付费。速度API调用存在网络延迟对于要求毫秒级响应的场景需谨慎。合规性确保你的使用方式符合目标网站的服务条款和法律法规。2.3 方案三自建机器学习模型识别——高定制化的“终极”方案如果目标网站的验证码是自家设计的、风格独特且长期稳定并且有海量的验证码样本可供收集那么训练一个专属的识别模型可能是最一劳永逸的方案。技术栈通常涉及图像处理库OpenCV用于样本的预处理去噪、分割字符等。机器学习框架TensorFlow或PyTorch。模型选择对于字符验证码CNN卷积神经网络是主流选择。更复杂的验证码如点选、滑块可能需要目标检测模型如YOLO或更复杂的网络结构。实施步骤简述数据收集这是最大的难点。需要通过脚本大量访问登录页面保存验证码图片并人工或半自动地为其打上标签。数据预处理与增强清洗数据统一尺寸、格式并通过旋转、缩放、添加噪声等方式进行数据增强以提升模型的泛化能力。模型训练设计或选择网络结构使用标注好的数据进行训练。模型集成将训练好的模型封装成服务或直接嵌入Python脚本在Selenium流程中调用。这个方案投入最大周期最长但一旦成功识别速度最快本地调用、长期成本可能最低并且完全自主可控。它更适合有长期稳定需求且技术资源充足的团队。注意在实际项目中我通常会采用“方案一 方案二” 的混合策略。即默认尝试使用成本较低的OCR API进行自动识别如果识别失败或置信度太低则自动降级到方案一触发人工干预。这样既兼顾了效率又保证了流程的最终成功率。3. 基于Selenium的完整实现流程拆解无论选择上述哪种识别方案其前端与Selenium交互的部分是共通的。下面我们以最常见的“用户名/密码 图片验证码”登录场景为例拆解一个稳健的实现流程。3.1 环境准备与基础操作首先确保你的环境已经就绪。# 基础依赖安装 # pip install selenium Pillow opencv-python from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time, os启动浏览器并访问目标登录页。这里以Chrome为例使用显式等待是良好实践能有效避免因页面加载速度导致的元素定位失败。driver webdriver.Chrome() # 或使用 Firefox(), Edge()等 driver.get(‘https://example.com/login’) wait WebDriverWait(driver, 10) # 设置最大等待时间10秒3.2 验证码元素的定位与捕获这是最关键的一步。验证码图片的定位方式五花八门需要仔细分析页面HTML结构。常见定位方式及实战技巧通过img标签定位最理想的情况。验证码通常是一个 元素。# 假设验证码图片有id captcha_element wait.until(EC.presence_of_element_located((By.ID, “captchaImg”))) # 或者通过src属性包含特定关键字 # captcha_element driver.find_element(By.XPATH, “//img[contains(src, ‘captcha’)]”)作为背景图有时验证码是某个div的CSS背景图background-image。这时你需要定位这个容器div然后获取其style属性或计算样式中的背景图URL再通过requests库下载图片。这比截图更精确。div_element driver.find_element(By.CLASS_NAME, “captcha-bg”) style div_element.get_attribute(‘style’) # 从 style 字符串中解析出 url(‘…’) 部分通过相邻元素定位如果图片本身没有明显特征可以尝试通过其旁边的文字如“验证码”或输入框来间接定位。# 先找到验证码输入框再找它前面的img标签 input_box driver.find_element(By.NAME, “captcha”) captcha_element input_box.find_element(By.XPATH, “./preceding-sibling::img”)获取图片数据定位到元素后获取其截图。# 方法1对元素截图推荐能精准截取该元素区域 captcha_png_data captcha_element.screenshot_as_png # 返回二进制数据 with open(‘captcha.png’, ‘wb’) as f: f.write(captcha_png_data) # 方法2如果元素截图失败可以截全屏再裁剪备用方案 driver.save_screenshot(‘full_page.png’) # 需要获取元素的位置和大小信息进行裁剪 location captcha_element.location size captcha_element.size # 使用PIL进行裁剪3.3 识别环节的集成这里以“人工半自动”和“调用百度OCR API”为例展示如何将识别环节嵌入流程。方案一集成示例人工def manual_captcha_solver(image_path): “”“人工识别验证码”“” # 打开图片文件根据系统不同可能需要用其他方式 os.system(f‘start {image_path}’) # Windows # os.system(f‘open {image_path}’) # Mac # 等待用户输入 user_input input(“请输入图片 ‘{}’ 中的验证码”.format(image_path)) return user_input.strip() # 在流程中调用 captcha_code manual_captcha_solver(‘captcha.png’)方案二集成示例百度OCR你需要先去百度AI开放平台创建应用获取API Key和Secret Key。import requests, base64 def baidu_ocr_solver(image_path, api_key, secret_key): “”“使用百度通用OCR识别验证码”“” # 1. 获取Access Token auth_url f“https://aip.baidubce.com/oauth/2.0/token?grant_typeclient_credentialsclient_id{api_key}client_secret{secret_key}” auth_resp requests.get(auth_url).json() access_token auth_resp.get(‘access_token’) # 2. 读取并编码图片 with open(image_path, ‘rb’) as f: img_data base64.b64encode(f.read()).decode(‘utf-8’) # 3. 调用OCR接口 ocr_url f“https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token{access_token}” headers {‘Content-Type’: ‘application/x-www-form-urlencoded’} data {‘image’: img_data} ocr_resp requests.post(ocr_url, headersheaders, datadata).json() # 4. 解析结果 words_result ocr_resp.get(‘words_result’, []) if words_result: # 通常验证码是单行取第一个结果 return words_result[0].get(‘words’, ‘’).replace(‘ ‘, ‘’) return “” # 在流程中调用 api_key “your_api_key” secret_key “your_secret_key” captcha_code baidu_ocr_solver(‘captcha.png’, api_key, secret_key) if not captcha_code: print(“OCR识别失败转为人工识别”) captcha_code manual_captcha_solver(‘captcha.png’)3.4 结果回填与登录完成获取到验证码字符串后剩下的就是标准的Selenium操作了。# 1. 定位验证码输入框并输入 captcha_input wait.until(EC.element_to_be_clickable((By.ID, “captchaInput”))) captcha_input.clear() captcha_input.send_keys(captcha_code) # 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”) # 3. 点击登录按钮 login_button driver.find_element(By.XPATH, “//button[type‘submit’]”) login_button.click() # 4. 等待登录成功后的页面跳转或元素出现进行验证 try: wait.until(EC.url_contains(“dashboard”)) # 或等待某个登录后特有的元素 print(“登录成功”) except TimeoutException: print(“登录可能失败检查验证码或账号密码。”) # 这里可以加入重试逻辑例如刷新验证码重试4. 进阶技巧与实战避坑指南在实际项目中你会遇到比基础教程复杂得多的情况。下面分享一些我踩过坑后总结的进阶技巧。4.1 处理动态刷新与多次尝试很多网站的验证码在输入错误后不会自动刷新或者有“看不清换一张”的链接。应对策略封装刷新函数定位到刷新按钮或链接点击它然后需要重新等待新图片加载完成并重新截图。def refresh_captcha(driver): refresh_btn driver.find_element(By.LINK_TEXT, “换一张”) refresh_btn.click() time.sleep(1) # 等待新图片加载最好用显式等待检查图片src或元素属性变化 # 重新定位并截图 new_captcha_element driver.find_element(By.ID, “captchaImg”) return new_captcha_element.screenshot_as_png实现重试机制当登录失败可能是验证码错误时捕获异常刷新验证码重新识别然后重试登录。注意要设置最大重试次数避免死循环。max_retries 3 for attempt in range(max_retries): # … (执行截图、识别、填写、登录的代码) … if “登录成功”的判断条件: break else: print(f“登录失败第{attempt1}次重试…”) refresh_captcha(driver) # 清除旧的验证码输入 captcha_input.clear()4.2 应对Canvas渲染的验证码现代前端越来越多地使用HTML5 Canvas来绘制验证码这给截图带来了挑战。element.screenshot对Canvas元素可能无效或截取到空白。解决方案执行JavaScript获取数据URL通过Selenium执行JS调用Canvas的toDataURL()方法将其转换为Base64格式的图片数据。canvas_element driver.find_element(By.TAG_NAME, “canvas”) # 获取Canvas的base64图片数据 canvas_base64 driver.execute_script(“”” var canvas arguments[0]; return canvas.toDataURL(‘image/png’).substring(22); // 去掉 data:image/png;base64, 前缀 “””, canvas_element) # 解码并保存 import base64 png_data base64.b64decode(canvas_base64) with open(‘canvas_captcha.png’, ‘wb’) as f: f.write(png_data)全屏截图后裁剪作为备选方案如果JS方案不奏效可以截取整个浏览器窗口然后根据Canvas元素的位置和大小进行裁剪。但这种方法容易受页面滚动和布局影响精度较差。4.3 验证码识别后的校验与容错不要认为识别出来的字符串就一定是正确的。一个好的自动化脚本应该有校验机制。长度校验很多验证码是4-6位数字或字母。如果识别结果长度不符可以直接判定为识别失败触发重试或人工干预。格式校验有些验证码只包含数字或者不区分大小写。可以对识别结果进行简单的正则匹配过滤。置信度检查如果你使用的是提供置信度分数的OCR API如某些服务返回probability字段可以设定一个阈值如0.8低于阈值则视为识别质量不高主动重试。4.4 关于验证码破解的伦理与法律边界这是一个必须严肃对待的问题。我们的讨论仅限于学习自动化技术和测试自己拥有或有权测试的系统。切勿滥用不要用这些技术去攻击、爬取或干扰他人的网站和服务这很可能违反《计算机信息网络国际联网安全保护管理办法》等相关法律法规并构成对网站服务条款的违反。尊重robots.txt即使技术上可行也应尊重网站设置的爬虫协议。测试环境所有技术实践最好在本地搭建的测试环境、或公司内部的测试系统上进行。频率限制即使是测试也应避免高频请求以免对服务器造成不必要的压力。5. 架构设计与代码封装建议为了让你的验证码处理逻辑更清晰、更易复用我建议进行简单的分层封装。一个可参考的类结构class CaptchaHandler: def __init__(self, driver, locator_strategy, locator_value): self.driver driver self.locator (locator_strategy, locator_value) # 如 (By.ID, “captchaImg”) self.wait WebDriverWait(driver, 10) def capture(self): “”“定位并捕获验证码图片返回图片二进制数据”“” element self.wait.until(EC.presence_of_element_located(self.locator)) return element.screenshot_as_png def save_to_file(self, png_data, filepath): with open(filepath, ‘wb’) as f: f.write(png_data) return filepath def solve_with_ocr(self, image_path, ocr_config): “”“调用OCR服务识别”“” # … 集成OCR API逻辑 … pass def solve_manually(self, image_path): “”“人工识别”“” # … 弹出图片并等待输入的逻辑 … pass def solve(self, strategy‘auto’, fallback‘manual’, **kwargs): “”“主解决函数可配置策略和降级方案”“” png_data self.capture() temp_file self.save_to_file(png_data, ‘temp_captcha.png’) if strategy ‘ocr’: result self.solve_with_ocr(temp_file, kwargs.get(‘ocr_config’)) if not result and fallback ‘manual’: result self.solve_manually(temp_file) else: # default manual result self.solve_manually(temp_file) # 清理临时文件 os.remove(temp_file) return result # 使用示例 handler CaptchaHandler(driver, By.ID, “captchaImg”) code handler.solve(strategy‘ocr’, ocr_config{‘api_key’: ‘xxx’}, fallback‘manual’)这样的封装将验证码处理的所有细节定位、截图、识别隐藏在一个类中主登录流程会变得非常简洁。你可以轻松地在不同的识别策略之间切换也方便后续维护和扩展。6. 不同场景下的策略选择与实战心得最后结合我多年的经验给不同场景下的朋友一些直接的建议如果你是自动化测试工程师稳定性压倒一切。优先采用“人工半自动”方案或者“OCR API 人工降级”方案。在测试脚本中可以将验证码识别步骤单独模块化并通过环境变量控制使用哪种模式。在CI/CD流水线中可以配置一个“安全模式”强制使用人工输入避免因识别失败导致构建中断。如果你是数据采集开发者需要权衡成功率、成本与速度。对于数据量不大、频率不高的采集任务人工半自动可能就够了。对于需要批量处理的任务可以调研目标网站验证码的难度选择性价比高的第三方打码平台。切记遵守法律法规和网站协议并务必设置合理的请求间隔如time.sleep(random.uniform(2,5))做到友好爬取。如果你面对的是内部系统或老旧系统这些系统的验证码往往比较简单如纯数字、无干扰。可以尝试使用开源的OCR库如pytesseract即Tesseract的Python封装进行离线识别成本为零。但Tesseract对复杂验证码效果一般需要配合精细的预处理灰度化、二值化、去噪。import pytesseract from PIL import Image # … 预处理图片 … text pytesseract.image_to_string(processed_image, config‘--psm 8 --oem 3 -c tessedit_char_whitelist0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ’) # --psm 8 表示将图片视为单个单词--oem 3 使用默认OCR引擎-c tessedit_char_whitelist… 限制识别的字符集能大幅提升准确率。一个最重要的心得没有银弹。图片验证码技术本身也在不断进化滑动拼图、点选文字、空间推理等。本文介绍的方法主要针对传统的字符图片验证码。当遇到更复杂的交互式验证码时可能需要结合图像识别、轨迹模拟等多种技术甚至需要更深入的分析。自动化与反自动化是一场持续的博弈我们的工具箱也需要不断更新。