赞
踩
各类网站采用了各种各样的措施来反爬虫,其中一个措施便是使用验证码。随着技术的发展,验证码的花样越来越多。验证码最初是几个数字组合的简单的图形,后来加入了英文字母和混淆曲线。还有一些网站使用了中文字符验证码,这使得识别愈发困难。
12306 验证码的出现使得行为验证码开始发展起来,用过 12306 的用户肯定多少为它的验证码头疼过,我们需要识别文字,点击与文字描述相符的图片,验证码完全正确,验证才能通过。随着技术的发展,现在这种交互式验证码越来越多,如滑动验证码需要将对应的滑块拖动到指定位置才能完成验证,点选验证码则需要点击正确的图形或文字才能通过验证。
验证码变得越来越复杂,爬虫的工作也变得越发艰难,有时候我们必须通过验证码的验证才可以访问页面。
涉及的验证码有普通图形验证码、滑动验证码、点选验证码、手机验证码等,这些验证码识别的方式和思路各有不同,有直接使用图像处理库完成的,有的则是借助于深度学习技术完成的,有的则是借助于一些工具和平台完成的。虽然说技术各有不同,但了解这些验证码的识别方式之后,我们可以举一反三,用类似的方法识别其他类型验证码,本章就针对普通图形验证码的识别进行讲解。
我们首先来看最简单的一种验证码,即图形验证码,这种验证码最早出现,现在依然也很常见,一般由 4 位左右字母或者数字组成。
例如这个案例网站 https://captcha7.scrape.center/ 就可以看到类似的验证码,如图所示:
这类验证码整体上比较规整,没有过多干扰线和干扰点,且文字没有大幅度的变形和旋转。
对于这一类的验证码我们就可以使用 OCR 技术来进行识别。
OCR,即 Optical Character Recognition,中文翻译叫做光学字符识别。它是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。OCR 现在已经广泛应用于生产生活中,如文档识别、证件识别、字幕识别、文档检索等等。当然对于本节所述的图形验证码的识别也没有问题。
本节我们会以当前示例网站的验证码为例来讲解利用 OCR 来识别图形验证码的流程,输入上是一上图验证码的图片,输出就是验证码识别结果。
识别图形验证码需要 Tesserocr 库,本库的安装相对没有那么简单,可以参考 https://setup.scrape.center/tesserocr
另外在本节学习过程中还需要安装 Selenium、Pillow、Numpy,Retrying 库用作模拟登录、图像处理和操作重试,我们可以使用 pip3 来进行安装:
pip3 install selenium pillow numpy retrying
如果某个库安装有问题,可以参考如下链接:
安装好了如上库之后,我们就可以开始本节的学习了。
为了便于实验,我们先将验证码的图片保存到本地。
我们可以在浏览器中打开上述示例网站,然后右键点击这张验证码图片,将其保存到本地,命名为 captcha.png,示例如图所示:
这样我们就可以得到一张验证码图片,以供测试识别使用。
接下来新建一个项目,将验证码图片放到项目根目录下,用 tesserocr 库识别该验证码,代码如下所示:
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 对象即可完成识别,实现过程非常简单,结果如下所示:
d241
另外,tesserocr 还有一个更加简单的方法,这个方法可直接将图片文件转为字符串,代码如下所示:
import tesserocr
print(tesserocr.file_to_text('captcha.png'))
可以得到同样的输出结果。
这时候我们可以看到,通过 OCR 技术我们便可以成功识别出验证码的内容了。
接下来我们换一个验证码,将其命名为 captcha2.png,如图所示。
重新用下面的代码来测试:
import tesserocr
from PIL import Image
image = Image.open('captcha2.png')
result = tesserocr.image_to_text(image)
print(result)
可以看到如下输出结果:
-b32d
这次识别和实际结果有偏差,多了一些干扰结果,这是因为验证码内的多余的点干扰了图像的识别,导致出现了一些多余的内容。
对于这种情况,我们可以需要做一下额外的处理,把一些干扰信息去掉。
这里观察到图片里面其实有一些杂乱的点,而这些点的颜色大都比文本更浅一点,因此我们可以做一些预处理,将干扰的点通过颜色来排除掉。
我们可以首先将原来的图像转化为数组看下维度:
import tesserocr
from PIL import Image
import numpy as np
image = Image.open('captcha2.png')
print(np.array(image).shape)
print(image.mode)
运行结果如下:
(38, 112, 4)
RGBA
可以发现这个图片其实是一个三维数组,前两维 38 和 112 代表其高和宽,最后一维 4 则是每个像素点的表示向量。为什么是 4 呢,因为最后一维是一个长度为 4 的数组,分别代表 R(红色)、G(绿色)、B(蓝色)、A(透明度),即一个像素点有四个数字表示。那为什么是 RGBA 四个数字而不是 RGB 或其他呢?这是因为 image 的模式 mode 是 RGBA,即有透明通道的真彩色,我们看到第二行输出也印证了这一点。
模式 mode 定义了图像的类型和像素的位宽,一共有 9 种类型:
为了方便处理,我们可以将 RGBA 模式转为更简单的 L 模式,即灰度图像。
我们可以利用 Image 对象的 convert 方法参数传入 L,即可将图片转化为灰度图像,代码如下所示:
image = image.convert('L')
image.show()
或者传入 1 即可将图片进行二值化处理,如下所示:
image = image.convert('1')
image.show()
在这里我们就转为灰度图像,然后根据阈值筛选掉图片中的干扰点,代码如下:
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,即黑色。
最后看下图片处理完之后是什么结果:
我们发现原来验证码中的很多点已经被去掉了,整个验证码变得黑白分明。这时重新识别验证码,代码如下所示:
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))
即可发现运行结果变成如下所示:
b32d
所以,针对一些有干扰的图片,我们可以做一些去噪处理,这会提高图片识别的正确率。
最后,我们可以来尝试下用自动化的方式来对案例进行验证码识别处理,这里我们使用 Selenium 来完成这个操作,代码如下:
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 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 方法,其逻辑执行步骤是:
在这里我们还用到了 retrying 来指定了重试条件和重试次数,以保证在识别出错的情况下反复重试,增加总的成功概率。
运行代码我们可以观察到浏览器弹出并执行以上流程,可能重试几次后得到登录成功的页面,运行过程如图所示:
登录成功后的结果如图所示:
到这里,我们就能成功通过 OCR 技术识别成功验证码,并将其应用到模拟登录的过程中了。
本节我们了解了利用 Tesserocr 识别验证码的过程并将其应用于实战案例中实现了模拟登录。为了提高 Tesserocr 的识别准确率,我们可以对验证码图像进行预处理去除一些干扰,识别准确率会大大提高。但总归来说 Tesserocr 识别验证码的准确率并不是很高。
本节代码:https://github.com/Python3WebSpider/CrackImageCaptcha
本文参考资料:
验证码识别的一般思路为:
所谓降噪就是把不需要的信息通通去除,比如背景,干扰线,干扰像素等等,只剩下需要识别的文字,让图片变成2进制点阵最好。
对于彩色背景的验证码:每个像素都可以放在一个5维的空间里,这5个维度分别是,X,Y,R,G,B
,也就是像素的坐标和颜色,在计算机图形学中,有很多种色彩空间,最常用的比如RGB,印刷用的CYMK,还有比较少见的HSL或者HSV,每种色彩空间的维度都不一样,但是可以通过公式互相转换。在RGB空间中不好区分颜色,可以把色彩空间转换为HSV或HSL。
色彩空间参见 http://baike.baidu.com/view/3427413.htm
验证码图片7039.jpg
:
1、导入Image包,打开图片:
from PIL import Image
im = Image.open('7039.jpg')
2、把彩色图像转化为灰度图像。RBG转化到HSI彩色空间,采用I分量:
imgry = im.convert('L')
imgry.show()
灰度看起来是这样的:
3、二值化处理
二值化是图像分割的一种常用方法;在二值化图象的时候把大于某个临界灰度值的像素灰度设为灰度极大值,把小于这个值的像素灰度设为灰度极小值,从而实现二值化(一般设置为0-1);根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值,这里选用比较简单的固定阈值。
把像素点大于阈值的设置,1,小于阈值的设置为0。生成一张查找表,再调用point()进行映射。
threshold = 140
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
out = imgry.point(table, '1')
out.show()
或者用匿名函数
from PIL import Image
im = Image.open("7364.jpg")
im_gary = im.point(lambda x: 0 if x<143 else 255) #二值化处理
im_gary.show()
处理结果看起来是这样的:
识别验证码的重点和难点就在于能否成功分割字符,对于颜色相同又完全粘连的字符,比如google的验证码,目前是没法做到5%以上的识别率的。不过google的验证码基本上人类也只有30%的识别率。本文使用的验证码例子比较容易识别。可以不用切割,有关图片切割的方法参见这篇博客:http://www.cnblogs.com/apexchu/p/4231041.html
pytesser是谷歌OCR开源项目的一个模块,在python中调用pytesser模块,pytesser又用tesseract识别图片中的文字。
链接:https://code.google.com/p/pytesser/
简单识别安装
pytesser
包解压到python目录的Lib/site_packages
里面,名字取为pytesser
,pytesser.pth
文件,内容为pytesser
或者新建一个__init__.py
空白文件,pytesser.py
,把第一句的import Image
修改为from PIL import Image
,这一步的原因是这里我们用的是pillow而不是用的原生PIL。tesseract.exe
和英语的数据包(默认只识别英文),还有一些示例图片,所以解压缩后即可使用。这样做好以后记得把**pytesser
这个目录放入到系统环境变量**,因为程序会调用这个目录里面的tesseract.exe
,如果不放到环境变量会因为找不到这个文件而抛出异常。
简单识别代码
# -*- coding:utf-8 -*- from PIL import Image from pytesser import pytesser im = Image.open(r'C:\Users\Administrator\Desktop\1.png') imgry = im.convert('L') # imgry.show() threshold = 140 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) out = imgry.point(table, '1') out.show() text = pytesser.image_to_string(out) # 将图片转成字符串 print text.replace(' ', '').replace('\n', '') #这里因为识别出来的文字可能会有空格和回车
复杂识别安装
Libsite-packages
下,并将其添加到path
环境变量中,不然在导入模块时会出错。另外如果现在都是从PIL库中运入Image,没有使用Image模块,所以需要把pytesser.py
中的import Image
改为from PIL import Image
, 其次还需要在pytesser
文件夹中新建一个__init__.py
的空文件。
pytesser提供了两种识别图片方法,通过image对象和图片地址,代码判断如下:
from PIL import Image
from pytesser import pytesser
image = Image.open('7039.jpg')
#通过打开的文件识别
print pytesser.image_to_string(image)
#通过文件路径直接识别
print pytesser.image_file_to_string('7039.jpg')
同时pytesser还支持其他语言的识别,比如中文。具体参见:http://www.tuicool.com/articles/amQJR3
可以增强图片的显示效果,或者将其转换为黑白的,这样可以使其识别率提升不少:
from PIL import ImageEnhance
image = Image.open(r'C:\Users\Administrator\Desktop\1.png')
enhancer = ImageEnhance.Contrast(image)
image2 = enhancer.enhance(4)
可以再对image2调用 image_to_string
识别
tesseract
是一个命令行下运行的程序,参数如下:tesseract imagename outbase [-l lang] [-psm N] [configfile...]
imagename
是输入的image
的名字outbase
是输出的文本的名字,默认为outbase.txt
-l lang
是定义要识别的的语言,默认为英文通过以下步骤可以识别其他语言:
下载其他语言数据包: https://code.google.com/p/tesseract-ocr/downloads/list
将语言包放入pytesser的tessdata文件夹下,要识别其他语言只要添加一个language
参数就行了,下面是我的例子:
"""OCR in Python using the Tesseract engine from Google http://code.google.com/p/pytesser/ by Michael J.T. O'Kelly V 0.0.1, 3/10/07""" from PIL import Image import subprocess import util import errors tesseract_exe_name = 'D:\\Python2.7\\Lib\\site-packges\\pytesser\\tesseract' # Name of executable to be called at command line scratch_image_name = "temp.bmp" # This file must be .bmp or other Tesseract-compatible format scratch_text_name_root = "temp" # Leave out the .txt extension cleanup_scratch_flag = True # Temporary files cleaned up after OCR operation def call_tesseract(input_filename, output_filename, language): """Calls external tesseract.exe on input file (restrictions on types), outputting output_filename+'txt'""" args = [tesseract_exe_name, input_filename, output_filename, "-l", language] proc = subprocess.Popen(args) retcode = proc.wait() if retcode!=0: errors.check_for_errors() def image_to_string(im, cleanup = cleanup_scratch_flag, language = "eng"): """Converts im to file, applies tesseract, and fetches resulting text. If cleanup=True, delete scratch files after operation.""" try: util.image_to_scratch(im, scratch_image_name) call_tesseract(scratch_image_name, scratch_text_name_root,language) text = util.retrieve_text(scratch_text_name_root) finally: if cleanup: util.perform_cleanup(scratch_image_name, scratch_text_name_root) return text def image_file_to_string(filename, cleanup = cleanup_scratch_flag, graceful_errors=True, language = "eng"): """Applies tesseract to filename; or, if image is incompatible and graceful_errors=True, converts to compatible format and then applies tesseract. Fetches resulting text. If cleanup=True, delete scratch files after operation.""" try: try: call_tesseract(filename, scratch_text_name_root, language) text = util.retrieve_text(scratch_text_name_root) except errors.Tesser_General_Exception: if graceful_errors: im = Image.open(filename) text = image_to_string(im, cleanup) else: raise finally: if cleanup: util.perform_cleanup(scratch_image_name, scratch_text_name_root) return text if __name__=='__main__': im = Image.open('phototest.tif') text = image_to_string(im) print text try: text = image_file_to_string('fnord.tif', graceful_errors=False) except errors.Tesser_General_Exception, value: print "fnord.tif is incompatible filetype. Try graceful_errors=True" print value text = image_file_to_string('fnord.tif', graceful_errors=True) print "fnord.tif contents:", text text = image_file_to_string('fonts_test.png', graceful_errors=True) print text
在调用image_to_string
函数时,只要加上相应的language
参数就可以了,如简体中文最后一个参数即为 chi_sim
, 繁体中文chi_tra
,也就是下载的语言包的XXX.traineddata
文件的名字XXX
,如下载的中文包是 chi_sim.traineddata
, 参数就是chi_sim
。
text = image_to_string(self.im, language = 'chi_sim')
至此,图片识别就完成了,额外附加一句:有可能中文识别出来了,但是乱码,需要相应地将text转换为你所用的中文编码方式,如:text.decode("utf8")
就可以了
参考:https://cuiqingcai.com/202291.html
http://blog.csdn.net/fu_shuwu/article/details/53365132
http://www.jianshu.com/p/4a7bba756192
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。