在这里插入图片描述

文章目录


学习目标

学完这一课,你将能够:

  1. 理解验证码的反爬原理——知道网站为什么要用验证码、它检测的是什么维度的“人机差异”
  2. 区分主流验证码类型——图形验证码、滑动验证码、点选验证码、无感验证码(reCAPTCHA v3/Turnstile),知道每种的大致成本和破解难度
  3. 掌握两种低成本绕过路线——本地OCR(ddddocr + pytesseract)和第三方打码平台集成(超级鹰等)
  4. 编写完整的识别-注入代码——将验证码图片从网页中提取、发送识别、结果回填到表单的完整闭环
  5. 建立重试与容错机制——识别失败时自动刷新验证码、更换代理、限制重试次数,防止程序假死
  6. 清晰认知合规边界——理解绕过技术措施的法律红线,知晓哪些验证码绝对不能碰,哪些场景风险极高
  7. 掌握商业级方案选型——知道大型项目的验证码处理架构(队列、负载均衡、多平台灾备)和成本预估

验证码是对抗反爬的最后一道关卡。这一课我们将学会用最低成本的方式,穿透这道防线。

本课特别提醒:验证码绕过存在明确的法律风险(详见第九章)。本文所有技术仅供学习研究,请勿用于侵犯他人合法权益的场合。你应当只在以下范围内使用本课技能:① 爬取自己拥有完全控制权的网站或服务器;② 获得对方书面授权的正规商业合作;③ 有明确法律依据的公开数据采集。

一、通俗原理:验证码是“人机识别的最后一道闸门”

1.1 为什么会有验证码?

你还记得第15课讲的反爬漏斗吗?验证码通常位于漏斗的最深层——前面几层(UA校验、Referer校验、频率限制、IP封禁)都没能拦住你的时候,网站才会祭出最终的“杀手锏”。

验证码的核心理念很简单:设计一个对人类来说相对容易、但对机器来说极其困难的任务。比如让人类识别扭曲的字母、找出图片中的红绿灯、把滑块拖到缺口处。

但是随着AI技术的进步,这个“对人类容易、对机器困难”的假设正在崩塌。事实上验证码的攻防已经进入了一个持续的拉锯战状态。

1.2 验证码的三大技术本质

从技术实现的角度,验证码本质上是在三个维度上构建“人机差异”:

维度 检测内容 真人特征 机器劣势
视觉识别 能否理解被扭曲/遮挡/干扰的视觉信息 人脑高度发达的模式识别能力 传统OCR对干扰线、粘连字符鲁棒性差
行为模式 操作是否有自然的时间分布和运动轨迹 随机停顿、中间抖动、非匀速移动 程序往往是匀速直线、无停顿、无误差
环境指纹 浏览器/设备是否具备真实人类的完整特征 Canvas指纹、WebGL指纹、字体列表等 无头浏览器或自动化工具的特征暴露

1.3 主流验证码类型一览

我们按“机器破解成本”从低到高排序:

类型 代表作品 破解成本 绕过难度
图形验证码(扭曲字符) 传统4-6位数字/字母 极低(ddddocr 5行代码)
数学/问答验证码 “3+4=?” 极低(正则匹配)
基础滑动拼图 拖动拼图至缺口 中等(模板匹配+轨迹模拟) ⭐⭐
点选验证码 “点击所有红绿灯” 较高(物体检测模型) ⭐⭐⭐
高级滑动验证码 极验3/4、腾讯防水墙 高(CNN+行为模拟) ⭐⭐⭐⭐
无感验证码 Google reCAPTCHA v3、Cloudflare Turnstile 极高(多重指纹分析) ⭐⭐⭐⭐⭐

重要结论:如果网站用的是高级滑动验证码(极验、腾讯)或无感验证码(reCAPTCHA v3、Cloudflare),本课的低成本方案基本无效,需要更专业的手段(逆向、高级模型训练、商业服务),这些内容超出本课范围。

1.4 验证码识别本质上解决一个什么问题?

对爬虫来说,验证码带来的挑战不是“能不能识别”,而是“识别成本”的问题。核心难点在于:

  • 验证码图片的位置:你需要先定位网页中验证码图片的URL或元素,然后下载它(下载时可能涉及前置Cookie、动态时间戳等,直接访问图片URL可能返回空白图)
  • 识别精度与时效平衡:识别过程不能太长(验证码会过期)
  • 结果回填与验证:识别出的文字/坐标需要插入正确的输入框或触发正确的点击事件,有时候还需要连带提交验证码ID

本课将从最简单的图形验证码入手,手把手教你搭建一个完整的识别闭环。

二、方案一:本地OCR识别(低成本、零网络依赖)

2.1 工具选型对比

工具 优势 劣势 适用场景
ddddocr 专为验证码训练,对扭曲、干扰线识别率高,纯Python(无需系统级安装),基础版仅20MB 不支持中文(有扩展版),模型固定无法调优 新手首选,绝大多数英文数字验证码
Tesseract 开源免费,支持100+语言 需要系统级安装,中文识别率一般,需大量预处理 印刷体验证码
EasyOCR 开箱即用,支持80+语言,深度学习模型 模型体积大(~2GB),推理速度慢 多语言验证码、快速原型
PaddleOCR 中文优化,超高精度 依赖PaddlePaddle生态,部署复杂 中文验证码、高精度需求

对于90%以上的英文数字混合验证码(4-6位字符、单纯扭曲、彩色背景、有干扰线),ddddocr 是最佳选择。

2.2 ddddocr 安装与基础使用

一条命令直接搞定安装:

pip install ddddocr

验证安装并测试最简单的识别:

import ddddocr

ocr = ddddocr.DdddOcr()

# 读取验证码图片(二进制模式)
with open('captcha.png', 'rb') as f:
    img_bytes = f.read()

result = ocr.classification(img_bytes)
print(f"识别结果: {result}")  # 输出类似:"8h7k"

就这么简单,classification 方法直接输出识别出的字符串。

ddddocr 支持三种常见验证码类型:

方法 用途 返回格式
classification(img_bytes) 图片文字识别 字符串,如 "8h7k"
slide_match(bg_bytes, slider_bytes) 滑块验证码缺口定位 {"target": [x, y], ...}
click_match(img_bytes, target) 点选验证码物体定位 [[x1,y1], [x2,y2], ...]

在常见的扭曲、含干扰线条件下,ddddocr 的基础模式准确率可稳定超过85%。如果你直接调用 classification 不够精准,可以启用检测模式来大幅提升抗干扰能力:

# 启用检测模式,语义自动帮你提高抗背景干扰的准确率
ocr = ddddocr.DdddOcr(det=True)
result = ocr.classification(img_bytes, det=True)
print(f"增强识别结果: {result}")

实测数据显示,在包含5条干扰线的验证码中,启用检测模式后准确率从72%提升至89%。

2.3 滑块验证码的缺口定位(ddddocr 内置)

对于简单的滑块验证码,ddddocrslide_match 可以直接返回缺口位置:

def get_slider_position(bg_img_path, slider_img_path):
    ocr = ddddocr.DdddOcr()
    with open(bg_img_path, 'rb') as f:
        bg_img = f.read()
    with open(slider_img_path, 'rb') as f:
        slider_img = f.read()
    
    result = ocr.slide_match(bg_img, slider_img)
    # 返回缺口左上角的x坐标(有时返回完整target坐标)
    target_x = result.get('target', [0])[0]
    return target_x

# 示例
x = get_slider_position('background.png', 'slider.png')
print(f"缺口X坐标: {x}")

这一步返回的是缺口的左上角x坐标,可以配合 Selenium 计算出实际需要移动的距离。

特别提醒:2025年后的主流滑动验证码通常叠加了轨迹特征校验和行为上下文校验,单纯靠 slide_match 获取位置之后用鼠标拖到底是不够的,必须模拟真人滑动轨迹(参见第六章“哪些验证码无法低成本绕过”一节)。

2.4 图像预处理增强识别率(OpenCV辅助)

ddddocr 直接识别率不够高时,可以用 OpenCV 给验证码图片先进行“降噪”处理:

import cv2
import ddddocr

def preprocess_and_recognize(image_path):
    # 读取图片并转为灰度图
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    
    # 二值化:把图片转换成纯黑白的字符和背景
    _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
    
    # 降噪:去除孤立的噪声点
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
    denoised = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    
    # 将处理后的图像保存为临时文件(ddddocr读取字节流)
    temp_path = 'processed.png'
    cv2.imwrite(temp_path, denoised)
    
    # 用ddddocr识别
    ocr = ddddocr.DdddOcr()
    with open(temp_path, 'rb') as f:
        result = ocr.classification(f.read())
    
    return result

code = preprocess_and_recognize('noisy_captcha.png')
print(f"预处理后识别结果: {code}")

2.5 实战:从网页中提取验证码图片(Selenium版)

Selenium 截图 + ddddocr 识别串联成完整流程:

import time
import base64
import ddddocr
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

def solve_captcha_from_page(driver, captcha_img_selector, input_selector, submit_selector, retry_times=3):
    """
    从页面中提取验证码 -> 识别 -> 填入 -> 提交
    captcha_img_selector: 验证码图片的CSS选择器
    input_selector: 验证码输入框的CSS选择器
    submit_selector: 提交按钮的CSS选择器
    """
    ocr = ddddocr.DdddOcr()
    
    for attempt in range(retry_times):
        try:
            # 等待验证码图片加载完成
            img_element = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, captcha_img_selector))
            )
            
            # 方案1: 截取img元素(推荐,最精准)
            # 如果想要更高精准度,也可以直接保存图片URL,但selenium截图更稳定
            img_path = f'captcha_{attempt}.png'
            img_element.screenshot(img_path)   # 截取元素截图
            
            # 方案2: 如果图片是动态src,可以使用requests直接取,但要保持同一个sessionid
            # 我们用更稳妥的截屏方式
            
            # 识别验证码
            with open(img_path, 'rb') as f:
                result = ocr.classification(f.read())
            
            print(f"第{attempt+1}次识别结果: {result}")
            
            # 填入验证码输入框
            input_box = driver.find_element(By.CSS_SELECTOR, input_selector)
            input_box.clear()
            input_box.send_keys(result)
            
            # 点击提交按钮
            driver.find_element(By.CSS_SELECTOR, submit_selector).click()
            
            # 等待页面响应,检查是否验证码错误(例如没有错误信息,或者页面跳转)
            time.sleep(2)
            # 注意:这里需要根据具体网站判断验证是否成功,如果页面中还有“验证码错误”提示,则继续循环
            if "验证码错误" not in driver.page_source:
                print("验证码识别成功,登录完成")
                return True
            else:
                print("验证码错误,刷新重试")
                # 刷新验证码图片(如有刷新按钮,或者重新请求图片URL)
                refresh_btn = driver.find_elements(By.CSS_SELECTOR, "img[onclick*='refresh']")
                if refresh_btn:
                    refresh_btn[0].click()
                time.sleep(1)
                continue
                
        except Exception as e:
            print(f"验证码处理失败: {e}")
            continue
    
    return False


# 使用示例(具体选择器请根据实际网站调整)
# driver = webdriver.Chrome()
# driver.get("https://example.com/login")
# solve_captcha_from_page(driver, "#captcha_img", "#captcha_input", "#submit_btn")

这个脚本已经实现了“截图→识别→填回→提交→错误重试”的完整业务闭环。要注意的是,img_element.screenshot() 是将指定 <img> 元素区域截图保存为 captcha_0.png,之后 ddddocr 直接从本地截图中读取字节流并识别——以图片二进制流形式传递给打码平台是最稳妥的方式,也是打码平台最推荐的格式。

三、方案二:第三方打码平台(低成本、高成功率)

3.1 什么时候应该用打码平台?

  • 本地OCR无法识别(中文字符、极复杂背景、粘连严重)
  • 滑块验证码、点选验证码
  • 需要高成功率(商业项目)
  • 识别速度要求高于开发时间(打码平台通常比本地模型更快训练部署)

3.2 打码平台的选型与成本

平台 图形验证码价格 滑块/点选价格 特点
超级鹰 ≈0.5分/次 ≈2分/次 国内主流、接口简单、API稳定覆盖
云打码 ≈1分/次 定制 老牌平台
打码兔 ≈1分/次 定制
CapMonster ≈0.3-1美元/千次 按次计费 国际平台,AI识别

常见费用参考

  • 4位纯数字验证码约 0.3-0.8分人民币/次
  • 4-6位英文数字混合约 0.5-1分/次
  • 滑块/点选验证码约 1-3分/次
  • 价格会随市场波动,请以各平台最新公告为准

参考对比:如果每月识别一万次,图形验证码的支出大约在 100 元(约0.01元/次)。对于中小项目成本可控。

3.3 超级鹰API接入(完整代码)

本节以国内最常用的 超级鹰(ChaoJiYing) 打码平台为例。

"""
超级鹰验证码识别模块
文档:http://www.chaojiying.com/api-14.html
"""
import requests
import hashlib
import base64
import time

class ChaoJiYingClient:
    def __init__(self, username, password, soft_id):
        self.username = username
        self.password = hashlib.md5(password.encode()).hexdigest()
        self.soft_id = soft_id
        self.base_url = "http://upload.chaojiying.net/Upload/Processing.php"
    
    def recognize(self, img_path, codetype=1902, timeout=30):
        """
        识别验证码
        :param img_path: 验证码图片路径
        :param codetype: 识别类型 1902=4位纯数字 1004=4位英文数字混合 等
        :param timeout: 超时秒数
        :return: 识别结果字符串
        """
        with open(img_path, 'rb') as f:
            img_data = f.read()
        
        files = {'userfile': ('captcha.png', img_data)}
        data = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
            'codetype': codetype,
        }
        
        response = requests.post(self.base_url, data=data, files=files, timeout=timeout)
        result = response.json()
        
        if result.get('err_no') == 0:
            return result.get('pic_str')
        else:
            raise Exception(f"识别失败: {result.get('err_no')} - {result.get('err_str')}")
    
    def report_error(self, pic_id):
        """识别错误时上报,平台返还该次扣分"""
        url = "http://upload.chaojiying.net/Upload/ReportError.php"
        data = {
            'user': self.username,
            'pass2': self.password,
            'id': pic_id,
        }
        response = requests.post(url, data=data)
        return response.json()


# 使用示例
if __name__ == "__main__":
    # 请从超级鹰官网获取你的注册参数
    client = ChaoJiYingClient(
        username='your_username',
        password='your_password',
        soft_id='123456'  # 软件ID
    )
    
    # 识别图形验证码
    start = time.time()
    result = client.recognize('captcha.png', codetype=1004)
    print(f"识别结果: {result}, 耗时: {time.time()-start:.2f}秒")
    
    # 识别滑块验证码缺口位置(需配合本地处理)
    # 滑块类型 codetype 取决于平台具体支持情况(通常是9101等)

3.4 接入打码平台的关键注意事项

根据业界经验,以下细节经常导致识别成功率低:

  • 图片文件名必须带后缀(.png/.jpg),否则部分平台解析失败
  • codetype 绝对不能填错:1004=4位英文数字混合,9004=中文,1902=4位纯数字;填错直接导致识别率骤降
  • 密码需要 MD5 加密pass2 = hashlib.md5(password.encode()).hexdigest(),不是明文传原始密码
  • 验证码图片必须先下载再传文件内容,直接传URL到打码平台容易被拒
  • 识别成功后,有些网站校验时还需要提交 pic_id(超级鹰返回的本次识别ID),要与验证码字符串一起提交给服务器,否则后端可能识别失败
  • 识别失败要上报:如果识别结果提交后服务器返回“验证码错误”,主动调用 report_error(pic_id),平台会返还该次扣分;长期不报告会导致账号信誉下降、成本增加

3.5 将打码平台集成到爬虫(完整闭环)

import time
import requests
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
from chaojiying import ChaoJiYingClient  # 假设上面代码已保存为chaojiying.py

def login_with_captcha(username, password, login_url, captcha_img_xpath, captcha_input_xpath):
    driver = webdriver.Chrome()
    driver.get(login_url)
    
    # 初始化打码平台客户端
    cjy = ChaoJiYingClient(
        username='YOUR_ACCOUNT',
        password='YOUR_PASSWORD',
        soft_id='YOUR_SOFT_ID'
    )
    
    for attempt in range(3):
        try:
            # 获取验证码图片元素
            img_element = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, captcha_img_xpath))
            )
            img_path = f"captcha_{attempt}.png"
            img_element.screenshot(img_path)
            
            # 调用打码平台识别
            captcha_text = cjy.recognize(img_path, codetype=1004)
            print(f"第{attempt+1}次 识别结果: {captcha_text}")
            
            # 填写表单
            driver.find_element(By.NAME, 'username').send_keys(username)
            driver.find_element(By.NAME, 'password').send_keys(password)
            driver.find_element(By.XPATH, captcha_input_xpath).send_keys(captcha_text)
            
            # 提交登录
            driver.find_element(By.XPATH, "//button[@type='submit']").click()
            time.sleep(2)
            
            # 验证登录是否成功(可用url或页面元素判断)
            if "登录成功" in driver.page_source or "dashboard" in driver.current_url:
                print("✅ 登录成功!")
                return driver
            else:
                print("❌ 验证码错误,重试...")
                # 刷新验证码(通常点击验证码图片刷新)
                img_element.click()
                time.sleep(1)
                continue
                
        except Exception as e:
            print(f"第{attempt+1}次尝试失败: {e}")
            continue
    
    print("登录失败,已达到最大重试次数")
    driver.quit()
    return None


# 使用
driver = login_with_captcha(
    username="myuser",
    password="mypass",
    login_url="https://example.com/login",
    captcha_img_xpath="//img[@id='captcha_img']",
    captcha_input_xpath="//input[@name='captcha']"
)

核心问题:你一定碰不到的隐藏坑:很多网站为了防止自动化,验证码图片的URL本身携带动态时间戳(/captcha?t=XXXXXX),如果用 requests.get(img_url) 直接请求可能拿不到真实图片 —— 但上面的代码用的是 img_element.screenshot(),从未分离地跟在当前的浏览器session后,不会遇到这类问题。因此教程中全部使用“截屏img元素”的方式,而非通过URL额外请求,这是新教程必须强调的最佳实践。

四、方案对比与选型决策

对比维度 本地OCR (ddddocr) 打码平台
图形验证码成功率 80%-95%(取决于难度) 95%-99%
滑块验证码支持 ✅ 可定位缺口(简单场景) ✅ 多数平台支持
成本 免费 0.5-3分/次
网络依赖 离线可用 需要联网
部署难度 简单(pip install) 简单(API调用)
汉字验证码能力 ❌ 识别弱 ✅ 支持
点选验证码支持 ❌ 不支持 ✅ 部分支持

决策矩阵

                     ┌─────────────┐
                     │  是什么类型  │
                     └──────┬──────┘
                            ↓
            ┌────────────────────────────────┐
            │                                │
        ┌───▼────┐                      ┌─────▼─────┐
        │图形验证码│                      │滑块/点选码│
        └────┬────┘                      └─────┬─────┘
             ↓                                   ↓
   ┌─────────────────┐                 ┌─────────────────┐
   │   准确率要求?   │                 │  强制生产环境?  │
   └────────┬────────┘                 └────────┬────────┘
            ↓                                     ↓
   ┌────────┴────────┐                   ┌───────┴────────┐
   ↓                 ↓                   ↓                ↓
┌────┐         ┌────────────┐        ┌────┐         ┌────────────┐
│低  │         │高(>95%)  │        │否  │         │是           │
└──┬─┘         └───────┬────┘        └──┬─┘         └───────┬────┘
   ↓                   ↓                 ↓                   ↓
┌─────┐          ┌──────────┐       ┌─────────┐        ┌──────────┐
│ddddocr│        │打码平台  │       │评估成本 │        │打码平台  │
└─────┘          └──────────┘       │+尝试组合│        └──────────┘
                                    └─────────┘

企业级方案:打码平台负载均衡架构

如果每天需要处理上万次验证码,需要引入以下设计:

  • 多打码平台并行:将请求分发到超级鹰、云打码、CapMonster 等多个平台,实现平台级容灾
  • 自动降级路由:主平台识别失败(或超时),自动切换到备用平台
  • 识别结果缓存:相同图片哈希值 5 分钟内置信较高的重复结果命中后,直接返回,节省成本
  • 识别失败自动重试队列:失败的验证码图片放入重试队列,延迟 3 秒、6 秒、12 秒重试,最多 3 次

核心伪代码:

captcha_platforms = [
    ChaoJiYingClient(...),   # 主平台
    YunDaMaClient(...),      # 备用1
    CapMonsterClient(...)    # 备用2
]

def solve_with_fallback(img_bytes, fallback_platforms):
    for idx, platform in enumerate(fallback_platforms):
        try:
            result = platform.recognize(img_bytes)
            if result:
                return result
        except Exception as e:
            log(f"{platform.__class__.__name__} 识别失败: {e}")
            continue
    raise Exception("All captcha platforms failed")

五、验证码处理的常见踩坑与解决方案

坑1:ddddocr 识别结果总差一位

原因:图片中的验证码包含大小写字母,且模型默认输出混合大小写,但服务器要求全大写下发了不一样的预期。

解决:用小写转换统一格式(.upper() / .lower()):

result = ocr.classification(img_bytes)
result_upper = result.upper()  # 强制转为大写

坑2:打码平台返回 ERROR 但本地图片正常

根本原因(API接入绝大部分都坑在这里):漏了前置请求(没种sessionid)直接去拿验证码图片,图片是动态生成的、服务器对不合法的访客返回 403 占位图。

  • 没有用 session 携带 Cookie
  • 图片 URL 含动态时间戳没有实时刷新

解决

# 必须复用 session 去请求图片
session = requests.Session()
session.get(login_url)  # 先访登录页,种下 sessionid
img_resp = session.get(captcha_img_url)    # 保持同一个会话去请求
# 再用 img_resp.content 传给打码平台

坑3:图片 filename 没有后缀

打码平台解析时认为文件格式无法确定,返回格式错误 9002。

不要直接传二进制数据用一个奇葩字符串作为文件名,一定要带 .png.jpg

# ❌ 错误
files = {'userfile': ('data', img_bytes)}
# ✅ 正确
files = {'userfile': ('captcha.png', img_bytes)}

坑4:滑块验证码滑动后总提示“网络不给力”或“验证码错误”

2025 年以后的滑块验证码不只是校验是否滑到缺口(基础位置校验),同时也在分析三个维度的行为特征:

  1. 轨迹特征:加速度、停顿、抖动
  2. 上下文行为:点滑块前的鼠标移动、点击位置偏差
  3. 设备指纹一致性

解决方案:缺口的精准识别 + 模拟真人轨迹(带加减速、随机抖动、随机停顿)必须一起上。通用策略:

import random

def generate_slider_track(distance):
    """
    模拟真人滑块的拖动轨迹(匀加速+匀减速模型)
    """
    track = []
    current = 0
    mid = distance * 4 / 5   # 前4/5加速,后1/5减速
    t = 0.2                  # 单位时间间隔
    v = 0
    
    while current < distance:
        if current < mid:
            a = random.uniform(2, 4)    # 加速度 2-4 模拟手指启动
        else:
            a = -random.uniform(3, 6)   # 减速度 3-6
        v0 = v
        v = v0 + a * t
        move = v0 * t + 0.5 * a * t * t
        if move < 0:
            break
        move = round(min(move, distance - current), 2)
        current += move
        track.append(move)
    # 补足剩余像素(防止未能准确到达距离)
    if current < distance:
        track.append(distance - current)
    return track

# 使用轨迹序列执行滑动(配合 Selenium ActionChains)
# for step in track:
#     action_chains.drag_and_drop_by_offset(slider, step, 0).perform()

轨迹的真实感测试:在代码中打印 track 数组,观察数据分布(先密集后稀疏,夹杂随机微调),大量固定步长轨迹会被服务器识别为机器行为。

坑5:验证码无限刷新,爬虫突然进入死循环

场景:登录页面验证码识别第一次失败后刷新,刷新了 10 次都失败,程序一直卡住。

解决:限制重试次数(retry 3次)+ 设置最大重试间隔,重试超过停止并抛出异常:

def safe_captcha_solve(max_retries=3):
    for attempt in range(max_retries):
        try:
            result = recognise_func()
            # 假设识别成功,填入并提交
            fill_and_submit(result)
            # 登录成功后跳出循环
            if login_success:
                return
        except Exception:
            # 刷新验证码
            refresh_captcha()
            time.sleep(1 + attempt * 0.5)   # 重试等待时间递增
    raise Exception("Max retries exceeded for captcha solving")

坑6:打码平台识别结果识别正确,网站却提示“验证码错误”,但人眼手动输入正确

原因:网站执行了“会话绑定”,验证码图片在生成时,服务器后端往内存里的 Session 中存下了本次图片对应的 ID(captcha_id)。你提交验证码时,必须一并将这个 captcha_id 提交上去,后端对不上的直接判定失败。

解决方案:抓包登录请求,发 POST 时除了验证码答案之外,带上一个隐藏字段(例如 captcha_idcaptcha_key),该字段必须从上一页面通过 BeautifulSoup 提取的动态值同步传入。

六、哪些验证码无法低成本绕过?

6.1 商业级(不可绕过)验证码列表

验证码类型 代表服务 为什么难 低成本方法有效吗
极验3/4 (Geetest) 淘宝、微博、B站 行为轨迹+深度指纹分析 ❌ 需CNN+轨迹模拟+逆向
Google reCAPTCHA v3 全球数百万网站 完全无感,不显示验证框,后台持续评分
阿里云验证码2.0 高德、菜鸟裹裹 AI驱动视觉+行为双重模型
Cloudflare Turnstile Cloudflare掩护的网站 切换为无感验证,纯环境指纹对抗
腾讯防水墙 微信、QQ空间 三因子风控模型
网易易盾 各大游戏官网 机器视觉模型+设备指纹

6.2 具体成本差异对比

  • ddddocr 成本:免费,安装 10 秒,推理 <0.1 秒
  • 打码平台单次成本:0.5~3 分人民币,适用图形验证码和部分滑块
  • 商业高精度模型(针对极验等) :需要大量的训练数据和工程师成本,普通项目完全烧不起

6.3 面对商业级验证码的调研思路

如果您遇到了商业级验证码,不要在本课的低成本方案上浪费时间。其绕过涉及三个方向:

  • 逆向其JS加密逻辑:找出签名生成函数,用Python重写后直接调用接口(需要前端逆向能力)
  • CNN + 轨迹模拟 + 设备指纹伪装:需要真金白银投入模型训练
  • 采用专业打码平台的人工/半人工识别服务:价格是普通图形验证码的10倍以上(按次收费)

关于极验、reCAPTCHA 的绕过方法较为敏感且技术细节远超本课范围,感兴趣可在学完本专栏并具备 JS 逆向能力后再做探索。

七、合规边界与法律风险(非常重要)

7.1 法律核心红线:避开或破坏技术措施即违法

《反不正当竞争法》第12条明确规定,不得利用技术手段妨碍其他经营者合法提供网络产品或服务。在爬虫情景下,这意味着通过破解技术手段绕过网站反爬机制(如验证码、IP限制)获取数据,或对目标系统造成实质性干扰(如高频请求导致服务崩溃),均已构成违法行为

7.2 入罪的边界——从行政处罚到刑事犯罪

若你绕过的验证码属于“平台的核心技术防护措施”,或你的爬取行为导致了服务器过载、数据泄露或商业模式被破坏,则除民事责任外还可能触及:

  • 《刑法》第285条:非法获取计算机信息系统数据罪
  • 《网络安全法》第44条:不得非法获取个人信息

行业曾有实际案例:某人绕过某平台登录验证码,批量爬取后台数据用于自己的商业分析,最终以非法获取计算机信息系统数据罪被判刑。虽然具体刑期高于此,但足以警醒:即便你只是“爬公开数据的非登录验证码”,只要你绕着走,就属于对抗技术措施,风险的尺度由被爬网站的损失与态度共同决定。

7.3 只有以下场景才是相对安全的

验证码技术措施只应在以下三种非常有限的场景中使用:

  • 爬取自己完全拥有的网站或服务器(没有对任何第三方产生侵入)
  • 获得对方书面授权的公开数据抓取,并且对方明确同意允许技术对抗
  • 学术研究和内部测试(但不得公开传播绕过方案)

7.4 合规的替代方案

与其绞尽脑汁绕过,不如主动融入平台规则:

  • 寻找官方API:先花时间研究平台的开发者文档(90%的平台有官方数据出口)。第12课的内容已经把如何找异步接口讲透。
  • 数据购买:对关键商业数据走付费授权渠道——这是任何公司都愿意接受的办法。
  • 只读非敏感数据 + 降低频率:不涉及用户隐私和核心业务数据,通过降速把验证码触发概率降到零。

如果需要绕过的是需登录的网站并绕过了图形验证码进行暴力撞库,会立刻触犯《网络安全法》第四十四条——这是强监管场景。

八、总结

本课核心知识清单

知识点 掌握程度
验证码类型与原理 能区分图形/滑块/点选的验证码,知道各自的技术本质
ddddocr 库使用 能独立处理4-6位字母数字混合验证码,定位简单滑块缺口
图像预处理(OpenCV) 能使用灰度化、二值化等提升低质量图片识别率
打码平台 API 接入 能完成超级鹰 SDK 集成,正确处理 codetype、Session复用、pic_id补充上报
验证码自动重试闭环 能从页面提取验证码→识别→提交→错误重试免人工干预
合规边界 清楚绕过“技术措施”的法律风险,能为自己的爬虫项目做书面合规评估
哪些验证码不能碰 能一眼识别极验、reCAPTCHA、腾讯等商业级验证码,判断是否需要投入高级人力

选型决策总结

  • 首选「无码」方案:低频率 + 绕开触发阈值,不触发才是最高效
  • 图形验证码:本地 ddddocr 免费(85-95%准确率)> 确认准确率难满足时再上打码平台(2分/次,98%准确率)
  • 简单滑块验证码ddddocr 缺口定位 + 手动构造轨迹(匀加速+抖动+停顿)【关键的成败点在于不能匀速滑动】
  • 点选/高级滑块/无感验证码:放弃解决,考虑是否一定要爬这个网站,或转向商业级高成本服务

九、课后作业

作业1:ddddocr 本地识别(必做)

从网上下载 10 张不同类型的图形验证码(4-6位英文数字混合,有轻微干扰线及无干扰线的各半),使用 ddddocr 识别,统计准确率。

提交要求

  • 附上图片样例和识别结果
  • 如果识别失败,尝试预处理(二值化、降噪),记录提升效果

作业2:滑块验证码缺口定位(必做)

找一个提供滑块验证码的测试网站(如 https://www.geetest.com/demo 演示版),用 Selenium 截图滑块和背景图,再用 ddddocr.slide_match() 获取缺口X坐标,计算出滑块需要移动的距离,并打印此距离。

作业3:超级鹰API集成(必做)

注册超级鹰账户并完成充值(最小金额体验),将本课中的 ChaoJiYingClient 类跑通,识别至少 5 张网页中自动出现的图形验证码。

作业4:登录验证码处理闭环(必做)

找一个公开的、带验证码的测试登录网站(开发自建测试站点或者如常见开源论坛演示站),用 Selenium 实现:

  1. 打开登录页面
  2. 定位验证码图片元素,截图传给 ddddocr(或打码平台)
  3. 识别验证码并填入
  4. 提交登录表单
  5. 循环重试最多 3 次

作业5:识别行为合规报告(必做)

选择一个你正在使用的爬虫项目(真实或假设),评估它所面对的验证码类型、触发频率以及在“商业级验证码”中的分类。写出 300 字的技术与法律合规角度分析,明确是否建议采用绕过方案。

结束语:验证码是反爬的终极防线,但绝非不可逾越。本课从成本最低的 ddddocr 方式切入,教你用极低的成本让绝大多数图形验证码不再成为障碍。但也希望你能深刻记住:绕过验证码的成本不只在技术上,更在法律和道德上。当你面对一条验证码时,技术问题解决了50%,剩下的50%是“该不该做”。

下一课,我们将学习爬虫框架 Scrapy——从“编写脚本”到“搭建生产级系统”,让你拥有大规模、分布式爬取的能力。

第18课见。


🔗《20节课精通网页爬虫》系列课程导航

GO


🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐