1. 前言
验证码主要是为了避免机器/恶意软件等恶意行为,保证系统的安全性。但对于数据爬取的需求或机器学习的训练等场景下,需要用到验证码的破解或者模拟。Python是广受欢迎的编程语言,并且有着强大的爬虫和机器学习库。因此Python识别验证码成为了一个重要的应用场景。本文将介绍如何使用Python实现验证码的识别。
2. 验证码及其应用场景
验证码是一种可以确认使用计算机的是人而不是机器的技术,包括数字、字母和汉字等,按统计的不同方式可分为图像验证码和语音验证码两类,常用的有数字图形验证码、字母图形验证码等。图形验证码是最常用的一种,一般是将一个字符串以图片形式展示给用户,要求用户输入这个字符串才能通过验证。
验证码的应用场景多种多样,例如:
1. 安全性验证: 防止机器或者黑客自动或者暴力破解账户密码、支付密码等;
2. 提高阅读质量: 通过验证码来过滤垃圾用户,提高网站论坛的阅读质量;
3. 数据采集: 部分网站由于反爬虫机制,需要使用验证码来获取相应的数据。
3. Python识别数字图形验证码
3.1. 验证码生成
在本文中,我们使用Python中的Pillow
库来生成数字图形验证码。该库是Python图像处理库PIL (Python Imaging Library)
的一个完美分支,支持多种类型的图像文件格式,如JPG, PNG等。
我们可以调用Pillow
库中提供的Image
和ImageDraw
模块,可以自己定制字体、颜色、图像大小等参数,来生成我们所需要的验证码。
from PIL import Image, ImageDraw, ImageFont
import random
def create_verify_code(size=(120, 30),
chars="abcdefghijklmnopqrstuvwxyz0123456789",
mode="RGB",
bg_color=(255, 255, 255),
fg_color=(0, 0, 255),
font_size=18,
font_type="arial.ttf",
length=5,
draw_lines=True,
n_line=(1, 2),
draw_points=True,
point_chance=2):
'''
生成验证码字符
:param size: 图像的大小,格式(宽,高),默认为(120, 30)
:param chars: 允许的字符集合,格式字符串
:param mode: 图像模式,默认为RGB
:param bg_color: 背景色, 默认为白色
:param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
:param font_size: 验证码字体大小
:param font_type: 验证码字体,默认使用arial.ttf
:param length: 验证码字符个数
:param draw_lines: 是否划干扰线
:param n_line: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
:param draw_points: 是否画干扰点
:param point_chance: 干扰点出现的概率,大小范围[0, 100]
:return: (text, image)
'''
width, height = size
# 创建图形对象
img = Image.new(mode, size, bg_color)
# 创建画笔对象
draw = ImageDraw.Draw(img)
def get_chars():
'''
生成给定长度的字符串,返回列表格式
:return: 随机字符串列表
'''
return random.sample(chars, length)
def create_lines():
'''
绘制干扰线
'''
line_num = random.randint(*n_line) # 干扰线的数量
for i in range(line_num):
# 起始点
begin = (random.randint(0, size[0]), random.randint(0, size[1]))
# 结束点
end = (random.randint(0, size[0]), random.randint(0, size[1]))
# 划线
draw.line([begin, end], fill=(0, 0, 0))
def create_points():
'''
绘制干扰点
'''
chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]
for w in range(width):
for h in range(height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=(0, 0, 0))
def create_strs():
'''
绘制验证码字符
'''
c_chars = get_chars()
strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开
font = ImageFont.truetype(font_type, font_size)
font_width, font_height = font.getsize(strs)
draw.text(((width - font_width) / 3, (height - font_height) / 3),
strs, font=font, fill=fg_color)
return ''.join(c_chars)
if draw_lines:
create_lines()
if draw_points:
create_points()
strs = create_strs()
return strs, img
# usage
if __name__ == '__main__':
code_text, img = create_verify_code()
img.show()
执行python captcha.py
,即可得到一个数字图形验证码,如下图所示。
3.2. 验证码识别
3.2.1. 前置知识
验证码字符识别技术通常采用计算机视觉技术,而计算机视觉技术涉及到的常见基础技术有:
图像预处理:把图像转换为计算机能够处理的形式,并进行降噪和增强等操作;
特征提取:找到识别图像的特征,例如边缘、形状、轮廓等;
模式匹配:匹配图像中的特征和已知模板的相似度,并输出相应的分类结果。
3.2.2. 策略
首先我们可以想到对于数字图形验证码,其图样相对单一,只由数字组成。我们可以通过分割图片,把每个数字分割出来,然后利用已有的深度学习模型把每个数字进行识别。常用的对于数字图形验证码识别,例如采用CNN(卷积神经网络)的方法,训练集用手动打多个相同格式的数字图形验证码样本,通过卷积神经网络提取出特征后进行模型训练,即可用于识别验证码。
3.3. 代码实现
3.3.1. 验证码生成
我们使用前面介绍的函数create_verify_code()
来生成我们所需要的输入数据。具体将数字图形验证码分割出来,其实就是找到数字的最小矩形框,然后进行裁剪。这个我们可以通过计算出每个数字的像素点占比,然后再进行排序后分割出每个数字。这里我们不再赘述。
# 生成数字图形验证码,返回数字和图形对象
def generate_captcha():
text, img = create_verify_code()
img = img.convert('L')
# 创建9张空白图片对象
ims = [Image.new("L", (40, 80), "white") for i in range(5)]
# 图片位置范围
positions = [(0, 0), (30, 0), (60, 0), (90, 0), (120, 0)]
# 像素点阈值,小于这个值的像素点就当做白色处理
threhold = 80
# 图片缩放比例
scale = 1 / 5
for i in range(5):
index = text[i]
div = img.crop((i * 24, 0, (i + 1) * 24, 30))
ys = []
# 计算每一列的像素值,如果黑色的比例超过0.3,这一列就认为是数字部分
for x in range(div.size[0]):
cnt = 0
for y in range(div.size[1]):
if div.getpixel((x, y)) < threhold:
cnt += 1
ys.append(cnt)
start, end = 0, 0
nums = []
# 分割数字
while start < div.size[0]:
if ys[start] > div.size[1] * 0.3:
end = start
while end < div.size[0] and ys[end] > div.size[1] * 0.3:
end += 1
num = div.crop((start, 0, end, div.size[1]))
start = end
nums.append(num.resize((int(num.width * scale),
int(num.height * scale)), Image.ANTIALIAS))
else:
start += 1
# 把数字贴到空白图片上
for j in range(len(nums)):
ims[i].paste(nums[j], (0, j * int(ims[i].height / len(nums))))
return text, ims
# usage
text, ims = generate_captcha()
for im in ims:
im.show()
执行python captcha.py
即可生成分割后的数字验证码,如下图所示。
3.3.2. 数据准备
首先需要下载手写数字MNIST数据集,具体如何下载不在本文讨论范围内。数据集中一共有10个类别,每个类别有大约6,000个样本,每个样本都是一个28 * 28的二维数组,并把它们展开成一维向量,大小为784。其中前60000条用于训练集,后10000条用于测试集。我们需要将每个生成的验证码分割后批量进行预处理,并对每个数字进行one-hot编码。
import numpy as np
from keras.utils import to_categorical
def prepare_dataset(imgs, labels):
'''
数据准备
:param imgs: 图片数据集
:param labels: 标签数据集
:return: 处理好的训练集和标签集
'''
dataset = []
for i, im in enumerate(imgs):
# img转成numpy格式,并且将像素值变成0~1之间的小数
img = np.array(im.convert("L"))
img = img.astype("float32") / 255.0
# 把图片打平成一维向量
img = img.reshape(1, 40, 40, 1)
dataset.append(img)
# 把数据集和标签集打包
dataset = np.vstack(dataset)
labels = np.array(labels)
labels = to_categorical(labels, 10)
return dataset, labels
train_xs, train_ys = [], []
for i in range(500):
text, ims = generate_captcha()
for j, im in enumerate(ims):
train_xs.append(im)
train_ys.append(int(text[j]))
train_xs = np.array(train_xs)
train_ys = np.array(train_ys)
train_xs, train_ys = prepare_dataset(train_xs, train_ys)
test_xs, test_ys = [], []
for i in range(100):
text, ims = generate_captcha()
for j, im in enumerate(ims):
test_xs.append(im)
test_ys.append(int(text[j]))
test_xs = np.array(test_xs)
test_ys = np.array(test_ys)
test_xs, test_ys = prepare_dataset(test_xs, test_ys)
3.3.3. 模型搭建
我们采用卷积神经网络(CNN)简单地搭建模型。CNN是一种前馈神经网络,它由一个或多个卷积层和池化层(有时也包括一个或多个全连接层)组成,最后通过卷积层的输出进行多分类的决策。
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dense, Dropout, Flatten
model = Sequential()
model.add(Conv2D(filters=32,
kernel_size=(3, 3),
activation='relu',
input_shape=(40, 40, 1)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(filters=64,
kernel_size=(3, 3),
activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.summary()
3.3.4. 训练模型
from keras.optimizers import Adam
model.compile(loss='categorical_crossentropy',
optimizer=Adam(),
metrics=['accuracy'])
model.fit(train_xs, train_ys,
batch_size=128,
epochs=20,
verbose=1,
validation_split=0.1)
score = model.evaluate(test_xs, test_ys, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
执行python captcha.py
,将会输出以上代码的运行结果,最终准确率为0.9895。
4. 总结
本文通过介绍了数字图形验证码的生成和识别,详细讲解了如何采用Python来实现对数字图形验证码的分割和机器学习算法来实现识别过程,以及对训练集和测试集的数据处理。
验证码的漏洞总是存在的,我们也并不想通过识别验证码来进行恶意破坏。相信在今天这样的技术发达世界,从破解入手或搞