Python识别验证码的实现示例

1. 前言

验证码主要是为了避免机器/恶意软件等恶意行为,保证系统的安全性。但对于数据爬取的需求或机器学习的训练等场景下,需要用到验证码的破解或者模拟。Python是广受欢迎的编程语言,并且有着强大的爬虫和机器学习库。因此Python识别验证码成为了一个重要的应用场景。本文将介绍如何使用Python实现验证码的识别。

2. 验证码及其应用场景

验证码是一种可以确认使用计算机的是人而不是机器的技术,包括数字、字母和汉字等,按统计的不同方式可分为图像验证码和语音验证码两类,常用的有数字图形验证码、字母图形验证码等。图形验证码是最常用的一种,一般是将一个字符串以图片形式展示给用户,要求用户输入这个字符串才能通过验证。

验证码的应用场景多种多样,例如:

1. 安全性验证: 防止机器或者黑客自动或者暴力破解账户密码、支付密码等;

2. 提高阅读质量: 通过验证码来过滤垃圾用户,提高网站论坛的阅读质量;

3. 数据采集: 部分网站由于反爬虫机制,需要使用验证码来获取相应的数据。

3. Python识别数字图形验证码

3.1. 验证码生成

在本文中,我们使用Python中的Pillow库来生成数字图形验证码。该库是Python图像处理库PIL (Python Imaging Library)的一个完美分支,支持多种类型的图像文件格式,如JPG, PNG等。

我们可以调用Pillow库中提供的ImageImageDraw模块,可以自己定制字体、颜色、图像大小等参数,来生成我们所需要的验证码。

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来实现对数字图形验证码的分割和机器学习算法来实现识别过程,以及对训练集和测试集的数据处理。

验证码的漏洞总是存在的,我们也并不想通过识别验证码来进行恶意破坏。相信在今天这样的技术发达世界,从破解入手或搞

后端开发标签