← 返回网络爬虫目录
← 返回学习笔记首页
验证码识别基础
网络爬虫专题 · 掌握验证码识别处理技术
专题: Python网络爬虫系统学习
关键词: Python, 网络爬虫, 验证码识别, OCR, Tesseract, 滑块验证码, 打码平台, CNN, OpenCV
一、验证码概述
验证码(CAPTCHA,全称Completely Automated Public Turing test to tell Computers and Humans Apart)是一种区分人类用户和计算机程序的自动安全测试机制。它的核心理念是设计一种对人类相对容易但计算机难以自动完成的任务,从而有效阻止恶意机器程序的自动化攻击。
在网络爬虫开发中,验证码是最常见的反爬手段之一。当爬虫访问频率过高、行为模式异常或触发某些敏感操作时,网站往往会弹出验证码进行拦截。理解验证码的种类和原理,是突破这道防线的第一步。
常见验证码类型
字符验证码
字符验证码是最传统也是最广泛使用的验证码类型。它由数字、字母(大小写)或中文组成,通常会加入噪点、干扰线、扭曲变形、背景色干扰等手段增加识别难度。早期的简单字符验证码使用清晰的字体和固定的颜色,容易被OCR工具直接识别。后期的字符验证码则通过复杂的扭曲、粘连、遮挡和随机背景来对抗自动化识别。
字符验证码又可以分为纯数字验证码(如银行交易验证码)、数字字母混合验证码(如常见的登录验证码)、中文验证码(用于防止境外自动化攻击)以及算术验证码(如"3+5=?",需要计算并输入结果)。
图形验证码
图形验证码利用人类对图像内容的识别优势来区分机器。常见的类型包括:滑块验证码(拖动滑块拼合缺口图片)、拼图验证码(将打散的图片碎片重新拼合)、点选验证码(按提示点击图片中的特定物体,如"请点击图片中的红绿灯")以及选图验证码(从多张图中选出符合要求的图片)。
行为验证码
行为验证码(无感验证)是近年兴起的高级验证方式。它在后台分析用户的鼠标移动轨迹、点击行为、页面浏览行为、浏览器指纹等特征,综合判断操作主体是否为人类。代表性的产品有极验(Geetest)验证码、腾讯防水墙、阿里云验证码等。行为验证码对用户体验影响最小,用户通常只需一次点击即可通过验证,但其背后复杂的风险分析引擎对爬虫构成了很大的挑战。
语音验证码和短信验证码
语音验证码将验证信息通过语音电话播报给用户,主要用于无障碍访问场景。短信验证码向用户手机发送包含验证码的短信,常用于高安全等级的操作(如支付确认、密码找回)。这两种验证码都需要额外的通信渠道,安全等级最高,但在常规爬虫场景中较少遇到。
验证码的安全等级
按照识别难度,验证码可以划分为四个安全等级:低安全等级验证码(纯数字、无干扰、字体固定,可直接用OCR识别);中安全等级验证码(数字字母混合、轻度扭曲和噪点,需要图像预处理);高安全等级验证码(重度扭曲、粘连严重、中文验证码或复杂图形验证码,需要深度学习模型);极高安全等级验证码(行为验证码、短信/语音验证码,需要模拟人类行为或第三方平台)。
二、简单字符验证码识别
对于安全等级较低的字符验证码,使用开源的OCR工具即可取得不错的识别效果。其中最常用的工具是Tesseract OCR,它是由HP实验室开发、目前由Google维护的开源OCR引擎,支持100多种语言的文字识别。
Tesseract OCR的安装与配置
在Python中使用Tesseract主要有两个库:pytesseract(Tesseract的Python封装,需要系统已安装Tesseract引擎)和tesserocr(Tesseract的C API的Python绑定,安装稍复杂但性能更好)。在Windows系统上,需要先下载安装Tesseract OCR引擎(可在GitHub的tesseract-ocr/tesseract仓库找到Windows安装包),然后在Python中通过pytesseract调用。
# 安装依赖
pip install pytesseract pillow
# 基础用法示例
import pytesseract
from PIL import Image
# 打开验证码图片
image = Image.open('captcha.png')
# 直接识别(适用于清晰无干扰的验证码)
text = pytesseract.image_to_string(image, lang='eng')
print(f'识别结果: {text.strip()}')
图像预处理提升识别率
大多数实际场景中的验证码不会那么"友好",通常需要经过一系列图像预处理步骤来提升OCR识别准确率。预处理的核心思路是尽可能去除干扰、突出字符主体、降低图像噪声。
import pytesseract
from PIL import Image, ImageFilter, ImageEnhance
def preprocess_captcha(image_path):
"""验证码图像预处理流水线"""
# 1. 读取图片
img = Image.open(image_path)
# 2. 灰度化(将彩色图像转为灰度图,减少颜色干扰)
img = img.convert('L')
# 3. 二值化(将灰度图转为纯黑白图像)
threshold = 128 # 阈值可调
img = img.point(lambda x: 255 if x > threshold else 0, '1')
# 4. 降噪(应用中值滤波去除孤立的噪点)
img = img.filter(ImageFilter.MedianFilter(size=3))
# 5. 去除干扰线(如果干扰线颜色或宽度有规律,可定制处理逻辑)
return img
# 预处理后识别
processed = preprocess_captcha('captcha.png')
text = pytesseract.image_to_string(processed, lang='eng',
config='--psm 7 --oem 3')
print(f'预处理后识别结果: {text.strip()}')
在Tesseract的config参数中,--psm(Page Segmentation Mode)指定页面分割模式,常用的有:psm 6(将图像视为一个统一的文本块)、psm 7(将图像视为单行文本)和psm 8(将图像视为单个单词)。对于验证码这类短文本,psm 7或psm 8通常效果更好。--oem(OCR Engine Mode)指定OCR引擎模式,oem 3表示使用LSTM神经网络引擎(默认推荐)。
字符分割技术
对于字符之间粘连严重的验证码,整图识别往往效果不佳。此时可以采用字符分割+单字符识别的方法:先通过投影法(垂直投影统计每一列的黑色像素数,根据波谷位置切分)或连通域分析(查找图像中的连通组件,每个组件作为一个字符)将验证码分割为单个字符图像,再分别对每个字符进行识别。字符分割的质量直接影响后续识别的准确率,是验证码识别中的关键技术环节。
import numpy as np
from PIL import Image
def vertical_projection_split(img):
"""基于垂直投影的字符分割"""
# 转换为numpy数组
arr = np.array(img)
# 如果图像是二值化的,黑色为0,白色为255
# 计算每一列的黑色像素数
height, width = arr.shape
proj = [height - np.count_nonzero(arr[:, col]) for col in range(width)]
# 找到字符的起始和结束位置(投影值为0表示空白列)
in_char = False
char_ranges = []
for col, val in enumerate(proj):
if val > 0 and not in_char:
start = col
in_char = True
elif val == 0 and in_char:
end = col
char_ranges.append((start, end))
in_char = False
if in_char:
char_ranges.append((start, width - 1))
# 分割每个字符
chars = []
for start, end in char_ranges:
if end - start > 2: # 过滤掉过窄的噪声列
char_img = img.crop((start, 0, end, height))
chars.append(char_img)
return chars
关键提示: 字符分割的质量直接影响识别准确率。对于严重粘连的验证码,垂直投影法可能无法正确分割,可以尝试使用水滴算法(Drop Fall Algorithm)或基于深度学习的端到端识别方法(如CRNN+CTC)。
三、图像处理技术(Pillow)
Pillow(PIL的分支)是Python中最常用的图像处理库,在验证码识别预处理中扮演着核心角色。掌握Pillow的图像处理技巧,可以在不依赖深度学习的情况下大幅提升OCR识别准确率。
图像读取与转换
Image.open()用于读取各种格式的图片。convert('L')方法将彩色图像转换为8位灰度图像(每个像素0-255),这是大多数图像预处理的第一步。灰度化可以消除颜色干扰、降低数据维度、减少计算量。
from PIL import Image
# 读取图片
img = Image.open('sample_captcha.png')
# 灰度化
gray = img.convert('L')
# 查看图像基本信息
print(f'图片格式: {img.format}')
print(f'图片尺寸: {img.size}')
print(f'图片模式: {img.mode}')
二值化处理
二值化将灰度图像转为纯黑白图像(像素值只有0和255)。阈值的选择非常关键:阈值太高会丢失字符笔画,阈值太低会保留过多噪点。常用的二值化策略包括:固定阈值法(适用于图像光照均匀的场景)、Otsu大津算法(自动计算最优阈值,适用于双峰直方图)和自适应阈值法(不同区域使用不同阈值,适用于光照不均匀的场景)。
import numpy as np
from PIL import Image
def binarize_otsu(image):
"""使用Otsu算法自动阈值二值化"""
# 转换为numpy数组
arr = np.array(image.convert('L'))
# 计算直方图
hist, bins = np.histogram(arr, bins=256, range=(0, 256))
# Otsu算法:最大化类间方差
total = arr.size
sum_total = np.sum(hist * np.arange(256))
sum_b = 0
w_b = 0
var_max = 0
threshold = 0
for t in range(256):
w_b += hist[t]
if w_b == 0:
continue
w_f = total - w_b
if w_f == 0:
break
sum_b += t * hist[t]
mean_b = sum_b / w_b
mean_f = (sum_total - sum_b) / w_f
var_between = w_b * w_f * (mean_b - mean_f) ** 2
if var_between > var_max:
var_max = var_between
threshold = t
# 二值化
result = arr > threshold
return Image.fromarray((result * 255).astype(np.uint8))
降噪与图像增强
Pillow提供了多种图像滤波器和增强工具。中值滤波(MedianFilter)在去除椒盐噪声的同时能较好保留字符边缘,是验证码降噪的首选。除此之外,还可以使用高斯模糊(GaussianBlur)去除高频噪声,或使用模式滤波(ModeFilter)去除孤立的噪点像素。
图像增强方面,ImageEnhance模块提供了对比度(Contrast)、锐度(Sharpness)、亮度(Brightness)和色彩(Color)四类增强器。对于验证码,适当提高对比度和锐度通常有助于OCR识别。
from PIL import Image, ImageFilter, ImageEnhance
def enhance_image(image):
"""综合图像增强"""
img = image.convert('L')
# 1. 中值滤波降噪
img = img.filter(ImageFilter.MedianFilter(size=3))
# 2. 增强对比度(让字符更清晰,背景更干净)
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(2.0)
# 3. 增强锐度(让字符边缘更锐利)
enhancer = ImageEnhance.Sharpness(img)
img = enhancer.enhance(2.0)
# 4. 二值化
img = img.point(lambda x: 255 if x > 128 else 0, '1')
return img
去除干扰线
许多验证码加入了干扰线来增加OCR识别难度。去除干扰线的方法取决于干扰线的特征:如果干扰线颜色与字符不同,可以通过颜色阈值过滤;如果干扰线较细,可以使用形态学操作(如腐蚀操作后膨胀恢复,即开运算)去除细线;如果干扰线的角度固定,可以使用霍夫变换检测并擦除。最通用的一种思路是先尝试降噪,再通过连通域分析保留面积较大的连通区域(通常字符的面积大于干扰线和孤立噪点)。
from PIL import Image
import numpy as np
def remove_noise_by_connected_area(image, min_area=30):
"""通过连通域分析去除小面积噪点"""
arr = np.array(image.convert('1'))
from scipy import ndimage
# 标记连通域
labeled, num_features = ndimage.label(arr)
# 统计每个连通域的面积
sizes = ndimage.sum(arr, labeled, range(num_features + 1))
# 去除小面积连通域
small_mask = (sizes < min_area) & (sizes > 0)
remove_pixels = small_mask[labeled]
arr[remove_pixels] = 0 # 设为背景色
return Image.fromarray(arr)
四、机器学习验证码识别
对于复杂验证码(严重扭曲粘连、中文验证码、复杂背景),传统图像处理方法往往力不从心。此时需要引入深度学习模型,通过大量标注样本训练端到端的验证码识别模型。
样本收集与标注
训练深度学习验证码识别模型的第一步是收集标注数据。数据来源主要有三种:从目标网站爬取真实验证码图片并人工标注(最准确但成本最高)、使用验证码生成库(如captcha库)生成合成训练数据(成本低但可能存在分布差异)以及半自动标注(先使用OCR预识别,再人工修正错误标签)。一般来说,训练一个可用的验证码识别模型需要数千至数万张标注样本。
# 使用captcha库生成合成验证码训练数据
from captcha.image import ImageCaptcha
import random
import string
# 生成1万张训练图片
generator = ImageCaptcha(width=160, height=60)
characters = string.digits + string.ascii_uppercase
for i in range(10000):
# 随机生成4位验证码
code = ''.join(random.choices(characters, k=4))
# 生成图片
image = generator.generate_image(code)
image.save(f'train/{code}_{i}.png')
CNN模型训练
卷积神经网络(CNN)是验证码识别最常用的深度学习架构。它将验证码识别建模为多标签分类问题(每个字符位置独立分类),或者使用序列识别模型(CRNN+CTC,适用于变长验证码)。对于4位固定长度的验证码,常用的做法是使用CNN提取图像特征,然后接4个独立的全连接分类器,每个分类器输出对应字符位置的概率分布。
import torch
import torch.nn as nn
class CaptchaCNN(nn.Module):
"""验证码识别CNN模型(4位固定长度)"""
def __init__(self, num_chars=36, code_length=4):
super().__init__()
# 卷积层提取特征
self.conv_layers = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2), # 80x30
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2), # 40x15
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2), # 20x7
)
# 全连接层
self.fc = nn.Sequential(
nn.Linear(128 * 20 * 7, 512),
nn.ReLU(),
nn.Dropout(0.5),
)
# 4个字符位置,每个位置输出36类概率
self.classifiers = nn.ModuleList([
nn.Linear(512, num_chars) for _ in range(code_length)
])
def forward(self, x):
x = self.conv_layers(x)
x = x.view(x.size(0), -1) # 展平
x = self.fc(x)
outputs = [clf(x) for clf in self.classifiers]
return outputs
# 训练循环简写
def train_model(model, dataloader, epochs=20):
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(epochs):
for images, labels in dataloader:
outputs = model(images)
loss = sum(criterion(out, lab)
for out, lab in zip(outputs, labels))
optimizer.zero_grad()
loss.backward()
optimizer.step()
对于变长验证码(字符数量不固定),CRNN(卷积循环神经网络)+ CTC(Connectionist Temporal Classification)是更合适的架构。CRNN使用CNN提取空间特征,使用双向LSTM建模序列依赖关系,CTC则负责在不需预对齐的情况下将帧级预测转换为标签序列。
关键提示: 训练集应覆盖尽可能多的字符变体——不同的字体、大小、旋转角度、干扰样式等。过拟合是验证码识别模型训练中最常见的问题,数据增强(旋转、缩放、添加噪声、弹性变形)和Dropout是有效的缓解手段。
五、第三方打码平台
当自建OCR或训练模型的成本过高、或验证码类型过于复杂时,使用第三方打码平台是一种高效的替代方案。打码平台提供标准化的API接口,将验证码图片上传后,平台的人工或自动识别系统会在短时间内返回识别结果。
主流打码平台介绍
国内常用的打码平台包括:超级鹰(支持字符验证码、中文验证码、部分图形验证码,价格实惠,适合大规模使用)、打码兔(支持验证码类型丰富,识别速度快)、2captcha(国际平台,支持多种语言和验证码类型,适合跨境业务)以及TrueCaptcha(提供AI自动识别和人工识别两种模式)。
平台名称 支持类型 价格参考(千次) 识别速度 准确率
超级鹰 字符、中文、滑块 1-10元 1-5秒 85-95%
打码兔 字符、图形、滑块 2-15元 2-8秒 80-90%
2captcha 字符、reCAPTCHA、滑块 $0.5-3 5-30秒 90-95%
TrueCaptcha 字符、滑块、拼图 3-20元 1-3秒 90-98%
API对接方法
对接打码平台API通常遵循标准的请求-响应模式:构建包含验证码图片和配置参数的POST请求发送到平台接口,平台返回识别结果。以下以超级鹰为例展示对接方式。
import requests
import base64
class SuperEagleClient:
"""超级鹰打码平台客户端"""
def __init__(self, username, password, soft_id):
self.username = username
self.password = password
self.soft_id = soft_id
self.base_url = 'http://upload.chaojiying.net/Upload/Processing.php'
def recognize(self, image_path, codetype=1902):
"""识别验证码
codetype: 1902=4位数字字母, 1903=4位纯数字等
"""
with open(image_path, 'rb') as f:
img_data = base64.b64encode(f.read()).decode()
data = {
'user': self.username,
'pass': self.password,
'softid': self.soft_id,
'codetype': codetype,
'file_base64': img_data,
}
resp = requests.post(self.base_url, data=data)
result = resp.json()
if result['err_no'] == 0:
return result['pic_str'] # 识别结果
else:
raise Exception(f'识别失败: {result["err_no"]} - {result["err_str"]}')
# 使用示例
client = SuperEagleClient('your_username', 'your_password', 'your_soft_id')
code = client.recognize('captcha.png')
print(f'验证码识别结果: {code}')
打码平台的选型建议
选择打码平台时需综合考虑验证码类型、数量和预算。对于入门级的字符验证码,超级鹰性价比高;对于reCAPTCHA等国际验证码,2captcha是更好的选择;对于长期大规模使用,建议评估自建OCR的成本回收周期——当每日打码量超过一定阈值(如5000次/天),自建模型可能是更经济的选择。
六、滑块验证码处理
滑块验证码是目前使用最广泛的图形验证码形式。它要求用户将滑块从起始位置拖动到目标位置(缺口)才能通过验证。处理滑块验证码的关键在于缺口识别和轨迹模拟两个核心环节。
滑块验证码的原理
典型的滑块验证码由三部分组成:带有缺口的背景图、滑块(缺口形状的拼块)和滑块轨道。验证过程分为三个步骤:服务器下发背景图和缺口位置参数 → 用户拖动滑块到缺口位置 → 服务器验证滑动距离和轨迹数据是否符合人类行为特征。一些高级的滑块验证码还会收集鼠标事件序列(mousedown、mousemove、mouseup的时间戳和坐标),通过机器学习模型判断拖动行为是否来自真人。
缺口位置识别(OpenCV)
对于带有缺口背景图的滑块验证码,可以使用OpenCV的模板匹配或边缘检测技术来定位缺口位置。最常见的做法是:获取完整背景图和带缺口背景图的对比图像(若只有带缺口图,可通过滑块图与背景图进行匹配),使用matchTemplate函数查找最佳匹配位置,或通过Canny边缘检测+轮廓分析定位缺口。
import cv2
import numpy as np
def locate_gap(bg_image_path, gap_image_path):
"""定位滑块验证码的缺口位置(模板匹配法)"""
# 读取背景图
bg = cv2.imread(bg_image_path, cv2.IMREAD_GRAYSCALE)
# 读取缺口图(或滑块图)
gap = cv2.imread(gap_image_path, cv2.IMREAD_GRAYSCALE)
# 方法一:模板匹配
result = cv2.matchTemplate(bg, gap, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
# 缺口左上角坐标
x, y = max_loc
return x, y, max_val
def locate_gap_edge(bg_image_path):
"""基于边缘检测的缺口定位(适用于无对比图场景)"""
# 读取并预处理
img = cv2.imread(bg_image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Canny边缘检测
edges = cv2.Canny(gray, 50, 150)
# 形态学操作闭合边缘
kernel = np.ones((5, 5), np.uint8)
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
# 查找轮廓
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
# 筛选符合缺口特征的轮廓(面积适中、宽高比接近滑块)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = w / h
area = w * h
if 1.5 < aspect_ratio < 3.0 and 500 < area < 5000:
return x, y, w, h
return None
人类滑动轨迹模拟
滑块验证码的后端验证环节通常会对用户的滑动轨迹进行分析。机器生成的匀速直线轨迹与人类操作的特征差异明显——人类的拖动具有加速-匀速-减速的变速特征,轨迹也不是完美的直线,存在微小的抖动和偏移。因此,轨迹模拟的质量直接影响能否通过验证。
import random
import math
def generate_human_track(distance):
"""生成模拟人类拖动的滑动轨迹
特征:先加速后减速,带微小抖动
"""
track = []
current = 0
mid = distance * random.uniform(0.6, 0.8) # 加速阶段占比60-80%
t = 0.2 # 时间步长
while current < distance:
if current < mid:
# 加速阶段(匀加速)
speed = random.uniform(2, 4)
else:
# 减速阶段(匀减速)
speed = random.uniform(1, 2)
current += speed
current = min(current, distance)
# 添加随机抖动(模拟人手不稳定)
jitter = random.uniform(-1, 1)
# 添加时间戳
t += random.uniform(0.05, 0.15)
track.append({
'x': round(current + jitter, 2),
'y': round(jitter * random.uniform(0.5, 1.5), 2), # Y轴微小偏移
't': round(t, 3)
})
# 确保最终位置精确到达缺口
track[-1]['x'] = distance
track[-1]['y'] = 0
return track
# 示例:生成轨迹并查看前几个点
track = generate_human_track(200)
print('轨迹示例(前5个点):')
for point in track[:5]:
print(f" x={point['x']:>6.2f}, y={point['y']:>5.2f}, t={point['t']}s")
print(f'总步数: {len(track)}, 总耗时: {track[-1]["t"]:.2f}s')
Selenium/Playwright滑动操作
生成轨迹后,需要通过浏览器自动化工具(Selenium或Playwright)来执行实际的滑动操作。关键操作步骤为:定位滑块元素 → 点击并按住滑块 → 按轨迹逐步移动鼠标 → 在目标位置释放鼠标。
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
def slide_captcha(driver, slider_selector, track):
"""执行滑块拖动操作"""
slider = driver.find_element_by_css_selector(slider_selector)
action = ActionChains(driver)
# 点击并按住滑块
action.click_and_hold(slider).perform()
# 按生成的轨迹逐步移动
for point in track:
action.move_by_offset(point['x'], point['y']).perform()
# 释放滑块
action.release().perform()
在Playwright中,操作更加简洁且对反爬的敏感度更低:
from playwright.sync_api import sync_playwright
def slide_captcha_playwright(page, slider_selector, track):
"""使用Playwright执行滑块拖动"""
slider = page.query_selector(slider_selector)
box = slider.bounding_box()
# 从滑块中心开始
start_x = box['x'] + box['width'] / 2
start_y = box['y'] + box['height'] / 2
# 执行滑动
page.mouse.move(start_x, start_y)
page.mouse.down()
for point in track:
page.mouse.move(start_x + point['x'],
start_y + point['y'],
steps=1)
page.wait_for_timeout(10) # 微小延时
page.mouse.up()
七、行为验证码(极验/腾讯验证码)
以极验(Geetest)为代表的行为验证码代表了验证码技术的最高水平。极验验证码从最初的点击验证发展到如今集滑块、拼图、无感验证于一体的综合风控体系,被广泛应用于各类网站的安全防护中。
极验验证码的加载流程
极验验证码的加载分为三个主要阶段:第一阶段,初始化请求(向极验服务器获取challenge、gt等参数,返回包含验证码类型的配置信息);第二阶段,用户操作验证(根据验证码类型执行拖动、点击等操作,客户端生成验证数据);第三阶段,提交验证(将操作数据提交给极验服务器进行校验,校验通过后返回validate参数)。
破解极验验证码需要攻破三个关卡:一是参数获取(从页面中提取gt和challenge参数,这些参数通常在页面脚本中动态生成);二是轨迹模拟(生成逼真的人类鼠标轨迹);三是参数加密(极验的提交数据经过多层加密,需要逆向分析JavaScript代码还原加密逻辑)。
参数获取与还原
极验验证码的关键参数包括gt(验证ID,每个网站唯一)、challenge(动态变化标识,每次刷新不同)和validate(验证通过凭证,最终需要获取的有效参数)。获取这些参数通常需要通过Selenium截获网络请求或执行JavaScript代码提取。对于极验v3版本,数据请求中还会包含userresponse和a等加密参数,需要逆向分析极验的JavaScript SDK文件(通常名为gt.js或geetest.js)来理解参数生成逻辑。
# 通过Selenium获取极验验证码参数
from selenium import webdriver
from selenium.webdriver.common.by import By
def get_geetest_params(driver):
"""获取极验验证码初始化参数"""
# 等待验证码加载完成
wait_element = driver.find_element(By.CLASS_NAME, 'geetest_holder')
# 方式一:从页面DOM中提取
gt = driver.execute_script(
"return window.geetest_gt || '';"
)
challenge = driver.execute_script(
"return window.geetest_challenge || '';"
)
# 方式二:截获网络请求(更可靠)
# 通过BrowserMobProxy或CDP监听geetest的请求
if not gt or not challenge:
# 尝试从元素属性获取
gt_el = driver.find_element(By.CLASS_NAME, 'geetest_radar_tip')
gt = gt_el.get_attribute('data-gt')
challenge = gt_el.get_attribute('data-challenge')
return {'gt': gt, 'challenge': challenge}
轨迹数据生成
极验的后端风控系统会对轨迹进行多维度分析,包括但不限于:轨迹加速度曲线(人类操作呈现平滑的加速-减速曲线)、轨迹抖动幅度(人类操作有微小抖动,机器过于平滑)、轨迹路径曲率(自然轨迹带一定弧度而非完美直线)、操作时间分布(人类在开始和结束阶段会有短暂停顿)以及各事件间的时间间隔分布(符合人类操作特征的非均匀分布)。生成高质量的轨迹数据需要大量观察和分析真实人类操作数据。
def generate_advanced_track(distance):
"""生成高质量的人类轨迹(带加速度曲线和自然抖动)"""
import random
import math
track = []
current = 0
# 加速距离占比(人类通常在60-80%位置达到峰值速度)
acc_dist = distance * random.uniform(0.65, 0.75)
max_v = random.uniform(3.5, 5.0) # 最大像素/步
t = 0
while current < distance:
remain = distance - current
if current < acc_dist:
# 加速阶段:速度线性增加
progress = current / acc_dist
# 加入随机波动,更自然
speed = max_v * (0.3 + 0.7 * progress)
speed *= random.uniform(0.85, 1.15)
else:
# 减速阶段:速度非线性衰减
progress = (current - acc_dist) / (distance - acc_dist)
# 使用sigmoid曲线模拟自然减速
speed = max(0.3, max_v * (1 - progress ** 0.7))
speed *= random.uniform(0.9, 1.1)
# 防止过头
speed = min(speed, remain + 1)
current += speed
current = min(current, distance)
# 自然的Y轴偏移(真实拖动中Y轴会有微小变化)
y_offset = random.gauss(0, 1.5)
# 抖动幅度随速度变化(速度快时抖动大)
jitter = random.gauss(0, 0.3 + speed * 0.05)
t += random.uniform(0.02, 0.08)
track.append({
'x': round(current + jitter, 4),
'y': round(y_offset, 4),
't': round(t, 4)
})
# 在关键位置增加停顿
if random.random() < 0.02: # 2%概率停顿
pause = random.uniform(0.05, 0.2)
t += pause
track.append({
'x': round(current, 4),
'y': round(y_offset, 4),
't': round(t, 4)
})
# 确保终点精确
track[-1]['x'] = float(distance)
track[-1]['y'] = 0.0
return track
关键提示: 极验验证码的难度随着版本升级不断提高。极验v3版本以滑块验证码为主,v4版本引入了更多的无感验证和风险分析维度。在实际项目中,建议优先尝试通过Cookie/Session复用减少验证码触发;当确实需要处理极验时,可以考虑商业化的第三方极验破解方案。
八、验证码处理策略
在实际的爬虫项目中,验证码处理不是孤立的技术点,而是一套需要与整体爬虫策略紧密结合的工程体系。合理的策略可以显著降低验证码触发的频率和处理成本。
优先使用Cookie/Session复用
绝大多数情况下验证码只在登录或首次访问时触发。通过在爬虫中妥善管理Cookie和Session会话,可以避免在后续请求中重复触发验证码。具体做法是:使用requests.Session()自动管理Cookie,将成功登录后的Cookie持久化保存到本地文件,下次启动爬虫时直接加载Cookie。此外,定期检查Cookie的时效性,对于需要长期运行的爬虫,维护一个Cookie池(从多个账号登录获取Cookie)可以有效延长爬取寿命。
import requests
import json
import os
class SessionManager:
"""Cookie持久化管理"""
def __init__(self, cookie_file='cookies.json'):
self.session = requests.Session()
self.cookie_file = cookie_file
def load_cookies(self):
"""从文件加载Cookie"""
if os.path.exists(self.cookie_file):
with open(self.cookie_file, 'r') as f:
cookies = json.load(f)
for cookie in cookies:
self.session.cookies.set(**cookie)
return True
return False
def save_cookies(self):
"""保存Cookie到文件"""
cookies = [
{'name': c.name, 'value': c.value,
'domain': c.domain, 'path': c.path}
for c in self.session.cookies
]
with open(self.cookie_file, 'w') as f:
json.dump(cookies, f)
def request(self, url, **kwargs):
"""带Cookie管理的请求"""
resp = self.session.get(url, **kwargs)
if resp.status_code == 200:
self.save_cookies() # 每次请求后更新Cookie
return resp
减少验证码触发频率
验证码是网站反爬手段中的最后防线,合理的爬虫行为管理可以让爬虫长时间不被触发验证码。具体策略包括:控制请求频率(在两次请求之间添加随机延迟,模拟人类浏览节奏),随机化User-Agent和HTTP Headers(避免因浏览器指纹一致被识别),使用代理IP轮换(避免同一IP的访问过于频繁),模拟人类访问路径(不要直接访问需要验证码保护的敏感资源,先访问首页、列表页等正常页面),以及避开高峰时段(在网站流量低的时候减少爬取频率)。
打码平台兜底
即使采取了各种预防措施,验证码仍然可能被触发。最稳妥的做法是构建一个分层验证码处理机制:第一层(无验证码触发):Cookie复用 + 正常访问行为 → 跳过验证码;第二层(简单验证码触发):Tesseract OCR + 图像预处理 → 自动识别;第三层(复杂验证码触发):自训练CNN模型 → 自动识别;第四层(无法自识别):第三方打码平台 → 兜底处理。这种分层策略可以在保证成功率的同时,最大化降低打码成本。
完整的验证码处理流程
一个成熟爬虫的验证码处理流程可以概括为以下步骤:检测验证码(通过页面元素判断是否有验证码弹窗)→ 确定验证码类型(字符/滑块/行为验证码)→ 应用对应处理策略(OCR/轨迹模拟/打码平台)→ 执行验证操作 → 验证结果检查(验证是否通过)→ 结果反馈(成功则继续爬取,失败则重试或切换方案)→ 记录日志(记录验证码类型、成功率、耗时等指标,用于后续优化)。
import time
import logging
class CaptchaHandler:
"""验证码处理调度器"""
def __init__(self):
self.strategies = {
'char': self.solve_char_captcha,
'slide': self.solve_slide_captcha,
'geetest': self.solve_geetest,
}
self.stats = {'total': 0, 'success': 0, 'failed': 0}
def handle(self, captcha_type, driver, **kwargs):
"""统一处理入口"""
self.stats['total'] += 1
start = time.time()
strategy = self.strategies.get(captcha_type)
if not strategy:
logging.warning(f'不支持的验证码类型: {captcha_type}')
return False
try:
result = strategy(driver, **kwargs)
if result:
self.stats['success'] += 1
logging.info(f'验证码处理成功 ({captcha_type}), '
f'耗时: {time.time() - start:.2f}s')
else:
self.stats['failed'] += 1
logging.warning(f'验证码处理失败 ({captcha_type})')
return result
except Exception as e:
self.stats['failed'] += 1
logging.error(f'验证码处理异常 ({captcha_type}): {e}')
return False
def solve_char_captcha(self, driver, **kwargs):
"""处理字符验证码(OCR方案 → 打码平台兜底)"""
try:
# 优先使用OCR
captcha_img = driver.find_element_by_css_selector('img.captcha')
img_src = captcha_img.get_attribute('src')
# 下载图片 → OCR识别 → 填写验证码
code = self.ocr_recognize(img_src)
if code and len(code) >= 4:
return self.fill_captcha(driver, code)
except Exception:
pass
# OCR失败,使用打码平台兜底
return self.solve_via_platform(driver, 'char')
def solve_slide_captcha(self, driver, **kwargs):
"""处理滑块验证码"""
# 定位背景图和滑块
# 识别缺口位置
# 生成人类轨迹
# 执行滑动操作
return self.slide_api(driver)
def solve_geetest(self, driver, **kwargs):
"""处理极验验证码"""
# 获取初始化参数
# 模拟点击验证按钮
# 执行滑动/点击验证
# 获取validate参数
return self.geetest_api(driver)
def get_stats(self):
"""获取处理统计"""
rate = self.stats['success'] / max(self.stats['total'], 1) * 100
return {
'total': self.stats['total'],
'success': self.stats['success'],
'failed': self.stats['failed'],
'success_rate': f'{rate:.1f}%'
}
经验总结: 验证码处理的核心不在于如何识别,而在于如何不触发。良好的爬虫行为管理可以让95%以上的请求不触发验证码。将精力优先投入到请求策略优化上,远比追求99%的验证码识别率更有工程价值。