10.3 识别验证码
目前,很多网站为了防止爬虫爬取,登录时需要用户输入验证码。下面我们学习如何在爬虫程序中识别验证码(在举例过程中不指明具体网站)。
图10-9所示为某网站登录页面,其中包含验证码。
图10-9
页面中的验证码图片对应一个<img>元素,即一张图片,浏览器加载完登录页面后,会携带之前访问获取的Cookie信息,继续发送一个HTTP请求加载验证码图片。和账号密码输入框一样,验证码输入框也对应一个<input>元素,因此用户输入的验证码会成为表单数据的一部分,表单提交后由网站服务器程序验证。
识别验证码有多种方式,下面介绍常用的几种。
10.3.1 OCR识别
OCR是光学字符识别的缩写,用于在图像中提取文本信息,tesseract-ocr是利用该技术实现的一个验证码识别库,在Python中可以通过第三方库pytesseract调用它。下面介绍如何使用pytesseract识别验证码。
首先安装tesseract-ocr,在Ubuntu下可以使用apt-get安装:
sudo apt-get install tesseract-ocr
接下来安装pytesseract,它依赖于Python图像处理库PIL或Pillow(PIL和Pillow功能类似,任选其一),可以使用pip安装它们:
$ pip install pillow $ pip install pytesseract
现在,我们使用pytesseract识别图片code.png中的验证码,代码如下:
>>> from PIL import Image >>> import pytesseract >>> img = Image.open('code.png') >>> img = img.convert('L') >>> pytesseract.image_to_string(img) 'cqKE'
上面的代码中,先使用Image.open打开图片,为了提高识别率,调用Image对象的convert方法把图片转换为黑白图,然后将黑白图传递给pytesseract.image_to_string方法进行识别,这里我们幸运地识别成功了。经测试,此段代码对于X网站中的验证码识别率可以达到72%,这已经足够高了。
下面我们以之前的LoginSpider为模板实现一个使用pytesseract识别验证码登录的Spider:
# -*- coding: utf-8 -*- import scrapy from scrapy import Request, FormRequest import json from PIL import Image from io import BytesIO import pytesseract from scrapy.log import logger class CaptchaLoginSpider(scrapy.Spider): name = "login_captcha" start_urls = ['http://XXX.com/'] def parse(self, response): ... # X 网站登录页面的url(虚构的) login_url = 'http://XXX.com/login' user = 'liushuo@XXX.com' password = '12345678' def start_requests(self): yield Request(self.login_url, callback=self.login, dont_filter=True) def login(self, response): # 该方法既是登录页面的解析函数,又是下载验证码图片的响应处理函数 # 如果response.meta['login_response']存在,当前response 为验证码图片的响应 # 否则当前response 为登录页面的响应 login_response = response.meta.get('login_response') if not login_response: # Step 1: # 此时response 为登录页面的响应,从中提取验证码图片的url,下载验证码图片 captchaUrl = response.css('label.field.prepend-icon img::attr(src)')\ .extract_first() captchaUrl = response.urljoin(captchaUrl) # 构造Request 时,将当前response 保存到meta 字典中 yield Request(captchaUrl, callback=self.login, meta={'login_response': response}, dont_filter=True) else: # Step 2: # 此时,response 为验证码图片的响应,response.body是图片二进制数据 # login_response 为登录页面的响应,用其构造表单请求并发送 formdata = { 'email': self.user, 'pass': self.password, # 使用OCR识别 'code': self.get_captcha_by_OCR(response.body), } yield FormRequest.from_response(login_response, callback=self.parse_login, formdata=formdata, dont_filter=True) def parse_login(self, response): # 根据响应结果判断是否登录成功 info = json.loads(response.text) if info['error'] == '0': logger.info('登录成功:-)') return super().start_requests() logger.info('登录失败:-(, 重新登录...') return self.start_requests() def get_captcha_by_OCR(self, data): # OCR识别 img = Image.open(BytesIO(data)) img = img.convert('L') captcha = pytesseract.image_to_string(img) img.close() return captcha
解释上述代码如下:
login方法
带有验证码的登录,需要额外发送一个HTTP请求来获取验证码图片,这里的login方法既处理下载登录页面的响应,又处理下载验证码图片的响应。
解析登录页面时,提取验证码图片的url,发送请求下载图片,并将登录页面的Response对象保存到Request对象的meta字典中。
处理下载验证码图片的响应时,调用get_captcha_by_OCR方法识别图片中的验证码,然后将之前保存的登录页面的Response对象取出,构造FormRequest对象并提交。
get_captcha_by_OCR方法
参数data是验证码图片的二进制数据,类型为bytes,想要使用Image.open函数构造Image对象,先要把图片的二进制数据转换成某种类文件对象,这里使用BytesIO进行包裹,获得Image对象后先将其转换成黑白图,然后调用pytesseract.image_to_string方法进行识别。
parse_login方法
处理表单请求的响应。响应正文是一个json串,其中包含了用户验证的结果,先调用json.loads将正文转换为Python字典,然后依据其中error字段的值判断登录是否成功,若登录成功,则从start_urls中的页面开始爬取;若登录失败,则重新登录。
10.3.2 网络平台识别
在10.3.1小节中,使用pytesseract识别的验证码比较简单,对于某些复杂的验证码,pytesseract的识别率很低或者无法识别。目前,有很多网站专门提供验证码识别服务,可以识别较为复杂的验证码(有些是人工处理的),它们被称之为验证码识别平台,这些平台多数是付费使用的,价格大约为1元钱识别100个验证码,平台提供了HTTP服务接口,用户可以通过HTTP请求将验证码图片发送给平台,平台识别后将结果通过HTTP响应返回。
在阿里云市场可以找到很多验证码识别平台,我们随意挑选了一个,如图10-10和图10-11所示。
图10-10
图10-11
购买服务后,我们利用该平台识别图片code.gif中较为复杂的验证码:
阅读API文档,实现代码如下:
import requests import base64 from pprint import pprint # 购买服务后,平台发放给我们一个appcode,用来识别请求者的身份 APPCODE = 'f239ccawf37f287418a90e2f922649273c4' url = 'http://ali-checkcode.showapi.com/checkcode' img_data = open('code.gif', 'rb').read() form = {} # 不转换为jpg form['convert_to_jpg'] = '0' # 对图片进行base64 编码 form['img_base64'] = base64.b64encode(img_data) # 7 位汉字 form['typeId'] = '4070' # 用户验证 headers = {'Authorization': 'APPCODE ' + APPCODE} response = requests.post(url, headers=headers, data=form) pprint(response.json())
表单中3个字段含义如下:
convert_to_jpg是否将图片转换为jpg格式。
img_base64图片数据的base64编码。
typeId验证码类型,这里的'4070'代表7位汉字。
运行脚本,并观察结果:
$ python3 ali_checkcode.py {'showapi_res_body': {'Id': '4d5fea-21eb-4043-8236-d478031916', 'Result': '损俱饶现渊弹翠', 'ret_code': 0}, 'showapi_res_code': 0, 'showapi_res_error': ''}
如上所示,识别成功了。
现在,我们把使用平台识别验证码的方式也加入CaptchaLoginSpider中,只需添加一个get_captcha_by_network方法:
class CaptchaLoginSpider(scrapy.Spider): ... def get_captcha_by_OCR(self, data): # OCR识别 img = Image.open(BytesIO(data)) img = img.convert('L') captcha = pytesseract.image_to_string(img) img.close() return captcha def get_captcha_by_network(self, data): # 平台识别 import requests url = 'http://ali-checkcode.showapi.com/checkcode' appcode = 'f23cca37f287418a90e2f922649273c4' form = {} form['convert_to_jpg'] = '0' form['img_base64'] = base64.b64encode(data) form['typeId'] = '3040' headers = {'Authorization': 'APPCODE ' + appcode} response = requests.post(url, headers=headers, data=form) res = response.json() if res['showapi_res_code'] == 0: return res['showapi_res_body']['Result'] return ''
10.3.3 人工识别
最后讲解的方法听起来似乎很笨:人工识别。通常网站只需登录一次便可爬取,在其他识别方式不管用时,人工识别一次验证码也是可行的,其实现也非常简单——在Scrapy下载完验证码图片后,调用Image.show方法将图片显示出来,然后调用Python内置的input函数,等待用户肉眼识别后输入识别结果。
我们将人工识别的方式也加入CaptchaLoginSpider中,再添加一个get_captcha_by_user方法:
class CaptchaLoginSpider(scrapy.Spider): ... def get_captcha_by_OCR(self, data): # OCR识别 img = Image.open(BytesIO(data)) img = img.convert('L') captcha = pytesseract.image_to_string(img) img.close() return captcha def get_captcha_by_network(self, data): # 平台识别 import requests url = 'http://ali-checkcode.showapi.com/checkcode' appcode = 'f23cca37f287418a90e2f922649273c4' form = {} form['convert_to_jpg'] = '0' form['img_base64'] = base64.b64encode(data) form['typeId'] = '3040' headers = {'Authorization': 'APPCODE ' + appcode} response = requests.post(url, headers=headers, data=form) res = response.json() if res['showapi_res_code'] == 0: return res['showapi_res_body']['Result'] return '' def get_captcha_by_user(self, data): # 人工识别 img = Image.open(BytesIO(data)) img.show() captcha = input('输入验证码:') img.close() return captcha
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论