验证码识别

写在前面

如今,不少网站采起各类各样的措施来反爬虫,其中之一就是使用验证码。当咱们访问网页时,必须先经过验证码才可以访问页面。下面咱们便来说2种验证码的识别方式和一些思路。固然咱们也能够直接使用付费的打码平台,那样能够增长识别的准确度,毕竟出了钱的嘛。哈哈!javascript

PIL库

其实,验证码识别归根到底仍是对各类各样图片的识别和操做,python中有很对图像处理的库,其中PIL就是其中之一。 因此在处理验证码识别以前,必须先了解PIL库和tesserocr。 下面附上其API源码地址,以及对应的学习博客。
源码地址:https://pillow-cn.readthedocs.io/zh_CN/latest/reference/index.html
参考博客:https://blog.csdn.net/louishao/article/details/69879981
下面咱们就开始验证码识别之路了。html

图形验证码

以中国知网为例:
图片描述java

首先,咱们先拿到上图中绿线标记的验证码,下载到本地项目文件中,
而后,编写以下代码:python

import tesserocr
from PIL import Image

image = Image.open('image.png')
res = tesserocr.image_to_text(image)
print(res)  # F8BS

输出结果为:F8BS, 但是实际图片为F8B8,这是由于验证码内多余线条干扰了图片的识别,像这类状况,还须要作出额外的处理,好比转灰度,二值化等。固然,实际处理中并非这样,通常咱们会先对模糊图片进行灰度处理后,再设定二值化的阈值,实际处理以下:git

import tesserocr
from PIL import Image

image = Image.open('code.jpg')  # 建立image对象

image = image.convert('L')
threshold = 150  # 指定二值化阈值
table = []
for i in range(256):
    if i < threshold:
        table.append(0)
    else:
        table.append(1)

image = image.point(table, '1')
image.show()
res = tesserocr.image_to_text(image)
print(res)

输出结果:F8B8
进行识别时,先设定好二值化阈值threshold,进行适当调试,直到图片能正常识别为止。github

滑动验证码

过程分析:chrome

滑动验证码主要的验证方式是拖动滑块,拼合图像;如图象彻底拼合,则验证成功,即表单提交成功,不然须要从新验证。
如图:
图片描述canvas

下面,咱们就以极验的验证码为例,来说诉一下识别方法。
由于极验的验证码在拖动验证码后会生成一个加密的表单提交到后台,全部为了不麻烦咱们直接用selenium模拟浏览器行为来完成验证。
登录网站:极验官网浏览器

目标站点: https://account.geetest.com/l...

图片描述

首先,咱们发现登录界面有个智能按钮,通常来讲,在输入邮箱以后,点击按钮就会弹出滑动验证窗口,而后咱们在拖动验证码完成图像拼接,完成验证。
图片描述app

因此,滑块验证识别须要完成如下步骤:

  1. 模拟点击验证按钮
  2. 识别滑块的缺口位置
  3. 模拟拖动滑块

如何实现以上步骤呢?咱们先须要将任务进行分解,看似只有三大步骤,其实里面坑还有不少的,稍后会作解释。

第一步,输入帐号,获取智能按钮,使用selenium模拟点击,获取带有缺口的图片。

第二步,获取上面缺口图片中的完整图片。这里有个地方要注意,正常状况下咱们在网页源代码里是找不到完整图的,由于它被隐藏了,必须执行javascript语句才能出现完整图。

图片描述

咱们将display参数改成block,opacity参数改成1,而后进行截图,就能够拿到完整的验证码图片了。

第三步,对比两张图片的全部RGB像素点,获得缺口位置。

第四步,模拟人的拖动习惯,这里也有坑,极验的验证码增长了机器轨迹识别,匀速移动,或者随机速度移动滑块都不能经过验证,因此咱们将须要拖动的总位移分红一段一段小的轨迹,先匀加速拖动后匀减速拖动。

第五步,按照规定轨迹进行拖动,完成验证。

第六步,完成帐号登录。

过程分析完了,下面咱们就来写代码试一下:
首先,咱们先将整个代码的一个逻辑思路作一个大体的归纳吧。

def main():
    """主函数"""

    # 获取带缺口验证码图片image1, 传入的参数后缀为: .png
    image1 = get_unFull_captcha('unfull_captcha.png')
    # print(image1.load()[12,25])
    # 获取完整验证码图片image2
    image2 = get_full_captcha('full_captcha.png')
    # 对比上述图片像素点,获取缺口位置,获得偏移距离
    distance = get_quekou_distance(image1, image2)
    print('缺口偏移量:', distance)
    # 获取滑块的移动轨迹
    track = get_track(distance)
    # 模拟人的行为,拖动滑块,完成验证
    slider = get_slider()
    move(slider, track)
    success = wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
    print(success)
    if success:
        login()
    else:
        main()

接下来,咱们便来逐一完成main函数里要实现的功能了。

代码示例:
经过以上代码咱们便拿到了完整的验证码和带有缺口的验证码。
缺口图片:

def get_unFull_captcha(name):
    """
    获取带缺口验证码图片
    :return: unfull captcha
    """
    top, bottom, left, right = get_captcha_position('geetest_canvas_slice')
    print('验证码1位置:', top, bottom, left, right)
    screenshot = get_screenshot()
    unfull_captcha = screenshot.crop((left, top, right, bottom)) # 按图片位置裁剪
    unfull_captcha.save(name)     # 这里传入的name要以xxx.png命名
    return unfull_captcha

完整图片:

def get_full_captcha(name):
    """
    获取完整验证码图片
    :return: full_captcha
    """
    # 这里要执行JavaScript脚本才能拿到完整图片的截图
    show_Full_img1= "document.getElementsByClassName('geetest_canvas_fullbg')[0].style.display='block'"
    browser.execute_script(show_Full_img1)
    show_Full_img2 = "document.getElementsByClassName('geetest_canvas_fullbg')[0].style.opacity=1"
    browser.execute_script(show_Full_img2)
    # 等待完整图片加载
    time.sleep(2)
    top, bottom, left, right = get_captcha_position('geetest_canvas_fullbg')
    print('验证码2位置:', top, bottom, left, right)
    screenshot = get_screenshot()
    full_captcha = screenshot.crop((left, top, right, bottom))  # 同上
    full_captcha.save(name)
    return full_captcha

这里我在调试的时候碰到一个坑,由于chrome中,location方法不滚动,直接返回相对整个html的坐标,个人电脑是15.6寸的,显示设置上布局的缩放大小被放大到1.25倍,致使location返回的坐标与验证码的坐标有偏差。修改布局为100%后就解决了。 下面即是对比图片找出缺口位置。这里咱们须要遍历图片的坐标点,获取像素点的RGB数据。

代码示例

def get_quekou_distance(image1, image2):
    """
    对比像素点,获取缺口位置
    :param image1: 缺口图片
    :param image2: 完整图片
    :return: 缺口的偏移距离
    """
    # 缺口在滑块右侧,设定遍历初始横坐标left为59
    left = 60
    # 像素对比阈值
    threshold = 60

    for i in range(left, image2.size[0]):
        for j in range(image2.size[1]):
            rgb1 = image1.load()[i, j]
            rgb2 = image2.load()[i, j]

            res1 = abs(rgb2[0] - rgb1[0])
            res2 = abs(rgb2[1] - rgb1[1])
            res3 = abs(rgb2[2] - rgb1[2])
            if not (res1 < threshold and res2 < threshold and res3 < threshold):
                return i-7 # 返回缺口偏移距离,这里需测试几回

接下来就是获取滑块的移动路径和模拟拖动行为了。

执行代码:

def get_track(distance):
    """
    获取移动路径
    :param distance: 偏移量
    :return: track:移动轨迹
    """
    # 存放移动轨迹
    track = []
    # 当前位置
    current = 0
    # 设定加速段和减速段临界点为路径的3/4处
    mid = distance*4/5
    # 时间间隔time, 取0.2~0.3之间随机数,避免被网站识别出来
    t = random.randint(2, 3)/10
    # 初速度
    v = 0

    while current < distance:
        if current < mid:
            # 匀加速移动,加速度a
            a = 2
        else:
            a = -3
        # 初速度
        v0 = v
        # 当前速度
        v = v0 + a*t
        # 移动距离
        s = v0*t + 1/2 * a * t*t
        # 当前位移
        current += s
        # 加入到移动轨迹
        track.append(round(s))
    return track

def move(slider, track):
    """
    模拟鼠标操做,点击,移动滑块按钮
    :param: 滑块
    :param: 轨迹
    :return:
    """
    ActionChains(browser).click_and_hold(slider).perform()
    # 操做鼠标按轨迹移动
    for x in track:
        ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.3)
    # 松开
    ActionChains(browser).release().perform()

最后终于成功了,踩了这么多坑,终于完成了滑块验证码的破解。。。如今已经实现功能,由于还可能出现其余状况,接下来咱们还须要完善一下代码,其实也可封装成一个类,测试的时候咱们会发现,图片会弹出小怪兽被吃了,那是由于系统识别咱们是机器行为,因此不经过,这里咱们须要修改加速度参数,再增长一个回调。

这样咱们就成功破解验证码,并登录到网页界面了。。。忙活了一上午,吃饭去了。
好像如今极验官网改了, 可是滑动验证码思路基本上就是这样的...

源码地址:https://github.com/appleguardu/spider_projects/tree/master/Captcha

相关文章
相关标签/搜索