使用打码平台识别验证码

在前面四节,我们学习了几种识别验证码的方法,这些方法或多或少存在一些缺点,例如 OCR、OpenCV 的识别正确率不高,深度学习的效果虽然还不错,但是训练和维护模型的流程相对复杂。那有没有其他识别验证码的方法呢?

有,就是本节要讲的打码平台。利用打码平台可以轻松识别各种各样的验证码,图形验证码、滑动验证码、点选验证码和逻辑推理验证码都不在话下,而且不需要懂任何算法,以及维护任何模型或服务。打码平台提供了一系列 API,只需要向 API 上传验证码图片,它便会返回对应的识别结果。

其实打码平台一般是半自动化的,也就是平台背后既有识别算法、模型的支持、也有人工打码的支持。对于普通的由数字或字母构成的图形验证码,平台背后一般有深度学习模型作为支持,不仅识别精度高而且速度快。对于一些较为复杂的、使用模型或算法难以实现或者难以达到较好效果的验证码,会转到人工处理,打码人员通过平台提供的标注工具做标注,平台再通过 API 返回标注结果。

我个人比较推荐的一个平台是超级鹰,其官网为 https://www.chaojiying.com/ ,首页如图 8-32 所示,这个平台提供的服务种类非常广泛,可识别的验证码类型非常多,识别效果也很不错。

图8-32 超级鹰平台

超级鹰平台支持识别如下内容。

  • 英文数字:英文字母和数字混合而成的内容,最多识别 20 位。

  • 中文汉字:最多识别 7 个汉字。

  • 纯英文:最多识别 12 个英文字母。

  • 纯数字:最多识别 11 位数字。

  • 任意特殊字符:不定长的汉字、英文和数字混合而成的内容,拼音首字母,计算题,成语混合,集装箱号等。

  • 问答:例如问答题、选择题、复杂计算题。

  • 坐标多选:支持二选一、多选一、多选多等,通常返回选择结果的左上角的点的坐标。

如有变动,请以官网为准: https://www.chaojiying.com/price.html

本节中我们就来学习利用打码平台识别各类验证码的流程,涉及的验证码类型有图形验证码、点选验证码、滑动验证码和问答验证码。

准备工作

本节需要用到两个 Python 库——opencv-python 和 Pillow,请确保已经正确安装,安装命令如下:

pip3 install opencv-python pillow

如果在安装过程中遇到问题,可以参考 https://setup.scrape.center/opencv-pythonhttps://setup.scrape.center/pillow

另外请在超级鹰平台上注册一个账号,并购买一定的题分,具体的操作流程见官网或者联系官方客服。

大家可以自行下载本节测试所用的验证码,地址为 https://github.com/Python3WebSpider/CaptchaPlatform 可以先复制下来:

git clone https://github.com/Python3WebSpider/CaptchaPlatform.git

复制之后,本地会出现一个 CaptchaPlatform 文件夹,该文件夹内部存放的便是本节测试所需的验证码图片。另外,文件夹中还有一个叫 chaojiying.py 的文件,这是基于官方 SDK 改写的,文件内容如下:

import requests
from hashlib import md5

class Chaojiying(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        self.password = md5(password.encode('utf-8')).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }
    def post_pic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.Net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def report_error(self, im_id):
        """
        im_id: 报错题目的图片 ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params,
                           headers=self.headers)
        return r.json()

其中定义了一个 Chaojiying 类,其构造方法接收三个参数。

  • username: 超级鹰账户的用户名。

  • password: 超级鹰账户的密码。

  • soft_id: 软件 ID, 需要到超级鹰后台的 “软件 ID” 中获取,例如图 8-33 这样,就生成了一个软件 ID: 915502。

图8-33 生成软件ID

这个类还实现了两个方法,post_pic 方法用于上传验证码并获取识别结果,report_error 方法用于上报识别错误,识别错误时不扣题分,也就是不花钱。

以上内容都准备好后,开始识别验证码,首先是图形验证码。

图形验证码

本节中我们用图 8-34 所示的图形验证码为例进行讲解,该图片被保存为 captcha1.png 文件。

图8-34 要识别的图形验证码

这是一个由英文字母和数字组合而成的验证码,一共六位,查阅价格文档 https://www.chaojiying.com/price.html ,和这个验证码相符合的描述是 “1-6位英文数字”,类型是 “1006”,如图 8-35 所示。

图8-35 价格文档

接着就可以编写实现代码:

from chaojiying import Chaojiying

"""
USERNAME, PASSWORD, SOFT_ID 需要更改为自己的用户名、密码和软件 ID
"""

USERNAME = ''
PASSWORD = ''
SOFT_ID = ''
CAPTCHA_KIND = '1006'
FILE_NAME = 'captcha1.png'
client = Chaojiying(USERNAME, PASSWORD, SOFT_ID)
result = client.post_pic(open(FILE_NAME, 'rb').read(), CAPTCHA_KIND)
print(result)

这里首先利用 USERNAME、PASSWORD 和 SOFT_ID 三个信息初始化了一个 Chaojiying 对象,赋值为 client 变量,然后调用 clientpost_pic 方法上传了图 8-34 的二进制内容,这里把 post_pic 方法的第二个参数设置为 CAPTCHA_KIND, 即 1006。

返回结果如下:

{'err_no': 0, 'err_str': 'OK', 'pic_id': '1138416360949200010', 'pic_str': '6m4nn', 'md5': '6f3f50e447fbb0b13abf828636096f94'}

可以看到,返回结果中的 pic_str 字段出现了正确的识别结果,识别成功!

点选验证码

点选验证码也是多种多样,12306 的验证码就是非常典型的一种点选验证码。本节中我们用图 8-36 所示的点选验证码为例进行讲解,该图片被保存为 captcha2.png 文件。

图8-36 要识别的点选验证码

查阅价格文档,比较符合这个验证码的描述是 “坐标多选”,类型是 “9004”,会返回 1~4 个坐标,如图 8-37 所示。

图 8-37 价格文档

将 “图形验证码” 代码中的 CAPTCHA_KIND 改成 9004, FILE_NAME 改成 captcha2.png, 然后重新运行代码,得到的结果如下:

{'err_no': 0, 'err_str': 'OK', 'pic_id': '1138416440949200011', 'pic_str': '-|08,133|227,143', 'md5': '526d232eb02afa7ed8319051a48ed7e9'}

可以看到返回结果中的 pic_str 字段变成了 108,133|227,143, 使用 OpenCV 技术在图 8-36 上标注这几个点:

import cv2

image = cv2.imread('captcha2.png')
image = cv2.circle(image, (108, 133), radius=10, color=(0, 0, 255), thickness=-1)
image = cv2.circle(image, (227, 143), radius=10, color=(0, 0, 255), thickness=-1)
cv2.imwrite('captcha2_label.png', image)

运行结果如图 8-38 所示。

图8-38 在 captcha2.png 上标注出返回坐标对应的点

可以看到标注出来的正是第 1 张和第 2 张图片,没问题,验证成功!

另外,还有一些验证码也属于点选类型,例如指定点击物品的颜色的验证码,如图 8-39 所示。指定文字点击顺序的验证码,如图 8-40 所示。

要求按照语序点击文字的验证码,如图 8-41 所示。

图8-39 指定物品的颜色 图8-40 指定文字点击顺序 图8-41 要求按照语序点击文字

滑动验证码

我们再来验证滑块验证码,这里以图 8-42 所示的图片为例进行讲解,这张图片被保存为 captcha3.png 文件。

查阅价格文档,比较符合这个验证码的描述是 “坐标选一”,类型是 “9101”,如图 8-43 所示。

图 8-43 价格文档

和 “点选验证码” 类似,将 “图形验证码” 代码中的 CAPTCHA_KIND 改成 9101, FILE_NAME 改成 captcha3.png, 然后重新运行代码,得到的结果如下:

{'err_no': 0, 'err_str': 'OK', 'pic_id': '9138416550949200012', 'pic_str': '231,85', 'md5': 'ae9cba3a8bbfcd9197551dda23aa0fd7'}

可以看到返回结果中的 pic_str 字段变成了 231,85, 我们用 OpenCV 技术在图 8-42 上标注出这个点:

import cv2

image = cv2.imread('captcha3.png')
image = cv2.circle(image, (231, 85), radius=10, color=(0, 0, 255), thickness=-1)
cv2.imwrite('captcha3_label.png', image)

返回的结果如图 8-44 所示。

图8-44 在 captcha3.png 上标注出返回坐标对应的点

很遗憾,标注的点在缺口右侧的中间位置,这样不方便我们判断。造成这个结果的原因在于平台的背后是标注入员,标注入员拿到验证码图片后并不知道应该标注哪里,例如标在目标缺口的左侧还是右侧,在信息量不足的前提下,标注结果自然不一定如我们所愿。

面对这种情况,一般应该怎么做?可以在图片上做一些处理,例如添加自定义的文字,提醒标注入员哪里是正确的位置。下面使用 OpenCV 技术在图 8-42 上加一行字 “请点击目标缺口的左上角”:

import cv2
from PIL import ImageFont, ImageDraw, Image
import numpy as np
import io

def cv2_add_text(image, text, left, top, textColor=(255, 0, 0), text_size=20):
    image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(image)
    font = ImageFont.truetype('simsun.ttc', text_size, encoding="utf-8")
    draw.text((left, top), text, textColor, font=font)
    return cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)

image = cv2.imread(FILE_NAME)
image = cv2_add_text(image, '请点击目标缺口的左上角', int(image.shape[1] / 10), int(image.shape[0] / 2), (255,
0, 0), 40)
client = Chaojiying(USERNAME, PASSWORD, SOFT_ID)
result = client.post_pic(io.BytesIO(cv2.imencode('.png', image)[1]).getvalue(), CAPTCHA_KIND)
print(result)

这里我们定义了一个 cv2_add_text 方法,由于直接添加中文会产生乱码,所以需要借助 Pillow 库,并且依赖一个中文字体文件。添加文字后,图片如图 8-45 所示。

重新运行代码,得到的结果如下:

{'err_no': 0, 'err_str': 'OK', 'pic_id': '9138418320949200031', 'pic_str': '167,55', 'md5': '12aef197b545bbf05ac28c7797e5ba46'}

这时返回结果中的 pic_str 字段变成了 167,55, 标注一下这个点,结果如图 8-46 所示。

图8-45 添加提醒文字后的图片 图8-46 标注新返回的坐标点

可以看到,这下标注的位置就正确了。所以在有必要的情况下,可以对图片稍做处理,以达到更好的标注效果。

问答验证码

再看一种验证码——问答验证码,样例图片如图 8-47 所示,这张图片被保存为 captcha4.png。

图8-47 要识别的问答验证码

可以看到,验证码上有一个问题,并且在问题后的括号里有答案提示,问题中每个字的颜色、形状和字与字之间距离各不相同,背景中还有一些干扰线。

对于这种验证码,如果想自动化完成识别,难度是比较大的。首先需要识别出图片上的文字,这对正确率有很高的要求。在能正确提取所有文字并且问题相对简单的前提下,可以通过用爬虫模拟一些网络搜索操作获得结果。如果问题稍微复杂一些或者在网络上搜索不到答案,可以通过一些自然语言处理技术或者知识库获得答案。但总的来说,通过纯技术手段识别问答验证码的难度还是比较高的。

面对这样的验证码,比较合适的解决方案依然是打码平台,借助平台背后的人工力量完成识别。同样,查阅一下超级鹰平台对此类验证码的支持情况,如图 8-48 所示。

图8-48 价格文档

可以看到 6004 类型是支持此类验证码的,我们把前面代码中的 CAPTCHA_KIND 改成 6004, FILE_NAME 改成 captcha4.png, 然后重新运行代码,得到的结果如下:

{'err_no': 0, 'err_str': 'OK', 'pic_id': '9138623590949200033', 'pic_str': '大象', 'md5': 'cc2a9466c15df990c559bb7d6f0eac55'}

返回结果中的 pic_str 字段是大象,回答正确。如此看来,打码平台着实非常强大。

总结

本节中我们总结了利用打码平台识别各种验证码的方法,图形验证码、点选验证码、滑动验证码和问答验证码都不在话下,而且正确率也还不错,毕竟背后都是真实的人在操作,而且还有健壮的模型。