返回介绍

7.3 处理复杂验证码

发布于 2024-02-05 23:37:18 字数 8726 浏览 0 评论 0 收藏 0

前面用于测试的验证码系统相对来说比较容易处理,因为文本使用的黑色字体与背景很容易区分,而且文本是水平的,无须旋转就能被Tesseract准确解析。一般情况下,网站使用的都是类似这种比较简单的通用验证码系统,此时可以使用OCR方法。但是,如果网站使用的是更加复杂的系统,比如Google的reCAPTCHA,OCR方法则需要花费更多努力,甚至可能无法使用。图7.5所示为网络上找到的一些更加复杂的验证码图像。

图7.5

在这些例子中,因为文本被置于不同的角度,并且拥有不同的字体和颜色,所以要使用OCR方法的话,需要更多工作来清理这些噪音。即使是真实人类,解析这些图像也可能会存在困难,尤其是对于那些视觉障碍人士而言。

7.3.1 使用验证码处理服务

为了处理这些更加复杂的图像,我们将使用验证码处理服务 [2] 。验证码处理服务有很多,比如2captcha.com 和deathbycaptcha.com ,一般其服务价位在1美元1000个验证码图像左右。当把验证码图像传给它们的API时,会有人进行人工查看,并在HTTP响应中给出解析后的文本。一般来说该过程在30秒以内。在本节的示例中,我们将使用9kw.eu 的服务。虽然该服务没有提供最便宜的验证码处理价格,也没有最好的API设计,但是使用该API可能不需要花钱。这是因为9kw.eu 允许用户人工处理验证码以获取积分,然后花费这些积分处理我们的验证码。

7.3.2 9kw入门

要想开始使用9kw,首先需要创建一个账号,注册网址为https://www. 9kw.eu/register.html ,注册界面如图7.6所示。

图7.6

然后,按照账号确认说明进行操作。登录后,我们被定位到https://www. 9kw.eu/usercaptcha.html ,其页面显示如图7.7所示。

图7.7

在本页中,需要处理其他用户的验证码来获取后面使用API时所需的积分。在处理了几个验证码之后,会被定位到https://www.9kw.eu/index. cgi?action=userapinew&source=api 来创建API key。

9kw验证码API

9kw的API文档地址为https://www.9kw.eu/api.html#apisubmit-tab。我们用于提交验证码和检查结果的主要部分总结如下。

提交验证码

URL :https://www.9kw.eu/index.cgi (POST)

apikey :你的API key

action :必须设为“usercaptchaupload”

file-upload-01 :需要处理的图像(文件、url或字符串)

base64 :如果输入是Base64编码,则设为“1”

maxtimeout :等待处理的最长时间(必须为60~3999秒)

selfsolve :如果自己处理该验证码,则设为“1”

返回值 :该验证码的ID

请求已提交验证码的结果

URL :https://www.9kw.eu/index.cgi (GET)

apikey :你的API key

action :必须设为“usercaptchacorrectdata”

id :要检查的验证码ID

info :若设为1,没有得到结果时返回“NO DATA”(默认返回空)

返回值 :要处理的验证码文本或错误码

错误码

0001 API key不存在

0002 没有找到API key

0003 没有找到激活的API key

……

0031 账号被系统禁用24小时

0032 账号没有足够的权限

0033 需要升级插件

下面是发送验证码图像到该API的初始实现代码。

import urllib
import urllib2
API_URL = 'https://www.9kw.eu/index.cgi'

def send_captcha(api_key, img_data):
    data = {
        'action': 'usercaptchaupload',
        'apikey': api_key,
        'file-upload-01': img_data.encode('base64'),
        'base64': '1',
        'selfsolve': '1',
        'maxtimeout': '60'
    }
    encoded_data = urllib.urlencode(data)
    request = urllib2.Request(API_URL, encoded_data)
    response = urllib2.urlopen(request)
    return response.read()

这个结构应该看起来很熟悉,首先我们创建了一个所需参数的字典,对其进行编码,然后将其作为请求体提交。需要注意的是,这里将selfsolve 选项设为'1' ,这种设置下,如果我们正在使用9kw的Web界面处理验证码,那么验证码图像就会传给我们自己处理,从而可以节约我们的积分。如果此时我们没有处于登录状态,验证码则会像平时一样传给其他用户。

下面是获取验证码图像处理结果的代码。

def get_captcha(api_key, captcha_id):
    data = {
        'action': 'usercaptchacorrectdata',
        'id': captcha_id,
        'apikey': api_key
    }
    encoded_data = urllib.urlencode(data)
    # note this is a GET request
    # so the data is appended to the URL
    response = urllib2.urlopen(API_URL + '?' + encoded_data)
    return response.read()

9kw的API有一个缺点是其响应是普通字符串,而不是类似JSON的结构化格式,这样就会使错误信息的区分更加复杂。例如,此时没有用户处理验证码图像,则会返回ERROR NO USER 字符串。不过幸好我们提交的验证码图像永远不会包含这类文本。

另一个困难是,只有在其他用户有时间人工处理验证码图像时,get_captcha() 函数才能返回错误信息,正如之前提到的,通常要在30秒之后。为了使实现更加友好,我们将会增加一个封装函数,用于提交验证码图像以及等待结果返回。下面的扩展版本代码把这些功能封装到一个可复用类当中,另外还增加了检查错误信息的功能。

    import time
    import urllib
    import urllib2
    import re
    from io import BytesIO

    class CaptchaAPI:
        def __init__(self, api_key, timeout=60):
            self.api_key = api_key
            self.timeout = timeout
            self.url = 'https://www.9kw.eu/index.cgi'

        def solve(self, img):
            """Submit CAPTCHA and return result when ready
            """
            img_buffer = BytesIO()
            img.save(img_buffer, format="PNG")
            img_data = img_buffer.getvalue()
            captcha_id = self.send(img_data)
            start_time = time.time()
            while time.time() < start_time + self.timeout:
                try:
                    text = self.get(captcha_id)
                except CaptchaError:
                    pass # CAPTCHA still not ready
                else:
                    if text != 'NO DATA':
                        if text == 'ERROR NO USER':
                            raise CaptchaError('Error: no user
                                available to solve CAPTCHA')
                        else:
                            print 'CAPTCHA solved!'
                        return text
            print 'Waiting for CAPTCHA ...'

        raise CaptchaError('Error: API timeout')

    def send(self, img_data):
        """Send CAPTCHA for solving
        """
        print'Submitting CAPTCHA'
        data = {
            'action': 'usercaptchaupload',
            'apikey': self.api_key,
            'file-upload-01': img_data.encode('base64'),
            'base64': '1',
            'selfsolve': '1',
            'maxtimeout': str(self.timeout)
        }
        encoded_data = urllib.urlencode(data)
        request = urllib2.Request(self.url, encoded_data)
        response = urllib2.urlopen(request)
        result = response.read()
        self.check(result)
        return result

    def get(self, captcha_id):
        """Get result of solved CAPTCHA
        """
        data = {
            'action': 'usercaptchacorrectdata',
            'id': captcha_id,
            'apikey': self.api_key,
            'info': '1'
        }
        encoded_data = urllib.urlencode(data)
        response = urllib2.urlopen(self.url + '?' + encoded_data)
        result = response.read()
        self.check(result)
        return result

    def check(self, result):
        """Check result of API and raise error if error code
        """
        if re.match('00\d\d \w+', result):
            raise CaptchaError('API error: ' + result)

class CaptchaError(Exception):
    pass

CaptchaAPI 类的源码可以从https://bitbucket.org/wswp/ code/src/tip/chapter07/api.py 获取,该链接中的代码会在9kw.eu修改其API时保持更新。这个类使用你的API key以及超时时间进行实例化,其中超时时间默认为60秒。然后,solve() 方法把验证码图像提交给API,并持续请求,直到验证码图像处理完成或者到达超时时间。目前,检查API响应中的错误信息时,check() 方法只检查了初始字符,确认其是否遵循错误信息前包含4位数字错误码的格式。要想该API在使用时更加健壮,可以对该方法进行扩展,使其包含全部34种错误类型。

下面是使用CaptchaAPI 类处理验证码图像时的执行过程示例。

>>> API_KEY = ...
>>> captcha = CaptchaAPI(API_KEY)
>>> img = Image.open('captcha.png')
>>> text = captcha.solve(img)
Submitting CAPTCHA
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
CAPTCHA solved!
>>> text
juxhvgy

这是本章前面给出的第一个复杂验证码图像的正确识别结果。如果再次提交相同的验证码图像,则会立即返回缓存结果,并且不会再次消耗积分。

>>> text = captcha.solve(img_data)
Submitting CAPTCHA
>>> text
juxhvgy

7.3.3 与注册功能集成

目前我们已经拥有了一个可以运行的验证码API解决方案,下面我们可以将其与前面的表单进行集成。下面的代码对register 函数进行了修改,使其将处理验证码图像的函数作为参数传递进来,这样我们就可以选择使用OCR方法还是API方法了。

def register(first_name, last_name, email, password, captcha_fn):
    cj = cookielib.CookieJar()
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
    html = opener.open(REGISTER_URL).read()
    form = parse_form(html)
    form['first_name'] = first_name
    form['last_name'] = last_name
    form['email'] = email
    form['password'] = form['password_two'] = password
    img = extract_image(html)
    form['recaptcha_response_field'] = captcha_fn(img)
    encoded_data = urllib.urlencode(form)
    request = urllib2.Request(REGISTER_URL, encoded_data)
    response = opener.open(request)
    success = '/user/register' not in response.geturl()
    return success

下面是使用该函数的例子。

>>> captcha = CaptchaAPI(API_KEY)
>>> register(first_name, last_name, email, password, captcha.solve)
Submitting CAPTCHA
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
Waiting for CAPTCHA ...
True

运行成功了!我们从表单中成功获取到验证码图像,提交给9kw的API,之后其他用户人工处理了该验证码,程序将返回结果提交到Web服务器端,注册了一个新账号。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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