1. 项目概述:当UI自动化遇上验证码

做UI自动化测试或者数据采集的朋友,肯定都绕不开一个“老朋友”——验证码。无论是登录、注册还是关键操作,这个小小的图片或者滑块,往往就是自动化脚本的“终结者”。我最近在做一个电商后台的自动化巡检项目,就频繁被各种验证码卡住,从简单的数字字母,到复杂的滑块、点选,再到行为验证,真是让人头疼。所以,我花了些时间,系统地研究和实测了在Python Selenium框架下,解决验证码问题的几种主流思路。今天就把这四种亲测有效(且免费)的方法,结合我的踩坑经验,详细拆解给大家。无论你是做自动化测试还是爬虫,这篇文章应该都能给你提供一套清晰的“破局”思路。

这四种方法并不是孤立的,它们代表了四种不同的解决层级:从“绕开”(人工介入)到“合作”(第三方平台),再到“硬刚”(本地识别)和“终极规避”(环境与协议)。我会按照从易到难、从临时到稳定的顺序来讲解,并重点分析每种方法的适用场景、核心原理以及你肯定会遇到的实操细节。我们的目标是:让你的Selenium脚本在面对验证码时,不再是“一碰就死”,而是能根据实际情况,选择最合适的策略继续跑下去。

2. 方法一:半自动化策略——人工介入与Cookie复用

这是最直接、最省事,也往往是项目初期或低频任务中最实用的方法。它的核心思想是:在验证码出现的关键节点,让脚本暂停,等待人工识别并输入,然后脚本再接管后续流程。或者,更聪明一点,直接复用已经通过验证的登录状态(Cookie),避免再次触发验证。

2.1 人工手动输入验证码

这个方法听起来很“低级”,但在很多内部系统、测试环境或者验证码出现频率不高的场景下,性价比极高。你不需要研究任何识别算法,只需要让脚本“等一等”你。

核心实现步骤:

  1. 定位与截图 :当脚本运行到出现验证码的页面时,使用Selenium定位到验证码图片元素,然后将其截图保存到本地。

    from selenium import webdriver
    import time
    
    driver = webdriver.Chrome()
    driver.get("your_login_page_url")
    
    # 假设验证码图片的id是 ‘captcha_image’
    captcha_element = driver.find_element(By.ID, 'captcha_image')
    captcha_element.screenshot('captcha.png') # 截图保存
    

    这里有个关键点:有些网站的验证码是CSS Sprite(雪碧图)或者动态背景,直接对 img 元素截图可能不完整。更稳妥的做法是截取整个浏览器窗口的图,然后根据验证码元素的坐标进行裁剪。不过对于大多数情况,直接对元素截图够用了。

  2. 脚本等待与人工输入 :截图后,脚本暂停运行。我们在代码里弹出一个输入框,或者直接在控制台提示,让人工去查看刚保存的 captcha.png 图片,识别出验证码。

    # 方式1:使用内置input函数在控制台等待输入
    captcha_code = input("请查看当前目录下的captcha.png图片,并输入验证码: ")
    
    # 方式2:使用更友好的图形化输入(如tkinter),适合做成小工具
    # import tkinter as tk
    # from tkinter import simpledialog
    # root = tk.Tk()
    # root.withdraw() # 隐藏主窗口
    # captcha_code = simpledialog.askstring("验证码", "请输入验证码:")
    
  3. 回填与继续 :获取到人工输入的验证码后,脚本将其填入网页对应的输入框,并继续执行后续操作(如点击登录按钮)。

    driver.find_element(By.ID, 'captcha_input').send_keys(captcha_code)
    driver.find_element(By.ID, 'login_button').click()
    

实操心得与避坑指南:

注意 :这种方法的最大缺点是“非全自动”。但它有几个意想不到的优点:首先,100%准确,不用担心识别率问题;其次,对于复杂的、变化频繁的验证码(如点选成语、旋转图片),这是成本最低的解决方案;最后,它完全免费,不依赖任何外部API。

  • 超时处理 :一定要为 input() 或图形化输入设置一个超时机制。否则如果人工离开,脚本会永远卡住。可以用 threading 模块开一个计时线程,超时后自动填入一个错误码或终止脚本。
  • 路径问题 :确保截图保存的路径是脚本可写且人工方便查看的。可以考虑用绝对路径,或者弹窗时直接显示图片。
  • 适用场景 :非常适合 开发调试阶段 内部管理系统自动化 每日仅需执行数次 的定时任务。我最初在调试登录流程时,就全靠这个方法,快速打通了流程,后续再考虑优化。

2.2 Cookie复用大法

如果说手动输入是“临时通行证”,那Cookie复用就是“永久VIP卡”。一旦用户成功登录一次,浏览器就会获得一个包含会话信息的Cookie。我们只要把这个Cookie保存下来,并在新的Selenium会话中加载,就可以直接跳过登录(包括验证码)环节,访问需要登录态的页面。

核心实现步骤:

  1. 首次登录并保存Cookie :专门写一个脚本,用上述手动或任何方式完成一次成功的登录。登录成功后,获取并保存所有的Cookie到文件(如JSON格式)。

    # 登录成功后的操作
    import json
    import pickle # pickle也可以,但JSON更通用安全
    
    cookies = driver.get_cookies()
    with open('cookies.json', 'w') as f:
        json.dump(cookies, f)
    driver.quit()
    
  2. 新会话加载Cookie :在新的自动化脚本开始时,先访问目标网站的任意页面(通常是首页),然后加载之前保存的Cookie。

    driver.get("https://www.target-site.com") # 先访问域名下的页面
    time.sleep(2) # 稍等,确保页面加载
    
    with open('cookies.json', 'r') as f:
        cookies = json.load(f)
    for cookie in cookies:
        # 添加前可能需要删除‘expiry’字段,因为它可能是浮点数
        if 'expiry' in cookie:
            # 有时expiry字段会导致添加失败,可以尝试删除或转换
            cookie.pop('expiry')
        try:
            driver.add_cookie(cookie)
        except Exception as e:
            print(f"添加cookie失败: {cookie.get('name')}, 错误: {e}")
    # 刷新页面或跳转到需要登录的页面,此时应该已是登录状态
    driver.refresh()
    

实操心得与避坑指南:

  • Cookie的有效性 :Cookie是有生命周期的( expiry )。会话Cookie在浏览器关闭后失效,持久化Cookie可以存活较长时间。保存前检查一下Cookie的 expiry 时间。如果过期了,就需要重新登录获取。
  • 域名与路径匹配 driver.add_cookie(cookie) 要求你必须在当前浏览器会话的域名和路径与Cookie的 domain path 属性匹配或为其父级时才能添加成功。这就是为什么我们要先 driver.get 到目标网站根域下的一个页面。
  • 安全Cookie :如果Cookie被标记为 secure (仅HTTPS)或 httpOnly (禁止JS访问),Selenium仍然可以添加,但你需要确保当前使用的是HTTPS协议。
  • 适用场景 :这是做 数据采集 自动化测试 的利器。对于需要长期登录态的任务(如每日定时爬取个人中心数据、自动化巡检已登录状态的功能),只需定期(如每天或每周)更新一次Cookie即可,完美规避验证码。我在做电商价格监控时,就用这个方式维持了长达一个月的会话,无需再次登录。

3. 方法二:借力打力——集成第三方验证码识别平台

当人工介入不现实,Cookie又容易过期时,我们就需要真正的“自动识别”了。自己从头研发识别算法门槛太高,这时候,第三方验证码识别平台就是最佳选择。它们提供了成熟的API,你只需要把验证码图片发过去,就能收到识别结果。

3.1 平台选择与工作原理

国内比较知名的平台有“超级鹰”、“图鉴”、“联众”等,国外有2Captcha、Anti-Captcha。它们通常按识别次数收费,但有丰富的免费试用额度,对于个人和小规模项目完全够用。

工作原理很简单:

  1. 你的脚本截取验证码图片。
  2. 将图片(通常是Base64编码)和平台分配的 软件ID(softid) 密钥(key) 一起,通过HTTP POST请求发送到平台API。
  3. 平台的后台可能有真人打码员,也可能是训练好的AI模型,对图片进行识别。
  4. 平台将识别结果(一串字符)返回给你的脚本。
  5. 你的脚本将结果填入网页。

以某个平台为例的代码框架:

import requests
import base64
from selenium.webdriver.common.by import By

def recognize_captcha(image_path, username, password, soft_id, codetype):
    """
    调用第三方平台识别验证码
    :param image_path: 验证码图片路径
    :param username: 平台用户名
    :param password: 平台密码
    :param soft_id: 软件ID(在平台注册后获取)
    :param codetype: 验证码类型代码(如1004代表4位英文数字)
    :return: 识别结果字符串
    """
    with open(image_path, 'rb') as f:
        img_data = f.read()
    base64_data = base64.b64encode(img_data).decode('utf-8')

    data = {
        'user': username,
        'pass': password,
        'softid': soft_id,
        'codetype': codetype,
        'file_base64': base64_data
    }
    headers = {'Connection': 'Close'}
    try:
        resp = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=data, headers=headers)
        result = resp.json()
        if result['err_no'] == 0:
            return result['pic_str'] # 识别成功
        else:
            print(f"识别失败,错误码:{result['err_no']}, 错误信息:{result['err_str']}")
            return None
    except Exception as e:
        print(f"请求API失败: {e}")
        return None

# 在Selenium脚本中使用
captcha_element = driver.find_element(By.ID, 'captcha_image')
captcha_element.screenshot('temp_captcha.png')
code = recognize_captcha('temp_captcha.png', 'your_username', 'your_password', 'your_softid', 1004)
if code:
    driver.find_element(By.ID, 'captcha_input').send_keys(code)

3.2 集成要点与成本考量

实操心得与避坑指南:

  • 验证码类型(codetype) :这是调用API的关键参数。平台会将验证码分类(如4位数字、5位英文数字混合、算术题、滑块等)。选错类型会极大影响识别准确率甚至导致失败。一定要在平台的文档里找到最匹配的类型码。对于不确定的,可以先用平台的“查码”功能手动测试。
  • 错误处理与重试 :第三方API不是100%成功,也可能有网络波动。 必须 添加健全的错误处理和重试机制。如果识别失败或返回空,可以尝试重新截图、重新识别,或者降级到人工处理流程。
  • 成本与额度 :虽然说是“免费”,但平台通常只提供少量免费额度用于测试。正式使用前,要清楚其计价模式(如1元1000次)。对于高频使用的场景,需要计算成本。我的经验是,对于常规的数字字母验证码,第三方平台的识别成功率(>95%)和性价比,远高于自己折腾一个识别模型。
  • 滑块与点选验证码 :对于这类交互式验证码,平台通常提供更复杂的API。它们返回的可能是需要滑动的距离(像素值),或者需要点击的坐标序列。你需要用Selenium的 ActionChains 来模拟这些鼠标操作。这部分的代码会复杂一些,但思路相通:获取问题图片->调用API获取答案->执行对应动作。
  • 适用场景 :适合 需要全自动化、验证码复杂度中等、且有一定预算(或用量不大) 的项目。是平衡开发成本、识别准确率和自动化程度的最佳选择。我负责的自动化测试平台,对于预发环境的测试用例,就集成了此类服务,保证了用例的完全无人值守执行。

4. 方法三:自力更生——基于本地OCR库识别

如果你不想依赖外部服务,或者处理的验证码非常固定(比如某个内部系统自带的简单验证码),那么使用本地OCR(光学字符识别)库是一个可行的方案。Python生态里最著名的就是 Tesseract

4.1 Tesseract-OCR的安装与基础使用

Tesseract是一个由Google维护的开源OCR引擎。在Python中,我们可以通过 pytesseract 这个库来调用它。

部署步骤:

  1. 安装Tesseract引擎 :这是核心识别程序,需要单独安装。

    • Windows :从 GitHub releases 下载安装包,安装时记得勾选“安装中文语言包”如果需要的话。安装后,将安装目录(如 C:\Program Files\Tesseract-OCR )添加到系统PATH环境变量。
    • Mac brew install tesseract
    • Linux sudo apt install tesseract-ocr (Ubuntu/Debian)
  2. 安装Python包 pip install pytesseract pillow Pillow 是图像处理库,通常用于预处理图片。

  3. 基础识别代码

    import pytesseract
    from PIL import Image
    
    # 指定tesseract.exe的路径(如果没加PATH,Windows需要)
    # pytesseract.pytesseract.tesseract_cmd = r‘C:\Program Files\Tesseract-OCR\tesseract.exe’
    
    # 打开并识别图片
    image = Image.open('captcha.png')
    text = pytesseract.image_to_string(image, config='--psm 6 --oem 3')
    print(f"识别结果: {text}")
    

    这里的 config 参数很重要:

    • --psm 6 :假设图片为单个统一的文本块。对于验证码,常用 6 (统一区块)或 7 (单行文本)。
    • --oem 3 :使用默认的OCR引擎模式。

4.2 图像预处理:大幅提升识别率的关键

直接对原始的验证码图片使用Tesseract,识别率往往惨不忍睹。验证码为了防机器识别,通常会加入干扰线、噪点、扭曲、颜色变换等。因此, 图像预处理 是本地OCR方案的核心环节,其目的就是去除干扰,让文字更清晰。

常见的预处理手段及Pillow实现:

  1. 二值化 :将彩色或灰度图转为纯黑白,突出文字。
    from PIL import Image
    image = Image.open('captcha.png').convert('L') # 先转为灰度图
    # 简单阈值二值化
    threshold = 150
    image = image.point(lambda x: 255 if x > threshold else 0)
    
  2. 降噪 :去除孤立的像素点(椒盐噪声)。
    # 可以使用Pillow的滤波器,或者OpenCV(更强大)
    from PIL import ImageFilter
    image = image.filter(ImageFilter.MedianFilter(size=3)) # 中值滤波去噪
    
  3. 去干扰线 :对于单色细干扰线,可以通过形态学操作(腐蚀膨胀)去除,这通常需要 OpenCV ( cv2 )。
    import cv2
    import numpy as np
    img = cv2.imread('captcha.png', 0) # 灰度读取
    _, thresh = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY_INV) # 二值化,背景黑字白
    kernel = np.ones((2,2), np.uint8)
    img_erode = cv2.erode(thresh, kernel, iterations=1) # 腐蚀,让字变细,线断开
    img_dilate = cv2.dilate(img_erode, kernel, iterations=1) # 膨胀,让字恢复
    # 最后再反转回来
    result = cv2.bitwise_not(img_dilate)
    
  4. 字符分割 :对于字符粘连严重的验证码,可以先尝试分割成单个字符再识别,但这步难度很高。

实操心得与避坑指南:

注意 :本地OCR方案是一个“调参”的过程。没有一套参数能通吃所有验证码。你需要针对目标验证码的特点,反复试验预处理流程。

  • 识别率天花板 :对于设计精良的商业验证码(如谷歌的reCAPTCHA),本地OCR基本无能为力。它更适用于 自研的、简单的、固定的 验证码,比如一些老旧系统或内部工具。
  • 预处理是核心 :90%的工作量都在预处理上。建议先用图片编辑工具(如Photoshop、GIMP)手动调整,找到能让人眼清晰辨认的处理步骤,再用代码实现这个流程。
  • 使用 pytesseract.image_to_data :这个函数能返回每个识别字符的置信度、位置等信息。你可以设定一个置信度阈值(如 conf > 60 ),过滤掉低置信度的结果,提高准确率。
  • 考虑训练自定义字库 :如果验证码字体非常特殊且固定,可以尝试用 jTessBoxEditor 等工具收集样本,训练Tesseract专用的字库。这能极大提升对该特定验证码的识别率,但过程繁琐。
  • 适用场景 验证码样式简单固定、对识别率要求不是100%、且希望完全离线运行 的场景。例如,公司内部一个老旧的CRM系统,其验证码多年未变,就非常适合用此方法。我曾用“灰度化->二值化->中值滤波”三步预处理,将一个内部系统的验证码识别率从不到20%提升到了85%以上。

5. 方法四:釜底抽薪——规避验证码触发机制

最高级的“解决”问题,是让问题根本不发生。有些验证码的触发是有条件的,我们可以通过技术手段,让自己看起来更像一个“正常用户”,从而避免被系统要求输入验证码。

5.1 理解验证码的触发逻辑

网站通常在以下情况会弹出验证码:

  1. 高频访问 :短时间内来自同一IP或同一会话的请求过多。
  2. 非人类行为模式 :鼠标移动轨迹是直线、点击速度恒定且极快、没有页面停留时间等。
  3. 浏览器指纹异常 :使用无头浏览器(Headless)、缺失常见浏览器特征(如WebGL、字体、插件列表等)。
  4. Cookie或本地存储状态 :没有携带正常的Cookie,或localStorage/sessionStorage状态异常。

5.2 Selenium的“拟人化”配置策略

我们的目标就是针对以上几点,对Selenium驱动的浏览器进行伪装。

  1. 禁用自动化控制标志 :这是最基本的一步。Chrome和Edge浏览器会通过 navigator.webdriver 属性暴露自己被自动化控制。我们需要在启动时添加参数来隐藏它。

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    
    options = Options()
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    
    driver = webdriver.Chrome(options=options)
    
    # 此外,还可以执行CDP命令来覆盖navigator.webdriver属性(更彻底)
    driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
        'source': '''
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            });
        '''
    })
    
  2. 模拟真人行为 :使用 ActionChains 来模拟人类的鼠标移动、点击、滚动等行为,加入随机延迟和曲线轨迹。

    from selenium.webdriver.common.action_chains import ActionChains
    import random
    import time
    
    element = driver.find_element(By.ID, 'some_button')
    # 不直接click,而是移动过去再点
    actions = ActionChains(driver)
    actions.move_to_element(element).pause(random.uniform(0.2, 0.5)).click().perform()
    
    # 模拟滚动
    driver.execute_script("window.scrollBy(0, 500);")
    time.sleep(random.uniform(1, 2))
    
  3. 完善浏览器指纹 :无头模式( --headless )很容易被检测。如果非要用,需要添加更多参数来模拟完整浏览器。更好的做法是使用非无头模式,但将其窗口最小化或移动到屏幕外。

    options.add_argument("--window-size=1920,1080")
    # 添加用户代理
    options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...")
    # 禁用自动化扩展,但启用其他常见扩展的模拟(复杂,需谨慎)
    
  4. 使用高质量代理IP池 :这是解决因IP高频访问而触发验证码的根本方法。通过轮换不同的IP地址,将请求分散开来。你可以在Selenium中为每个WebDriver实例配置不同的代理。

    proxy = "123.45.67.89:8080"
    options.add_argument(f'--proxy-server=http://{proxy}')
    

    注意,免费代理大多不稳定,商业代理IP池是一笔成本。

  5. 妥善管理Cookie和本地存储 :像方法二提到的那样,尽量复用登录态。同时,可以尝试在启动浏览器时,加载一个包含正常历史记录、Cookie的“用户数据目录”(User Data Dir),让你的浏览器看起来像一个长期使用的真实浏览器。

    options.add_argument(r"user-data-dir=C:\Users\YourName\AppData\Local\Google\Chrome\User Data\TestProfile")
    

实操心得与避坑指南:

  • 道高一尺,魔高一丈 :反爬与反反爬是持续对抗。上述技巧可能今天有效,明天就失效。尤其是大型网站(如各大电商、社交平台),他们的验证码和风控系统非常复杂,单靠Selenium伪装很难完全规避。
  • 成本与复杂度 :维护代理IP池、管理多个浏览器指纹配置文件,会显著增加系统的复杂度和运维成本。
  • 检测与反检测 :有些网站会集成像 distil PerimeterX 这样的专业反爬服务,它们能检测非常细微的浏览器环境差异。对抗这些服务,通常需要更底层的工具,如 Playwright Puppeteer 配合更高级的指纹伪装,甚至考虑使用浏览器自动化框架的“隐身”模式。
  • 适用场景 :适用于 风控不那么严格的中小型网站 ,或者作为其他方法的 辅助手段 。例如,在调用第三方识别平台前,先通过行为模拟和IP轮换,尝试降低触发验证码的频率,从而节省识别费用。在我的爬虫项目中,通常会将方法四作为基础配置,再结合方法二(第三方平台)作为后备方案,形成一个混合策略。

6. 方案选型与混合策略实战

面对一个具体的项目,我们该如何选择?没有银弹,只有最适合的权衡。下面我提供一个决策思路和混合策略的实战案例。

6.1 四象限决策法

我们可以根据两个维度来决策: 验证码的复杂程度 自动化任务的执行频率

执行频率 / 复杂程度 简单(数字、字母) 复杂(滑块、点选、行为)
低频(< 10次/天) 方法一(人工/Cookie) :成本最低,实现最快。 方法一(人工) :对于复杂码,人工识别准确率最高,成本可控。
高频(>= 10次/天) 方法三(本地OCR)或 方法二(第三方) :如果样式固定,优先本地OCR;否则用第三方平台,计算成本。 方法二(第三方平台) :这是最务实的选择。自己开发识别模型投入产出比太低。

风控强度 是第三个隐藏维度:对于风控极强的网站(如大型电商、社交平台),上述所有方法都可能失效,可能需要结合 方法四(深度伪装) 甚至探索更底层的接口请求方式(这已超出纯UI自动化范畴)。

6.2 混合策略实战:一个自动登录脚本的例子

假设我们要自动化登录一个论坛,它有时会出现4位数字字母混合验证码,且同一IP短时多次登录会触发。

我们的策略可以是:

  1. 首选 :尝试使用之前保存的Cookie直接登录(方法二复用)。
  2. 备选 :如果Cookie失效,则进入正常登录流程。在遇到验证码时: a. 尝试本地OCR识别(方法三) :因为该论坛验证码样式固定,我们已调好预处理参数。 b. 本地识别失败则调用第三方平台(方法二) :作为降级方案。 c. 第三方平台也失败则暂停并报警,转为人工处理(方法一) :作为最终保障。
  3. 全程辅助 :在脚本中启用随机延迟、模拟人类行为(方法四),并考虑在部署时使用代理IP轮换,以降低触发验证码的概率。

代码结构示意:

import json
import time
from selenium import webdriver
from recognize_local import local_ocr # 假设封装好的本地识别函数
from recognize_third_party import third_party_ocr # 假设封装好的第三方识别函数

def login_with_cookie(driver):
    """尝试Cookie登录"""
    try:
        driver.get("https://bbs.example.com")
        with open('forum_cookies.json', 'r') as f:
            cookies = json.load(f)
        for cookie in cookies:
            driver.add_cookie(cookie)
        driver.refresh()
        # 检查是否登录成功,例如查找用户头像元素
        if driver.find_elements(By.CLASS_NAME, "user-avatar"):
            print("Cookie登录成功!")
            return True
    except:
        pass
    return False

def handle_captcha(driver):
    """处理验证码,混合策略"""
    # 1. 截图
    captcha_element = driver.find_element(By.ID, 'captcha_img')
    captcha_path = 'temp_cap.png'
    captcha_element.screenshot(captcha_path)

    # 2. 优先本地识别
    code = local_ocr(captcha_path)
    if code and len(code) == 4: # 假设验证码是4位
        print(f"本地识别结果: {code}")
        return code

    # 3. 本地失败,降级到第三方平台
    print("本地识别失败,尝试第三方平台...")
    code = third_party_ocr(captcha_path, codetype=1004) # 1004代表4位英文数字
    if code:
        print(f"第三方平台识别结果: {code}")
        return code

    # 4. 全部失败,抛出异常或转为人工
    raise Exception("验证码识别失败,请手动处理。")

# 主流程
driver = webdriver.Chrome(options=get_stealth_options()) # 获取伪装后的options

if not login_with_cookie(driver):
    print("Cookie失效,开始常规登录流程...")
    driver.get("https://bbs.example.com/login")
    # ... 填写用户名密码
    username_input = driver.find_element(By.ID, 'username')
    password_input = driver.find_element(By.ID, 'password')
    username_input.send_keys("your_username")
    # 模拟人类输入间隔
    time.sleep(random.uniform(0.3, 0.7))
    password_input.send_keys("your_password")

    # 处理验证码
    try:
        captcha_code = handle_captcha(driver)
        driver.find_element(By.ID, 'captcha_input').send_keys(captcha_code)
        time.sleep(random.uniform(0.5, 1.5)) # 随机等待
        driver.find_element(By.ID, 'login_btn').click()
    except Exception as e:
        print(e)
        # 这里可以触发邮件/钉钉报警,或者暂停脚本等待人工介入
        input("验证码识别失败,请手动登录后按回车继续...")

这种混合策略,既保证了高成功率,又兼顾了成本和自动化程度,是工业级项目中常用的设计模式。

7. 常见问题与排查技巧实录

在实际操作中,你会遇到各种各样稀奇古怪的问题。这里我记录了几个最典型的问题和我的解决思路。

7.1 验证码图片无法截图或截图空白

  • 问题现象 element.screenshot() 保存的图片是空白、纯色或者不完整的。
  • 可能原因与排查
    1. 元素未完全加载/不可见 :截图前确保元素已在视口中并且可见。可以尝试滚动到元素位置,或等待更长时间。
      from selenium.webdriver.common.by import By
      from selenium.webdriver.support.ui import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
      
      element = WebDriverWait(driver, 10).until(
          EC.visibility_of_element_located((By.ID, "captcha_image"))
      )
      driver.execute_script("arguments[0].scrollIntoView(true);", element)
      time.sleep(0.5) # 等待滚动稳定
      
    2. Canvas或SVG验证码 :现代验证码可能使用 <canvas> <svg> 绘制,直接对元素截图无效。此时需要截取整个页面或Canvas所在的父容器,然后根据其坐标进行裁剪。更复杂的情况可能需要通过执行JavaScript来获取Canvas的图像数据。
    3. 动态背景/拼图验证码 :这类验证码通常由多张图片叠加而成。你需要识别出前景(验证码文字或滑块)所在的图层进行截图。可能需要分析网页CSS和DOM结构,找到正确的元素。

7.2 第三方平台识别准确率突然下降

  • 问题现象 :之前一直好用的平台API,突然返回的识别结果错误百出。
  • 排查步骤
    1. 检查图片质量 :首先确认你上传的图片是否清晰、完整。可能是网站更新了验证码样式,导致你的截图逻辑出了问题。
    2. 核对验证码类型(codetype) :验证码类型是否选对?去平台后台查看最近的识别记录,看错误码是什么。有时平台会新增类型。
    3. 查看平台状态与公告 :平台可能在维护,或者你的账户余额不足、调用频率超限。
    4. 样本测试 :手动在平台的“测试识别”功能里上传几张当前的问题图片,看结果如何。如果平台手动测试都识别不准,说明该验证码已升级,平台模型需要时间更新。此时你可能需要暂时切换为人工处理,或者寻找其他平台。

7.3 本地OCR(Tesseract)识别结果乱码或为空

  • 问题现象 pytesseract 返回一堆乱码符号,或者空字符串。
  • 排查与解决
    1. 语言包 :确保安装了正确的语言包。对于纯英文数字验证码, eng 包就够了。如果需要中文,要安装 chi_sim 等。在代码中指定语言: pytesseract.image_to_string(img, lang='eng')
    2. 图片模式 :Tesseract对输入图片的格式有要求。确保传递给它的 PIL.Image 对象是 RGB L (灰度)模式,而不是 RGBA (带透明度)。可以用 image.convert('RGB') 转换。
    3. 预处理不足 :这是最常见的原因。尝试以下步骤:
      • 将图片显示出来,看看人眼是否容易识别。
      • 增加二值化的对比度。
      • 尝试不同的滤波器和形态学操作去除噪点。
      • 如果字符粘连,尝试调整 --psm 参数,或者进行字符分割。
    4. 使用 image_to_data 调试 :输出识别数据的详细信息,看看置信度如何,字符框的位置是否正确。这能帮你判断是预处理没做好,还是Tesseract根本找不到文字区域。

7.4 行为模拟后仍然被检测到

  • 问题现象 :已经添加了各种反检测参数,并模拟了鼠标移动,但网站仍然弹出验证码或直接封锁。
  • 升级对抗思路
    1. 检查WebDriver属性 :在浏览器控制台输入 navigator.webdriver ,看是否返回 undefined 。如果返回 true ,说明你的CDP命令或启动参数未生效。
    2. 使用更隐蔽的驱动 :考虑使用 undetected-chromedriver 这样的库,它专门为绕过检测做了更多工作。
    3. 指纹深度伪装 :检查其他浏览器指纹,如 plugins , languages , hardwareConcurrency 等。可以使用 selenium-stealth 这类库,或者手动通过CDP命令覆盖更多属性。
    4. 放弃纯UI自动化 :对于顶尖的反爬系统(如Cloudflare 5秒盾、Distil Networks),纯Selenium可能力不从心。此时需要考虑:
      • 逆向工程 :分析网站登录或获取数据的真实API接口,直接发送HTTP请求。这需要抓包和分析JS代码。
      • 使用Playwright/Puppeteer :它们提供比Selenium更底层的控制,能更好地模拟真实浏览器环境。
      • 终极方案 :在可控环境下,运行带有完整图形界面的浏览器,通过VNC远程控制,但这牺牲了性能和可扩展性。

验证码对抗是一个持续的过程,没有一劳永逸的方案。最关键的思路是: 明确你的需求和成本边界,选择最适合当前场景的技术组合,并准备好当一种方法失效时的备用方案(Fallback) 。保持代码的灵活性和可维护性,比追求一个“万能”的破解方法更重要。

更多推荐