第三节–验证码识别

一.验证码类型

在开发爬虫时,经常会遇到验证码识别,在网站中加入验证码的目的是加强用户安全性和提高防爬虫机制,有效防止对某一个特定注册用户用特定程序暴力破解的方式不断地进行登录尝试。在此介绍验证码的种类:

  • 字符验证码:在图片上随机产生数字,英文字母或汉字,一般有4位或者6位验证码字符。通过添加干扰线,添加噪点以及增加字符的粘连程序或旋转角度来增加机器识别的难度
  • 图片验证码:图片验证码也只是换汤不换药,引用了字符验证码的技术,只是不是随机的字符,而是让人识别图片
  • GIF动画验证码:Gif动态的验证码图片,使得识别器不容易辨识哪一个图层是真正的验证码图片,在提供清晰图片的同时,可以更有效地防止识别器的识别
  • 极验验证码:基于行为式验证技术,通过拖动滑块完成拼图的形式实现验证
  • 手机验证码:通过短信的形式发送到用户手机上面的验证码
  • 语音验证码:也属于手机端验证的一种方式
  • 视频验证码:视频中的验证码字母,数字组合,字体的形状,大小,速度的快慢,显示效果和轨迹的动态变换,增加了恶意抓屏破解的难度

现在大多数网站还使用字符验证码,主要用于用户登录。有些网站数据需要用户登录才有访问权限,爬取这样的数据时,首先要完成用户登录,获取访问权限才能继续下一步的数据爬取

对于用户登录设置验证码识别的网站有三种解决方案:

  1. 人工识别验证码。将验证码图片下载到本地,然后考使用者自行识别并将识别内容输入,程序获取输入内容,完成用户登录。其特点是开发简单,适合初学者,但过分依赖人为控制,难以实现批量爬取
  2. 通过python调用OCR引擎识别验证码。这是最理想的解决方案,但正常情况下,OCR准确率较低,需要机器学习不断提高OCR准确率,开发成本相对较高
  3. 调用API使用第三方平台识别验证码。开发成本较低,有完善的API接口,直接调用即可,识别准确率高,但每次识别收取小额费用

二.OCR技术

OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗,亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程,即针对印刷体字符,采用光学的方式将纸质文档中的文字转换成为黑白点阵的图像文件,并通过识别软件将图像中的文字转换成文本格式,供文字处理软件进一步编辑加工的技术

在python中,支持OCR的模块有pytesser3pyocr,其原理主要是通过模块功能调用OCR引擎识别图片,OCR引擎再将识别的结果返回到程序中

Tesseract-OCR是一个免费,开源的OCR引擎,读者可从网上自行搜索下载安装

在python中使用pyocr实现OCR识别,方法如下:



创建OCR文件夹,在该文件夹下创建ocr.py文件和图片a.png



打开ocr.py文件,输入代码:



form PIL import Image
from pyocr import tesseract
# 使用PIL打开图片
im=Image.open('a.png')
# OCR识别
code=tesseract.iamge_to_string(im)



运行ocr.py,运行结果如下

在实际使用时,验证码图片不会是一张白底黑字的图片,往往会掺入很多干扰因素,这样会导致识别出来的结果与实际想差甚大。为了提高准确率,可以使用PIL模块对图片进行简单的处理

from PIL import Image
from pyocr import tesseract

pic_list = ['pic1.png','pic2.png']
for i in pic_list:
    im = Image.open(i)
    im = im.convert('L')# 图片转换为灰色图像
    # 保存转换后的图片
    im.save("temp.png")
    code = tesseract.image_to_string(im)
    print(code)

PIL模块打开图片并生成图片对象im,然后转换图片颜色模式,将带有颜色的图片转换成灰度模式,形成黑–>灰–>白的过渡,如同黑白照片,最后交给OCR引擎识别并返回识别结果

补充说明:

颜色模式是将某种颜色表现为数字形式的模型,或者是一种记录图像颜色的方式,分为RGB模式CMYK模式HSB模式Lab颜色模式位图模式灰度模式索引颜色模式双色调模式多通道模式

三.第三方平台

除了使用OCR识别验证码之外,还可以利用第三方平台实现验证码的识别。到目前为止,这是解决验证码最快,最简单的途径,而且有完善的API接口,能帮助开发者完成快速开发的需求,但每次调取API接口需要收取少量费用

验证码识别平台主要有:

  • 打码平台:主要有在线人员识别验证码。开发者只需调用平台API接口,一般在10秒内返回结果。识别错误或者无法识别不收费
  • AI开发者平台:主要有人工智能系统识别,准确率取决于系统的智能程度。调用API接口每天有免费使用次数,也可以付费使用。目前,主流平台有百度AI和腾讯AI

以打码平台为例,在浏览器上访问http://www.yundama.com/,注册账号后充值就能调用API接口识别验证码,在平台上提供开发文档,代码如下:

import json
import time
import requests

class YDMHttp:
    apiurl = 'http://api.yundama.com/api.php'
    username = ''
    password = ''
    appid = '4055'
    appkey = 'c5e26d1a207df586d7aaec21522dd446'

    def __init__(self, name, passwd, app_id, app_key):
        self.username = name
        self.password = passwd
        self.appid = str(app_id)
        self.appkey = app_key

    def request(self, fields, files=[]):
        response = self.post_url(self.apiurl, fields, files)
        response = json.loads(response)
        return response

    def balance(self):
        data = {
            'method': 'balance',
            'username': self.username,
            'password': self.password,
            'appid': self.appid,
            'appkey': self.appkey
        }
        response = self.request(data)
        if response:
            if response['ret'] and response['ret'] < 0:
                return response['ret']
            else:
                return response['balance']
        else:
            return -9001

    def login(self):
        data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey}
        response = self.request(data)
        if response:
            if response['ret'] and response['ret'] < 0:
                return response['ret']
            else:
                return response['uid']
        else:
            return -9001

    def upload(self, filename, codetype, timeout):
        data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)}
        file = {'file': filename}
        response = self.request(data, file)
        if response:
            if response['ret'] and response['ret'] < 0:
                return response['ret']
            else:
                return response['cid']
        else:
            return -9001

    def result(self, cid):
        data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey, 'cid': str(cid)}
        response = self.request(data)
        return response and response['text'] or ''

    def decode(self, file_name, code_type, time_out):
        cid = self.upload(file_name, code_type, time_out)
        if cid > 0:
            for i in range(0, time_out):
                result = self.result(cid)
                if result != '':
                    return cid, result
                else:
                    time.sleep(1)
            return -3003, ''
        else:
            return cid, ''

    def post_url(self, url, fields, files=[]):
        for key in files:
            files[key] = open(files[key], 'rb')
        res = requests.post(url, files=files, data=fields)
        return res.text


def code_verificate(name, passwd, file_name, app_id=4055, app_key='c5e26d1a207df586d7aaec21522dd446',
                    code_type=1005, time_out=60):
    """
    :param name: 云打码注册用户名,这是普通用户注册,而非开发者用户注册名
    :param passwd: 用户密码
    :param file_name: 需要识别的图片名
    :param app_id: 软件ID,这里默认是填的我的,如果需要,可以自行注册一个开发者账号,填自己的。
    软件开发者会有少额提成,希望大家支持weibospider的发展(提成的20%会给celery项目以支持其发展)
    :param app_key: 软件key,这里默认是填的我的,如果需要,可以自行注册一个开发者账号,填自己的
    :param code_type: 1005表示五位字符验证码。价格和验证码类别,详细请看http://www.yundama.com/price.html
    :param time_out: 超时时间
    :return: 验证码结果
    """
    yundama_obj = YDMHttp(name, passwd, app_id, app_key)
    cur_uid = yundama_obj.login()
    print('uid: %s' % cur_uid)
    rest = yundama_obj.balance()
    print('balance: %s' % rest)

    # 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
    cid, result = yundama_obj.decode(file_name, code_type, time_out)
    print('cid: %s, result: %s' % (cid, result))
    return result

if __name__ == '__main__':
    # 云打码注册的登录用户名(通过用户注册)
    username = 'xxx'
    # 登录密码
    password = 'xxx'
    rs = code_verificate(username, password, 'pincode.png')