拒绝反爬虫!教你搞定爬虫验证码

Irene181
• 阅读 1613

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

使用验证码可以防止应用或者网站被恶意注册、攻击,对于网站、APP而言,大量的无效注册、重复注册甚至是恶意攻击很令人头痛。使用验证码能够很大程度上减少这些恶意操作。验证码变得越来越复杂,爬虫的工作也变得越发艰难。有时候我们必须通过验证码的验证才能够访问页面(如图1所示)。

拒绝反爬虫!教你搞定爬虫验证码

图1 验证码界面

目前主流的 4 种验证码为输入式验证码、滑动式验证码、宫格式验证码和点击式的图文验证,下面我们来分别讲解它们的解决思路。

拒绝反爬虫!教你搞定爬虫验证码

4种验证码的解决思路

拒绝反爬虫!教你搞定爬虫验证码

01 输入式验证码

这种验证码主要是通过用户输入图片中的字母、数字、汉字等进行验证,如图2所示。

拒绝反爬虫!教你搞定爬虫验证码

图2 输入式验证码

解决思路:这是最简单的一种验证码,只要识别出里面的内容,然后填入输入框中即可。这种识别技术叫OCR,这里推荐使用 Python 的第三方库 tesserocr。tesserocr 与 pytesseract 是 Python 的一个 OCR 识别库,但其实是对 Tesseract 做的一层 Python API 封装,pytesseract 是 Google 的 Tesseract-OCR 引擎包装器;所以它们的核心是 Tesseract。对于没有什么背景影响的验证码,直接通过这个库来识别就可以。但是对于有嘈杂的背景的验证码,直接识别的识别率会很低,遇到这种验证码需要先对图片进行灰度化,然后再进行二值化,再去识别,这样识别率会大大提高。

02 滑动式验证码

这种是将备选碎片直线滑动到正确的位置,如图3所示。

拒绝反爬虫!教你搞定爬虫验证码

图3 滑动式验证码

解决思路:对于这种验证码就比较复杂一点,但也是有相应的办法。我们直接想到的就是模拟人去拖动验证码的行为,点击按钮,然后看到了缺口的位置,最后把拼图拖到缺口位置处完成验证。

第一步:点击按钮。当没有点击按钮的时候图片中的缺口和拼图是没有出现的,点击后才出现,这为我们找到缺口的位置提供了灵感。

第二步:拖到缺口位置。我们知道拼图应该拖到缺口处,但是这个距离如何用数值来表 示?通过第一步观察到的现象,我们可以找到缺口的位置。这里我们可以比较两张图的像素, 设置一个基准值,如果某个位置的差值超过了基准值,那我们就找到了这两张图片不一样的位置,当然我们是从那块拼图的右侧开始并且从左到右,找到第一个不一样的位置时就结束,这时的位置应该是缺口的 left,所以我们使用 selenium 拖到这个位置即可。这里还有个疑问,就 是如何能自动保存这两张图?我们可以先找到这个标签,然后获取它的 location 和 size,接着 是 top = int(location['y'])、bottom = int(location['y'] + size['height'])、left = int(location['x']) 以及right = int(location['x'] + size['width']),然后截图,最后抠图填入这四个位置就行。具体的使用 可以查看 selenium 文档,点击按钮前抠一张图,点击后再抠一张图。最后拖动时需要模拟人的 行为,先加速然后减速。因为这种验证码有行为特征检测,人是不可能做到一直匀速的,否则 它就判定为是机器在拖动,这样就无法通过验证了。

03 宫格验证码

如图4所示的验证码,爬虫难度比较大,每一次出现的都不一样,就算出现一样的,其拖动顺序也不相同。但是,我们发现不一样的验证码个数是有限的,这里采用模版匹配的方 法,把所有出现的验证码保存下来,然后挑出不一样的验证码,按照拖动顺序命名。我们从 左到右从上到下,将其分别设为 1、2、3、4。上图的滑动顺序为 4→3→2→1,所以我们命名 4_3_2_1.png。当验证码出现的时候,用我们保存的图片一一枚举,与出现的这种来比较像素, 方法见“滑动式验证码”部分。如果匹配上了,拖动顺序就为 4→3→2→1。然后使用 selenium 模拟即可。

拒绝反爬虫!教你搞定爬虫验证码

图4 宫格验证码

04 点击式的图文验证和图标选择

1)图文验证:通过文字提醒用户点击图中相同字的位置从而进行验证。

2)图标选择:给出一组图片,按要求点击其中一张或多张。借用万物识别的难度阻挡 机器。这两种原理相似,只不过一个是给出文字,点击图片中的文字,而一个是给出图片,点 出内容相同的图片。这两种都没有特别好的方法,只能借助第三方识别接口来识别出相同的内 容。推荐一个方法,把验证码发过去,会返回相应的点击坐标,然后再使用 selenium 模拟点击即可。

拒绝反爬虫!教你搞定爬虫验证码

OCR验证码

拒绝反爬虫!教你搞定爬虫验证码

OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式来确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程,下面介绍使用这种图像识别技术输入验证码的方法。

验证码识别基本步骤:

1)预处理

2)灰度化

3)二值化

4)去噪

5)分割

6)识别

在使用pytesseract之前,必须安装 Tesseract-OCR,因为pytesserat 依赖于 Tesseract-OCR, 若未安装则无法使用。首先使用 pytesseract 将彩色的图像转化为灰色的图像。

# 使用路径导入图片
im = Image.open(imgimgName) 
# 使用byte流导入图片 
# im = Image.open(io.BytesIO(b)) 
# 转化到灰度图
imgry = im.convert('L') 
# 保存图像
imgry.save('gray-'+imgName)

灰度化的图像如图5所示。

拒绝反爬虫!教你搞定爬虫验证码

图5 灰度化图像

紧接着将所得的图像二值化,将图片处理为只有黑白两色的图片,利于后面的图像处理和识别。

# 二值化,采用阈值分割法,threshold为分割点 
Threshold = 140 
Table = [ ]
For j in range(256): 
If j < threshold: 
Table.append(0) 
Else: 
Table.append(1) 
Out = imgry.point(table,'1') 
Out.save('b'+imgName)

二值化的图像如图 6 所示。

拒绝反爬虫!教你搞定爬虫验证码

图6 二值化图像

最后进行识别,得到的结果如图7所示。

拒绝反爬虫!教你搞定爬虫验证码

图7 识别结果

# 识别 
Text = pytesseract.image_to_string(out) 
Print(“识别结果:" +text)

拒绝反爬虫!教你搞定爬虫验证码

实战案例

拒绝反爬虫!教你搞定爬虫验证码

目前,很多网站为了防止爬虫肆意模拟浏览器登录,采用增加验证码的方式来拦截爬虫。验证码的形式有多种,最常见的就是图片验证码。

1 基本识别原理概述

1)每一幅图像在结构上,都是由一个个像素组成的矩阵,每一个像素都为单元格。

2)彩色图像的像素由三原色(红、绿、蓝)构成 元组,灰度图像的像素是一个单值,每个像素的值范围为(0, 255)。

某系统门户登录界面中的验证码如图8所示, 现在我们要实现自动的验证码识别。

拒绝反爬虫!教你搞定爬虫验证码

图8 验证码

2 图像特征

首先,我们仔细观察一下这个验证码图像,可以发现一些如图9所示的固定特征。

1)验证码中的字符数始终为 6,并且是灰度图像。

2)字符间的间隔看起来每次都一样。

3)每个字符都是完全定义的。

4)图像有许多杂散的黑暗像素,以及穿过图像的线条作为干扰因素。

拒绝反爬虫!教你搞定爬虫验证码

图9 固定特征

3 图像分析

使用一个工具(binary-image)以二进制形式可视化图像(0 表示黑色像素,1 表示白色像 素)。图像尺寸为 45×180,每个字符分配 30 个像素的空间来进行适配,从而使它们的间隔比 较均匀。因此,取得了验证码识别路上的第一步。如图10所示的结果:把图像裁剪成 6 个不同的部分,每个部分的宽度均为 30 像素。

拒绝反爬虫!教你搞定爬虫验证码

图10 二进制可视化图像

4 字符部分裁剪

图像裁剪的语法如下:

from PIL import Image 
image = Image.open("filename.png") 
cropped_image = image.crop((left, upper, right, lower)

比如要裁剪第一个字符:

from PIL import Image 
image = Image.open("captcha.png").convert("L") 
cropped_image = image.crop((0, 0, 30, 45))
cropped_image.save("cropped_image.png")

得到的图像如图11所示。

拒绝反爬虫!教你搞定爬虫验证码

图11 结果图像

将其打包到一个循环中,编写了一个简单的脚本,从该站点获取 500 个验证码图像,并将所有裁剪后的字符保存到一个文件夹中。

5 图像去杂

为了“清理”图像中的干扰因素(删除不必要的线和点),我们可以使用一个很简单的算法:字符中的所有像素都是纯黑色(0)。如果它不是完全黑色的,则将它当成白色的。因此,对于值大于0的每个像素,将给其重新赋值为255。使用 load() 函数将图像转换为 45×180 数字矩阵,然后对其进行处理。

pixel_matrix = cropped_image.load() 
for col in range(0, cropped_image.height): 
for row in range(0, cropped_image.width):
if pixel_matrix[row, col] != 0: 
pixel_matrix[row, col] = 255
image.save("thresholded_image.png")

为了清晰起见,将代码应用于原始图像。原始图像如图12所示。

拒绝反爬虫!教你搞定爬虫验证码

图12 原始图像

矫正后的图像如图13所示。

拒绝反爬虫!教你搞定爬虫验证码

图13 矫正后的图像

可以看到,并非完全黑暗的所有像素都被删除了,比如通过图像的线。上述方法在图像处理 中的专业术语叫作阈值处理,当然还有很多其他的处理方法,阈值处理只是最简单实用的方法。

6 去除图像中的黑点

图像中有许多杂散的黑暗像素作为干扰因子。循环遍历图像矩阵,如果相邻像素是白色的,并且与相邻像素相对的像素也是白色的,而中心像素是黑色的,则设定中心像素为白色。

for column in range(1, image.height - 1):
for row in range(1, image.width - 1):
if pi xel_matrix[row, column] == 0 and pixel_matrix[row, column - 1] == 255 and
pixel_matrix[row, column + 1] == 255:
pixel_matrix[row, column] = 255
if pi xel_matrix[row, column] == 0 and pixel_matrix[row - 1, column] == 255 and
pixel_matrix[row + 1, column] == 255:
pixel_matrix[row, column] = 25

处理后的结果如图14所示。

拒绝反爬虫!教你搞定爬虫验证码

图14 处理后的结果

经过以上步骤的处理,图像已经只剩下字符框架了。虽然有些字符已经丢失了一些基础像 素,但是每个字符的图像骨架基本上都完备。当然这个是必需的,我们进行这么多处理的主要 目的就是为每个可能的字符都截取生成合适的字符图。

7 构建字符图库

将上述算法裁剪得到的所有字符图像都存储于文件夹下。下一个任务是为属于“ A-Z0-9” 的每个字符找到至少一个样本图像(如图15所示)。这一步就像“训练”步骤,手动为每个 字符选择了一个字符图像并对其更名。

拒绝反爬虫!教你搞定爬虫验证码

图15 样本图像

8 选择最优的字符图

运行其他几个脚本,以确保每个字符的图像中都有最佳的图像,例如,如果有 20 个“ A” 的字符图像,那么暗色数量最少的图像显然是噪声最少的图像,因此最适合作为骨架图像。选择的原则如下:

1)一个按照字符排序的相似图像(约束条件:黑色像素数量大小,并且相似度 > = 90%~95%)。

2)一个从每个分组字符获得的最佳图像。因此,到目前为止,我们生成了一个像素图像库。我们将其转换为像素矩阵,并将位图字符图转为数字点阵 JSON 文件。

9 识别算法

最后是获取任何新的验证码图像的算法:使用相同的算法尽量减少新图像中不必要的干扰因子。对于新验证码图片中的每个字符,强制通过与生成的 JSON 文件的像素矩阵来匹配,基 于相应的黑色像素匹配来计算相似度。如果一个像素是黑色的,其在图像中的位置恰好是待破 解的验证码,并且此像素位于字符库中的骨架图像 / 位图内的相同位置处,则计数会递增 1。与骨架图像中黑色像素的数量进行对比,计算匹配百分比,选择具有最高匹配百分比的字符就是识别结果的字符。

最终结果如图16所示,若得到的字符为 Z5M3MQ,则验证码被成功识别出来了。

拒绝反爬虫!教你搞定爬虫验证码

图16 识别结果

本文摘编于《Python网络爬虫技术与实践》,经出版方授权发布。

**-----**------**-----**---**** 送书 **-----**--------**-----**-****

内容简介

欢迎各位大佬点击链接加入群聊【helloworld开发者社区】:https://jq.qq.com/?_wv=1027&k=mBlk6nzX进群交流IT技术热点。

本文转自 https://mp.weixin.qq.com/s/viWLy-aYUAIjat1VCi63kA,如有侵权,请联系删除。

点赞
收藏
评论区
推荐文章
浅梦一笑 浅梦一笑
2个月前
初学 Python 需要安装哪些软件?超级实用,小白必看!
编程这个东西是真的奇妙。对于懂得的人来说,会觉得这个工具是多么的好用、有趣,而对于小白来说,就如同大山一样。其实这个都可以理解,大家都是这样过来的。那么接下来就说一下python相关的东西吧,并说一下我对编程的理解。本人也是小白一名,如有不对的地方,还请各位大神指出01名词解释:如果在编程方面接触的比较少,那么对于软件这一块,有几个名词一定要了解,比如开发环
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
1年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
Wesley13 Wesley13
1年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
1年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序selectfromtable_nameorderiddesc;2.按照指定(多个)字段排序selectfromtable_nameorderiddesc,statusdesc;3.按照指定字段和规则排序selec
Wesley13 Wesley13
1年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
helloworld_34035044 helloworld_34035044
5个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为