Python3网络爬虫开发实战(8)验证码的识别

目前,许多网站采取各种各样的措施来反爬虫,其中一个措施便是使用验证码。随着技术的发展,验证码的花样越来越多。验证码最初是几个数字组合的简单的图形验证码,后来加入了英文字母和混淆曲线。有的网站还可能看到中文字符的验证码,这使得识别愈发困难。

一、 图片增强:OpenCV

1. OpenCV 基础使用

文中使用 Opencv 来识别目标缺口位置,Opencv 是一个计算机视觉和算法库,其安装也十分简单;

pip install python-opencv

高斯滤波

import cv2

blur_image = cv2.GaussianBlur(src, ksize, sigmaX, sigmaY, borderType)
  • src 输入图像,可以是单通道灰度图像或多通道彩色图像
  • ksize 高斯核的大小,是一个 (width, height) 的元组,必须是正数和奇数
  • sigmaX X 轴方向上的高斯核的标准差
  • dst 输出图像,如果未指定,将创建与 src 相同大小和类型的图像
  • sigmaY Y 轴方向上的高斯核的标准差,如果为 0,则 sigmaY 将与 sigmaX 相同
  • borderType 边界处理方式,默认为 cv2.BORDER_DEFAULT
  • blur_image 表示

边缘检测

import cv2

edges = cv.Canny(image, threshold1, threshold2[, apertureSize[, L2gradient]])
  • edges 为计算得到的边缘图像
  • image 为 8 位输入图像
  • threshold1 表示处理过程中的第一个阈值
  • threshold2 表示处理过程中的第二个阈值
  • apertureSize 表示 Sobel 算子的孔径大小
  • L2gradient 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)

轮廓提取

import cv2

contours, hierarchy = cv2.findContours(image,mode,method)
  • image:二值图,即黑白的(不是灰度图),所以读取的图像要先转成灰度的,再转成二值图。
  • mode:轮廓的模式。cv2.RETR_EXTERNAL只检测外轮廓;cv2.RETR_LIST检测的轮廓不建立等级关系;cv2.RETR_CCOMP建立两个等级的轮廓,上一层为外边界,内层为内孔的边界。如果内孔内还有连通物体,则这个物体的边界也在顶层;cv2.RETR_TREE建立一个等级树结构的轮廓。
  • method:轮廓的近似方法。cv2.CHAIN_APPROX_NOME存储所有的轮廓点,相邻的两个点的像素位置差不超过1;cv2.CHAIN_APPROX_SIMPLE压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需要4个点来保存轮廓信息;cv2.CHAIN_APPROX_TC89_L1,cv2.CV_CHAIN_APPROX_TC89_KCOS
  • contours:返回的轮廓
  • hierarchy:每条轮廓对应的属性

外接矩形

import cv2

retval = cv2.boundingRect(array)
  • retval 表示返回矩形边界左上角顶点的坐标值及矩形边界的宽和高;
  • array 是灰度图像或轮廓;

轮廓面积

import cv2

area = cv2.contourArea(contour, oriented=None)
  • contour: 轮廓信息;
  • oriented: 方向标识符,默认值为 False,若取 True,则该方法会返回一个带符号的面积值,正负取决于轮廓的方向,若取 False,则面积值以绝对值的形式返回;
  • area: 轮廓的面积;

轮廓周长

import cv2

length = cv2.arclength(curve, closed)
  • curve: 轮廓信息
  • closed: 轮廓是否关闭
  • length: 轮廓的周长

2. 滑动验证码缺口识别

要实现这一流程,关键有两步:

  1. 识别目标缺口的位置;
  2. 将滑块拖到缺口的位置;

其中第二步的实现方式有很多,例如可以用 Selenium 等自动化工具模拟这个流程,验证并登入成功后获取对应的 Cookie 或者 Token 信息;也可以直接逆向验证码背后的 Javascript 逻辑,将缺口信息直接传给 Javascript 代码,执行获取类似于 密匙 信息的操作,再利用密匙进行下一步操作;

在这里我们可以利用 OpenCV 去识别目标缺口的位置,在这里我们首先定义高斯滤波,边缘检测,轮廓提取三个方法:

import cv2

GAUSSIAN_BLUR_KERNEL_SIZE = (5, 5)
GAUSSIAN_BLUR_SIGMA_X = 0
CANNY_THRESHOLD1 = 200
CANNY_THRESHOLD2 = 450

def get_gaussian_blur_image(image):
	'''传入待处理图片的信息,返回高斯滤波处理后的图片信息'''
	return cv2.GaussianBlur(image, GAUSSIAN_BLUR_KERNEL_SIZE, GAUSSIAN_BLUR_SIGMA_X)

def get_canny_image(image):
	'''传入待处理图片的信息,返回边缘检测处理后的图片信息'''
	return cv2.Canny(image, CANNY_THRESHOLD1, CANNY_THRESHOLD2)

def get_contours(image):
	'''传入待处理的图片信息,返回提取得到的轮廓信息'''
	contours, _ = cv2.findContours(image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
	return contours

图片经过 高斯滤波,边缘检测,轮廓提取 这三个方法后,得到各个轮廓的信息,我们需要根据这些轮廓的外接矩形的面积和周长筛选想要的结果,

在这里我们可以给面积,周长和缺口位置都设定一个范围,经过实际测量可以得出目标缺口的外接矩形的高度大约是验证码高度的 0.25 倍,宽度大约是验证码宽度的 0.15 倍,所以在允许误差是 20% 的情况下,可以根据验证码的宽高信息大约计算出外接矩形的面积和周长的取值范围。同时缺口的位置有一个最小偏移量和最大偏移量,这里最小偏移量是验证码宽度的 0.2 倍,最大偏移量是验证码宽度的 0.85 倍,将这些综合起来我们可以定义三个阈值方法:

def get_contour_area_threshold(image_width, image_height):
	'''定义目标轮廓的面积下限和上限'''
	contour_area_min = (image_width * 0.15) * (image_height * 0.25) * 0.8
	contour_area_max = (image_width * 0.15) * (image_height * 0.25) * 1.2
	return contour_area_min, contour_area_max

def get_arc_length_threshold(image_width, image_height):
	'''定义目标轮廓的周长下限和上限'''
	arc_length_min = ((image_width * 0.15) + (image_height * 0.25)) * 2 * 0.8
	arc_length_max = ((image_width * 0.15) + (image_height * 0.25)) * 2 * 1.2
	return arc_length_min, arc_length_max

def get_offset_threshold(image_width):
	'''定义缺口位置的偏移量下限和上限'''
	offset_min = 0.2 * image_width
	offset_max = 0.85 * image_width
	return offset_min, offset_max

定义完毕后,我们只需要遍历各个轮廓的信息,然后根据这三个上下限进行筛选;

# 获得轮廓信息
image_raw = cv2.imread('captcha.png')
image_height, image_width, _ = image_raw.shape
image_gaussian_blur = get_gaussian_blur_image(image_raw)
image_canny = get_canny_image(image_gaussian_blur)
contours = get_contours(image_canny)

# 获得上下限
contour_area_min, contour_area_max = get_contour_area_threshold(image_width, image_height)
arc_length_min, arc_length_max = get_arc_length_threshold(image_width, image_height)
offset_min, offset_max = get_offset_threshold(image_width)

# 筛选信息
offset = None
for contour in contours:
	x, y, w, h = cv2.boundingRect(contour)
	if contour_area_min < cv2.contourArea(contour) < contour_area_max and
		arc_length_min < cv2.arcLength(contour, True) < arc_length_max and
		offset_min < x < offset_max:
		cv2.rectangle(image_raw, (x, y), (x + w, y + h), (0, 0, 255), 2)
		offset = x

cv2.imwrite('image_label.png', image_raw)
print('offset:', offset)

得到缺口信息后,我们可以使用 Selenium 等自动化工具模拟这个流程,验证并登入成功后获取对应的 Cookie 或者 Token 信息;也可以直接逆向验证码背后的 Javascript 逻辑,将缺口信息直接传给 Javascript 代码,执行获取类似于 密匙 信息的操作,再利用密匙进行下一步操作;

二、图片验证码和滑块验证码

图片验证码基本上都是使用 OCR 来解决的,这里有几种方法;

1. tesserocr

tesserocr 是 Python 的一个 OCR 识别库 ,但其实是对 tesseract 做的一 层 Python API 封装,所以它的核心是 tesseract。 因此,在安装 tesserocr 之前,我们需要先安装tesseract。

进入网站:Index of /tesseract (uni-mannheim.de),寻找 tesseract-ocr-w64-setup-v***.exe,下载完毕后安装,找到安装路径,将路径添加到环境变量中;这样 tesseract 就安装完毕了,接下来安装 tesserocr;

pip install tesserocr

识别图片

import tesserocr
from PIL import Image

# mode:1
image = Image.open('code.jpg')
result = tesserocr.image_to_text(image)

# mode:2
result = tesserocr.file_to_text('code.jpg')

print(result)

针对一些有干扰的图片,我们做一些灰度和二值化处理,这会提高图片识别的正确率。

但是在这里咱们不推荐使用 tesserocr,主要有两个缺点:1. 识别准确率低;2. 安装起来麻烦;

2. ddddocr

国产开发的一个验证码识别库,DdddOcr、最简依赖的理念,尽量减少用户的配置和使用成本,希望给每一位测试者带来舒适的体验:sml2h3/ddddocr: 带带弟弟 通用验证码识别OCR pypi版 (github.com)

安装 ddddocr 相当简单,直接 pip 安装就好;

pip install ddddocr

ddddocr 有许多的功能,如基础的 ocr 识别能力可以识别图片验证码:主要用于识别单行文字,即文字部分占据图片的主体部分,例如常见的英数验证码等,本项目可以对中文、英文(随机大小写or通过设置结果范围圈定大小写)、数字以及部分特殊字符。

import ddddocr

ocr = ddddocr.DdddOcr()

image = open("example.jpg", "rb").read()
result = ocr.classification(image)
print(result)

本库内置有两套ocr模型,默认情况下不会自动切换,需要在初始化ddddocr的时候通过参数进行切换

import ddddocr

ocr = ddddocr.DdddOcr(beta=True)  # 切换为第二套ocr模型

image = open("example.jpg", "rb").read()
result = ocr.classification(image)
print(result)

提示 对于部分透明黑色png格式图片得识别支持: classification 方法 使用 png_fix 参数,默认为False

 ocr.classification(image, png_fix=True)

目标检测能力可以识别图片中的点击位置:主要用于快速检测出图像中可能的目标主体位置,由于被检测出的目标不一定为文字,所以本功能仅提供目标的bbox位置 (在⽬标检测⾥,我们通常使⽤bbox(bounding box,缩写是 bbox)来描述⽬标位置。bbox是⼀个矩形框,可以由矩形左上⻆的 x 和 y 轴坐标与右下⻆的 x 和 y 轴坐标确定)

如果使用过程中无需调用ocr功能,可以在初始化时通过传参ocr=False关闭ocr功能,开启目标检测需要传入参数det=True

import ddddocr
import cv2

det = ddddocr.DdddOcr(det=True)

with open("test.jpg", 'rb') as f:
    image = f.read()

bboxes = det.detection(image)
print(bboxes)

im = cv2.imread("test.jpg")

for bbox in bboxes:
    x1, y1, x2, y2 = bbox
    im = cv2.rectangle(im, (x1, y1), (x2, y2), color=(0, 0, 255), thickness=2)

cv2.imwrite("result.jpg", im)

滑块检测能力,是基于 OpenCV 实现的:本项目的滑块检测功能并非AI识别实现,均为opencv内置算法实现。可能对于截图党用户没那么友好~,如果使用过程中无需调用ocr功能或目标检测功能,可以在初始化时通过传参ocr=False关闭ocr功能或det=False来关闭目标检测功能

其中有两种算法,第一种原理是通过滑块图像的边缘在背景图中计算找到相对应的坑位,可以分别获取到滑块图和背景图,滑块图为透明背景图

滑块图

背景图

det = ddddocr.DdddOcr(det=False, ocr=False)

with open('target.png', 'rb') as f:
	target_bytes = f.read()

with open('background.png', 'rb') as f:
	background_bytes = f.read()

res = det.slide_match(target_bytes, background_bytes)

print(res)

由于滑块图可能存在透明边框的问题,导致计算结果不一定准确,需要自行估算滑块图透明边框的宽度用于修正得出的bbox

提示:如果滑块无过多背景部分,则可以添加simple_target参数, 通常为jpg或者bmp格式的图片

slide = ddddocr.DdddOcr(det=False, ocr=False)

with open('target.jpg', 'rb') as f:
	target_bytes = f.read()

with open('background.jpg', 'rb') as f:
	background_bytes = f.read()

res = slide.slide_match(target_bytes, background_bytes, simple_target=True)

print(res)

第二种算法的原理是:通过比较两张图的不同之处进行判断滑块目标坑位的位置

参考图a,带有目标坑位阴影的全图

参考图b,全图

slide = ddddocr.DdddOcr(det=False, ocr=False)

with open('bg.jpg', 'rb') as f:
	target_bytes = f.read()

with open('fullpage.jpg', 'rb') as f:
	background_bytes = f.read()

img = cv2.imread("bg.jpg")

res = slide.slide_comparison(target_bytes, background_bytes)

print(res)

OCR概率输出,为了提供更灵活的ocr结果控制与范围限定:项目支持对ocr结果进行范围限定。

可以通过在调用classification方法的时候传参probability=True,此时classification方法将返回全字符表的概率 当然也可以通过set_ranges方法设置输出字符范围来限定返回的结果。

set_ranges 方法限定返回字符返回

本方法接受1个参数,如果输入为int类型为内置的字符集限制,string类型则为自定义的字符集

如果为int类型,请参考下表

参数值意义
0纯整数0-9
1纯小写英文a-z
2纯大写英文A-Z
3小写英文a-z + 大写英文A-Z
4小写英文a-z + 整数0-9
5大写英文A-Z + 整数0-9
6小写英文a-z + 大写英文A-Z + 整数0-9
7默认字符库 - 小写英文a-z - 大写英文A-Z - 整数0-9

如果为string类型请传入一段不包含空格的文本,其中的每个字符均为一个待选词 如:"0123456789+-x/=""

import ddddocr

ocr = ddddocr.DdddOcr()

image = open("test.jpg", "rb").read()
ocr.set_ranges("0123456789+-x/=")
result = ocr.classification(image, probability=True)
s = ""
for i in result['probability']:
    s += result['charsets'][i.index(max(i))]

print(s)

最后该项目的 AI 模型支持自定义OCR训练模型导入:本项目支持导入来自于 dddd_trainer 进行自定义训练后的模型,参考导入代码为

import ddddocr

ocr = ddddocr.DdddOcr(det=False, ocr=False, import_onnx_path="myproject_0.984375_139_13000_2022-02-26-15-34-13.onnx", charsets_path="charsets.json")

with open('test.jpg', 'rb') as f:
    image_bytes = f.read()

res = ocr.classification(image_bytes)
print(res)

3. 深度学习识别

简单的图片验证码识别可以使用深度学习中的 Faster R-CNN 和 LSTM 来执行:Python深度学习基于Tensorflow(15)OCR验证码 文本检测与识别实例_tensorflow 文字识别-CSDN博客

滑动验证码的缺口,也可以使用深度学习来识别,其任务具体来说就是一个单纯的目标检测任务,目前比较流行的目标检测算法有 R-CNN,Fast R-CNN,Faster R-CNN,SSD,YOLO 等;

其中目标检测算法分为两类,一类是一阶段目标检测,具有代表性的是 SSD 和 YOLO;另一类是二阶段目标检测,具有代表性的是 R-CNN,Fast R-CNN,Faster R-CNN。前者检测速度较快,但是准确性较低(对比);

滑动验证码缺口的识别模型具体操作方式是利用多次请求滑动验证码网站获取滑块图像,然后利用 LabelImg 工具将收集到的滑动验证码图像缺口框起来,放入目标检测模型中训练,利用训练好的模型对新的验证码进行缺口识别;

4. 超级鹰打码平台

利用打码平台可以轻松的识别各种各样的验证码,图形验证码,滑动验证码,点选验证码和逻辑验证码等等,而且不需要懂任何算法,只需要向 API 上传验证码图片,它便会返回对应的识别结果;

API 接口 demo 测试如下:Python语言Demo下载-超级鹰验证码识别API接口 (chaojiying.com)

三、手机验证码的自动化处理

现在许多的验证码需要提供手机号码发送短信验证码来验证,我们需要将手机上的短信验证码发送到 PC 端上,然后利用发送请求;

对于安卓手机,这里有两种方式,第一种是使用安卓开发的方式;第二种是使用一个开源软件 SmsForwarder,中文叫做短信转发器;pppscn/SmsForwarder: 短信转发器——监控Android手机短信、来电、APP通知,并根据指定规则转发到其他手机:钉钉群自定义机器人、钉钉企业内机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。包括主动控制服务端与客户端,让你轻松远程发短信、查短信、查通话、查话簿、查电量等。(V3.0 新增)PS.这个APK主要是学习与自用,如有BUG请提ISSUE,同时欢迎大家提PR指正 (github.com)

除此之外,我们还可以使用专业的手机卡池和猫池配以专业的软件设备实现短信监听;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值