0%

【2022 年】Python3 爬虫教程 - OCR 识别图形验证码

系列文章总目录:【2022 年】Python3 爬虫学习教程,本教程内容多数来自于《Python3 网络爬虫开发实战(第二版)》一书,目前截止 2022 年,可以将爬虫基本技术进行系统讲解,同时将最新前沿爬虫技术如异步、JavaScript 逆向、AST、安卓逆向、Hook、智能解析、群控技术、WebAssembly、大规模分布式、Docker、Kubernetes 等,市面上目前就仅有《Python3 网络爬虫开发实战(第二版)》一书了,点击了解详情

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

12306 验证码的出现使得行为验证码开始发展起来,用过 12306 的用户肯定多少为它的验证码头疼过,我们需要识别文字,点击与文字描述相符的图片,验证码完全正确,验证才能通过。随着技术的发展,现在这种交互式验证码越来越多,如滑动验证码需要将对应的滑块拖动到指定位置才能完成验证,点选验证码则需要点击正确的图形或文字才能通过验证。

验证码变得越来越复杂,爬虫的工作也变得越发艰难,有时候我们必须通过验证码的验证才可以访问页面。

本章就针对验证码的识别进行统一讲解,涉及的验证码有普通图形验证码、滑动验证码、点选验证码、手机验证码等,这些验证码识别的方式和思路各有不同,有直接使用图像处理库完成的,有的则是借助于深度学习技术完成的,有的则是借助于一些工具和平台完成的。虽然说技术各有不同,但了解这些验证码的识别方式之后,我们可以举一反三,用类似的方法识别其他类型验证码。

我们首先来看最简单的一种验证码,即图形验证码,这种验证码最早出现,现在依然也很常见,一般由 4 位左右字母或者数字组成。

例如这个案例网站 https://captcha7.scrape.center/ 就可以看到类似的验证码,如图所示:

这类验证码整体上比较规整,没有过多干扰线和干扰点,且文字没有大幅度的变形和旋转。

对于这一类的验证码我们就可以使用 OCR 技术来进行识别。

1. OCR 技术

OCR,即 Optical Character Recognition,中文翻译叫做光学字符识别。它是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。OCR 现在已经广泛应用于生产生活中,如文档识别、证件识别、字幕识别、文档检索等等。当然对于本节所述的图形验证码的识别也没有问题。

本节我们会以当前示例网站的验证码为例来讲解利用 OCR 来识别图形验证码的流程,输入上是一上图验证码的图片,输出就是验证码识别结果。

2. 准备工作

识别图形验证码需要 Tesserocr 库,本库的安装相对没有那么简单,可以参考 https://setup.scrape.center/tesserocr

另外在本节学习过程中还需要安装 Selenium、Pillow、Numpy,Retrying 库用作模拟登录、图像处理和操作重试,我们可以使用 pip3 来进行安装:

1
pip3 install selenium pillow numpy retrying

如果某个库安装有问题,可以参考如下链接:

安装好了如上库之后,我们就可以开始本节的学习了。

3. 获取验证码

为了便于实验,我们先将验证码的图片保存到本地。

我们可以在浏览器中打开上述示例网站,然后右键点击这张验证码图片,将其保存到本地,命名为 captcha.png,示例如图所示:

这样我们就可以得到一张验证码图片,以供测试识别使用。

4. 识别测试

接下来新建一个项目,将验证码图片放到项目根目录下,用 tesserocr 库识别该验证码,代码如下所示:

1
2
3
4
5
6
import tesserocr
from PIL import Image

image = Image.open('captcha.png')
result = tesserocr.image_to_text(image)
print(result)

在这里我们新建了一个 Image 对象,调用了 tesserocr 的 image_to_text方法。传入该 Image 对象即可完成识别,实现过程非常简单,结果如下所示:

1
d241

另外,tesserocr 还有一个更加简单的方法,这个方法可直接将图片文件转为字符串,代码如下所示:

1
2
import tesserocr
print(tesserocr.file_to_text('captcha.png'))

可以得到同样的输出结果。

这时候我们可以看到,通过 OCR 技术我们便可以成功识别出验证码的内容了。

5. 验证码处理

接下来我们换一个验证码,将其命名为 captcha2.png,如图所示。

重新用下面的代码来测试:

1
2
3
4
5
6
import tesserocr
from PIL import Image

image = Image.open('captcha2.png')
result = tesserocr.image_to_text(image)
print(result)

可以看到如下输出结果:

1
-b32d

这次识别和实际结果有偏差,多了一些干扰结果,这是因为验证码内的多余的点干扰了图像的识别,导致出现了一些多余的内容。

对于这种情况,我们可以需要做一下额外的处理,把一些干扰信息去掉。

这里观察到图片里面其实有一些杂乱的点,而这些点的颜色大都比文本更浅一点,因此我们可以做一些预处理,将干扰的点通过颜色来排除掉。

我们可以首先将原来的图像转化为数组看下维度:

1
2
3
4
5
6
7
import tesserocr
from PIL import Image
import numpy as np

image = Image.open('captcha2.png')
print(np.array(image).shape)
print(image.mode)

运行结果如下:

1
2
(38, 112, 4)
RGBA

可以发现这个图片其实是一个三维数组,前两维 38 和 112 代表其高和宽,最后一维 4 则是每个像素点的表示向量。为什么是 4 呢,因为最后一维是一个长度为 4 的数组,分别代表 R(红色)、G(绿色)、B(蓝色)、A(透明度),即一个像素点有四个数字表示。那为什么是 RGBA 四个数字而不是 RGB 或其他呢?这是因为 image 的模式 mode 是 RGBA,即有透明通道的真彩色,我们看到第二行输出也印证了这一点。

模式 mode 定义了图像的类型和像素的位宽,一共有 9 种类型:

  • 1:像素用 1 位表示,Python 中表示为 True 或 False,即二值化。
  • L:像素用 8 位表示,取值 0-255,表示灰度图像,数字越小,颜色越黑。
  • P:像素用 8 位表示,即调色板数据。
  • RGB:像素用 3x8 位表示,即真彩色。
  • RGBA:像素用 4x8 位表示,即有透明通道的真彩色。
  • CMYK:像素用 4x8 位表示,即印刷四色模式。
  • YCbCr:像素用 3x8 位表示,即彩色视频格式。
  • I:像素用 32 位整型表示。
  • F:像素用 32 位浮点型表示。

为了方便处理,我们可以将 RGBA 模式转为更简单的 L 模式,即灰度图像。

我们可以利用 Image 对象的 convert 方法参数传入 L,即可将图片转化为灰度图像,代码如下所示:

1
2
image = image.convert('L')
image.show()

或者传入 1 即可将图片进行二值化处理,如下所示:

1
2
image = image.convert('1')
image.show()

在这里我们就转为灰度图像,然后根据阈值筛选掉图片中的干扰点,代码如下:

1
2
3
4
5
6
7
8
9
10
from PIL import Image
import numpy as np

image = Image.open('captcha2.png')
image = image.convert('L')
threshold = 50
array = np.array(image)
array = np.where(array > threshold, 255, 0)
image = Image.fromarray(array.astype('uint8'))
image.show()

在这里,变量 threshold 代表灰度的阈值,这里设置为 50。接着我们将图片 image 转化为了 Numpy 数组,接着利用 Numpy 的 where 方法对数组进行筛选和处理,这里指定了大于阈值的就设置为 255,即白色,否则就是 0,即黑色。

最后看下图片处理完之后是什么结果:

我们发现原来验证码中的很多点已经被去掉了,整个验证码变得黑白分明。这时重新识别验证码,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
import tesserocr
from PIL import Image
import numpy as np

image = Image.open('captcha2.png')
image = image.convert('L')
threshold = 50
array = np.array(image)
array = np.where(array > threshold, 255, 0)
image = Image.fromarray(array.astype('uint8'))
print(tesserocr.image_to_text(image))

即可发现运行结果变成如下所示:

1
b32d

所以,针对一些有干扰的图片,我们可以做一些去噪处理,这会提高图片识别的正确率。

6. 识别实战

最后,我们可以来尝试下用自动化的方式来对案例进行验证码识别处理,这里我们使用 Selenium 来完成这个操作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import time
import re
import tesserocr
from selenium import webdriver
from io import BytesIO
from PIL import Image
from retrying import retry
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
import numpy as np


def preprocess(image):
image = image.convert('L')
array = np.array(image)
array = np.where(array > 50, 255, 0)
image = Image.fromarray(array.astype('uint8'))
return image


@retry(stop_max_attempt_number=10, retry_on_result=lambda x: x is False)
def login():
browser.get('https://captcha7.scrape.center/')
browser.find_element_by_css_selector('.username input[type="text"]').send_keys('admin')
browser.find_element_by_css_selector('.password input[type="password"]').send_keys('admin')
captcha = browser.find_element_by_css_selector('#captcha')
image = Image.open(BytesIO(captcha.screenshot_as_png))
image = preprocess(image)
captcha = tesserocr.image_to_text(image)
captcha = re.sub('[^A-Za-z0-9]', '', captcha)
browser.find_element_by_css_selector('.captcha input[type="text"]').send_keys(captcha)
browser.find_element_by_css_selector('.login').click()
try:
WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.XPATH, '//h2[contains(., "登录成功")]')))
time.sleep(10)
browser.close()
return True
except TimeoutException:
return False


if __name__ == '__main__':
browser = webdriver.Chrome()
login()

在这里我们首先定义了一个 preprocess 方法,用于验证码的噪声处理,逻辑就和前面说的是一样的。

接着我们定义了一个 login 方法,其逻辑执行步骤是:

  • 打开样例网站
  • 找到用户名输入框,输入用户名
  • 找到密码输入框,输入密码
  • 找到验证码图片并截取,转化为 Image 对象
  • 预处理验证码,去除噪声
  • 对验证码进行识别,得到识别结果
  • 识别结果去除一些非字母和数字字符
  • 找到验证码输入框,输入验证码结果
  • 点击登录按钮
  • 等待「登录成功」字样的出现,如果出现则证明登录成功,否则重复以上步骤重试。

在这里我们还用到了 retrying 来指定了重试条件和重试次数,以保证在识别出错的情况下反复重试,增加总的成功概率。

运行代码我们可以观察到浏览器弹出并执行以上流程,可能重试几次后得到登录成功的页面,运行过程如图所示:

登录成功后的结果如图所示:

到这里,我们就能成功通过 OCR 技术识别成功验证码,并将其应用到模拟登录的过程中了。

7. 总结

本节我们了解了利用 Tesserocr 识别验证码的过程并将其应用于实战案例中实现了模拟登录。为了提高 Tesserocr 的识别准确率,我们可以对验证码图像进行预处理去除一些干扰,识别准确率会大大提高。但总归来说 Tesserocr 识别验证码的准确率并不是很高,下一节我们来介绍其他识别验证码的方案。

本节代码:https://github.com/Python3WebSpider/CrackImageCaptcha

本文参考资料: