返回介绍

10.3 识别验证码

发布于 2024-02-05 21:13:20 字数 8032 浏览 0 评论 0 收藏 0

目前,很多网站为了防止爬虫爬取,登录时需要用户输入验证码。下面我们学习如何在爬虫程序中识别验证码(在举例过程中不指明具体网站)。

图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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文