4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】

目前许多网站采用验证码来进行反爬虫!且花式越来越多!验证码最初是由几个数字的简单的图形验证码,后来加入英文字母和混淆曲线,设置还有中文字符的验证码,使得识别越来越难!

目前交互式验证码越来越多,如:极验滑动验证码需要滑动拼合滑块才可以完成验证,点触验证码需要完全点击正确结果才可以完成验证,另外还有滑动宫格验证码、计算题验证码等。

本帖涉及的地验证码:普通验证码、极验滑动验证码、点触验证码、微博宫格验证码。这些验证码识别的方式和思路各有不同,了解这些之后,就差不多啦~~~

一、图形验证码的识别

图形验证码一般是由4位字母或者数字组成。比如:中国知网的注册如下图,网址:http://my.cnki.net/elibregister/commonRegister.aspx

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第1张图片

措施:利用OCR技术识别图形验证码,需要安装tesserocr库。

#例子:现在保存一张验证码图片,命名为code.jpg,使用tesserocr库识别该验证码。

验证码图片为:b42b38aa0bb498fa852495ef68311332901.jpg

import tesserocr      
from PIL import Image

image=Image.open('code.jpg')
res=tesserocr.image_to_text(image)  #重要!
print(res)   #结果显示:  .1960

发现识别结果和实际情况有偏差,这是因为验证码内的多余线条干扰了图片的识别。需要对验证码图片进行额外的处理,如:灰度化、二值化等。

利用Image对象的convert()方法参数传入L,可将图片转化为灰度图像参数传入1,可将图片进行二值化处理

image=image.convert('L')  #灰度化
image=image.convert('1')  #二值化

还可以指定二值化的阈值。上面的方法(传入参数1)采用的是默认的阈值127。但是不能直接二值化原图,要先进行灰度化

image=image.convert('L')  #灰度化
threshold=127  #阈值
table=[]    
for i in range(256):
    if i#二值化  【自己设置阈值】   此时等价于  image.convert('1')
image.show()   #

此时原来验证码图片中的线条已经去除,整个验证码图片变得黑白分明。这是再利用tesserocr识别即可。

import tesserocr
from PIL import Image
image=Image.open('code.jpg')
image=image.convert('L')   #灰度化
threshold=100  #设置阈值
table=[]
for i in range(256):
    if i #二值化
res=tesserocr.image_to_text(image)
print(res)   #  J96C

图片验证码的识别成功!

总结:利用tesserocr识别验证码!若是验证码图片较为简单,就可以直接识别,但是若是较为复杂(有线条的干扰),需要对验证码图片进行预处理(灰度化、二值化)来提高识别的准确度。

二、极验滑动验证码的识别--->以极验证官方后台登录为例!

【极验验证码的这块知识:参考的是崔庆才作者的《Python3-网络爬虫开发实战》】

注:实际上现在很多的网站的极验验证码都是查找不到原始的验证码(不带缺口的图片)!但是我们要掌握这种方法的思路。

其需要拖动拼合滑块才可以验证,难!!!如图:4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第2张图片

目标:用程序来识别并通过极验验证码的验证,包括:分析识别思路、识别缺口位置、生成滑动拖动路径、模拟实现滑块拼合通过验证等步骤。

工具:使用Selenium中的Chrome动态爬取。

识别思路:首先找到一个带有极验验证的网站,如:极验官方后台,链接:https://auth.geetest.com/login/。其是先点击d745defa89d58b7e3e6e6aa9207e395a5e6.jpg,再进行极验校正4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第3张图片,验证成功之后会出现:d1b64800889923df983a7ef0c6d642c3b29.jpg

操作步骤:(1)模拟点击验证按钮   (2)识别滑动缺口的位置     (3)模拟拖动滑块

分析步骤:

第(1)步操作最简单,我们可以直接用Selenium模拟点击按钮

第(2)步操作识别缺口的位置比较关键,这里需要用到图像的相关处理方法。首先观察缺口的样子(见上图),缺口的四周边缘有明显的断裂边缘,边缘和边缘周围有明显的区别。我们可以实现一个边缘检测算法来找出缺口的位置。

对于极验验证码来说,我们可以利用和原图对比检测的方式来识别缺口的位置,因为在没有滑动滑块之前, 缺口并没有呈现。4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第4张图片  4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第5张图片

获取两张图片。设定一个对比阔值,然后遍历两张图片,找出相同位置像素RGB差距超过此阔值的像素点,那么此像素点的位置就是缺口的位置

第(3)步操作看似简单,但其中的坑比较多。极验验证码增加了机器轨迹识别,匀速移动、随机速度移动等方法都不能通过验证,只有完全模拟人的移动轨迹才可以通过验证。人的移动轨迹一般是先加速后减速,需要模拟这个过程才能成功。

1、初始化

极验的管理后台登录页面:https://account.geetest.com/login。初始化一些配置,如Selenium 对象的初始化及一些参数的配置,如下:

EMAIL = '[email protected]'
PASSWORD = '******************'  #自己的密码

class CrackGeetest():
    def __init__(self):
        self.url = 'https://account.geetest.com/login'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.email = EMAIL
        self.password = PASSWORD

2、模拟点击

实现第1步的操作, 也就是模拟点击初始的验证按钮。利用显式等待的方法来实现,如下:

def get_geetest_button(self):
    """
    获取初始验证按钮
    :return:
    """
    button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
    return button

获取一个Web Element对象,调用它的click()方法即可模拟点击,代码如下所示:

button=self.get_geetest_button()
button.click()   #点击验证按钮

则第1步工作完成!

3、识别缺口

接下来识别缺口的位置。首先获取前后两张比对图片,二者不一致的地方即为缺口。获取不带缺口的图片,利用Selenium 选取图片元素,得到其所在位置和宽高,然后获取整个网页的截图,图片裁切出来即可,如下:

def get_position(self):
    """
    获取验证码位置
    :return: 验证码位置元组
    """
    img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
    time.sleep(2)
    location = img.location
    size = img.size
    top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
        'width']
    return (top, bottom, left, right)

def get_screenshot(self):
    """
    获取网页截图
    :return: 截图对象
    """
    screenshot = self.browser.get_screenshot_as_png()
    screenshot = Image.open(BytesIO(screenshot))
    screenshot.show()
    return screenshot

def get_geetest_image(self, name='captcha.png'):
    """
    获取验证码图片
    :return: 图片对象
    """
    top, bottom, left, right = self.get_position()
    print('验证码位置', top, bottom, left, right)
    screenshot = self.get_screenshot()
    captcha = screenshot.crop((left, top, right, bottom))
    captcha.save(name)
    return captcha

这里get_position()函数首先获取图片对象,获取它的位置和宽高,随后返回其左上角和右下角的坐标。get_geetest_image()方法获取网页截图,调用了crop()方法将图片裁切出来,返回的是Image对象。

接下来我们需要获取第二张图片,也就是带缺口的图片。要使得图片出现缺口,只需要点击下方的滑块即可。这个动作触发之后,图片中的缺口就会显现,如下所示:

def get_slider(self):
    """
    获取滑块
    :return: 滑块对象
    """
    slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
    return slider

这里利用get_slider()方法获取滑块对象,调用click()方法即可触发点击,缺口图片即可呈现,如下所示:

slider=self.get_slider()
slider.click()  #点击呼出缺口

调用get_geetest_image()方法将第二张图片获取下来即可。

现在得到两张图片对象,分别赋值给变量image1和image2。接下来对比图片获取缺口。遍历图片的每个坐标点,获取两张图片对应像素点的RGB数据。如果二者的RGB 数据差距在一定范围内,那就代表两个像素相同,继续比对下一个像素点。如果差距超过一定范围,则代表像素点不同,当前位置即为缺口位置,代码如下:

def is_pixel_equal(self, image1, image2, x, y):
    """
    判断两个像素是否相同
    :param image1: 图片1
    :param image2: 图片2
    :param x: 位置x
    :param y: 位置y
    :return: 像素是否相同
    """
    # 取两个图片的像素点
    pixel1 = image1.load()[x, y]
    pixel2 = image2.load()[x, y]
    threshold = 60
    if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                    pixel1[2] - pixel2[2]) < threshold:
        return True
    else:
        return False

def get_gap(self, image1, image2):
    """
    获取缺口偏移量
    :param image1: 不带缺口图片
    :param image2: 带缺口图片
    :return:
    """
    left = 60
    for i in range(left, image1.size[0]):
        for j in range(image1.size[1]):
            if not self.is_pixel_equal(image1, image2, i, j):
                left = i
                return left
    return left

get_gap()方法即获取缺口位置的方法。此方法的参数是两张图片, 一张为带缺口图片,另一张为不带缺口图片。这里遍历两张图片的每个像素,利用is_pixel_equal()方法判断两张固片同一位置的像素是否相同。比较两张图RGB 的绝对值是否均小于定义的阔值threshold。如果绝对值均在阈值之内,则代表像素点相同,继续遍历。否则代表不相同的像素点,即缺口的位置。

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第6张图片  4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第7张图片

两张图片有两处明显不同的地方: 一个就是待拼合的滑块, 一个就是缺口。滑块的位置会出现在左边位置,缺口会出现在与滑块同一水平线的位置,所以缺口一般会在滑块的右侧。如果要寻找缺口,直接从滑块右侧寻找即可。我们直接设置遍历的起始横坐标为60 ,也就是从滑块的右侧开始识别,这样识别出的结果就是缺口的位置。

此时获取了缺口的位置。完成验证还剩下最后一步一一模拟拖动。

4、模拟拖动

模拟拖动过程不复杂,但其中的坑比较多。完全模拟加速减速的过程通过了验证。前段滑块做匀加速运动,后段滑块做匀减速运动,利用物理学的加速度公式即可完成验证。

滑块滑动的加速度用a来表示, 当前速度用v表示, 初速度用V0表示,位移用x表示,所需时间用t表示,它们之间满足如下关系:

# 当前速度v = v0 + at
# 移动距离x = v0t + 1/2 * a * t^2

利用这两个公式可以构造轨迹移动算法,计算出先加速后减速的运动轨迹,代码如下:

def get_track(self, distance):
    """
    根据偏移量获取移动轨迹
    :param distance: 偏移量
    :return: 移动轨迹
    """
    # 移动轨迹
    track = []
    # 当前位移
    current = 0
    # 减速阈值
    mid = distance * 4 / 5
    # 计算间隔
    t = 0.2
    # 初速度
    v = 0

    while current < distance:
        if current < mid:
            # 加速度为正2
            a = 2
        else:
            # 加速度为负3
            a = -3
        # 初速度v0
        v0 = v
        # 当前速度v = v0 + at
        v = v0 + a * t
        # 移动距离x = v0t + 1/2 * a * t^2
        move = v0 * t + 1 / 2 * a * t * t
        # 当前位移
        current += move
        # 加入轨迹
        track.append(round(move))
    return track

定义了get_track()方法,传人的参数为移动的总距离,返回的是运动轨迹。运动轨迹用track表示,它是一个列表,列表的每个元素代表每次移动多少距离。

首先定义变量mid,即减速的阔值,也就是加速到什么位置开始减速。在这里mid值为4/5 ,即模拟前4/5路程是加速过程,后1/5路程是减速过程。

接着定义当前位移的距离变量current,初始为0,然后进入while循环,循环的条件是当前位移小于总距离。在循环里我们分段定义了加速度,其中加速过程的加速度定义为2,减速过程的加速度定义为-3。之后套用位移公式计算出某个时间段内的位移,将当前位移更新并记录到轨迹里即可。

直到运动轨迹达到总距离时,循环终止。最后得到的track 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了。

最后按照该运动轨迹拖动滑块即可,实现如下:

def move_to_gap(self, slider, track):
    """
    拖动滑块到缺口处
    :param slider: 滑块
    :param track: 轨迹
    :return:
    """
    ActionChains(self.browser).click_and_hold(slider).perform()
    for x in track:
        ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.5)
    ActionChains(self.browser).release().perform()

这里传人的参数为滑块对象和运动轨迹。首先调用ActionChains的click_and_hold()方法按住拖动底部滑块,遍历运动轨迹获取每小段位移距离,调用move_by_offset()方法移动此位移,最后调用release()方法松开鼠标即可。

经过测试,验证通过,识别完成!4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第8张图片

最后,完善表单, 模拟点击登录按钮,成功登录后即跳转到后台。

极验验证码的识别完整代码如下:

import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

EMAIL = '[email protected]'
PASSWORD = '*******'
BORDER = 6
INIT_LEFT = 60

class CrackGeetest():
    def __init__(self):
        self.url = 'https://account.geetest.com/login'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.email = EMAIL
        self.password = PASSWORD

    def __del__(self):
        self.browser.close()

    def get_geetest_button(self):
        """
        获取初始验证按钮
        :return:
        """
        button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        return button

    def get_position(self):
        """
        获取验证码位置
        :return: 验证码位置元组
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)

    def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        screenshot.show()
        return screenshot

    def get_slider(self):
        """
        获取滑块
        :return: 滑块对象
        """
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        return slider

    def get_geetest_image(self, name='captcha.png'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()
        print('验证码位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

    def open(self):
        """
        打开网页输入用户名密码
        :return: None
        """
        self.browser.get(self.url)
        email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
        email.send_keys(self.email)
        password.send_keys(self.password)

    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :return:
        """
        left = 60
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        return left

    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素是否相同
        :param image1: 图片1
        :param image2: 图片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取两个图片的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 60
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                        pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False

    def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:
            if current < mid:
                # 加速度为正2
                a = 2
            else:
                # 加速度为负3
                a = -3
            # 初速度v0
            v0 = v
            # 当前速度v = v0 + at
            v = v0 + a * t
            # 移动距离x = v0t + 1/2 * a * t^2
            move = v0 * t + 1 / 2 * a * t * t
            # 当前位移
            current += move
            # 加入轨迹
            track.append(round(move))
        return track

    def move_to_gap(self, slider, track):
        """
        拖动滑块到缺口处
        :param slider: 滑块
        :param track: 轨迹
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()

    def login(self):
        """
        登录
        :return: None
        """
        submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
        submit.click()
        time.sleep(10)
        print('登录成功')

    def crack(self):
        # 输入用户名密码
        self.open()
        # 点击验证按钮
        button = self.get_geetest_button()
        button.click()
        # 获取验证码图片
        image1 = self.get_geetest_image('captcha1.png')
        # 点按呼出缺口
        slider = self.get_slider()
        slider.click()
        # 获取带缺口的验证码图片
        image2 = self.get_geetest_image('captcha2.png')
        # 获取缺口位置
        gap = self.get_gap(image1, image2)
        print('缺口位置', gap)
        # 减去缺口位移
        gap -= BORDER
        # 获取移动轨迹
        track = self.get_track(gap)
        print('滑动轨迹', track)
        # 拖动滑块
        self.move_to_gap(slider, track)

        success = self.wait.until(
            EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
        print(success)

        # 失败后重试
        if not success:
            self.crack()
        else:
            self.login()


if __name__ == '__main__':
    crack = CrackGeetest()
    crack.crack()

极验验证码的识别工作全部完成。此识别方法同样适用于其他使用极验验证码3.0 的网站,原理都是相同的。

实际获得的验证码图片是:4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第9张图片这边做了加密,中间一条横线,纵向很多竖线,且是有很多个小单元拼接起来的,个人推测:网站的js应该做了还原现实,这边我就不多说啦,反正大家掌握原理就行,遇到真实的验证码问题:还是得具体问题具体分析!

三、点触验证码的识别

12306 就是典型的点触验证码!关于12306的点触验证码,我之前已经做过了。

题目:4-12306网站的登录实现【面向过程+面向对象】

链接:https://my.oschina.net/pansy0425/blog/2988355

四、微博宫格验证码的识别

【微博宫格验证码的这块知识:参考的是崔庆才作者的《Python3-网络爬虫开发实战》】

注:实际上微博宫格验证码好像已经不存在了......!........但是我们要掌握这种方法的思路。

微博宫格验证码是一种新型交互式验证码, 每个’白’格之间会有一条指示连线, 指示了应该的滑动轨迹。我们要按照滑动轨迹依次从起始宫格滑动到终止宫格,才可以完成验证,如下图:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第10张图片

访问新浪微博移动版登录页面,就可以看到如上验证码,链接为https://passport.weibo.cn/signin/login ,不是每次登录都会出现验证码,当频繁登录或者账号存在安全风险的时候,验证码才会出现。

工具:使用的Python 库是Selenium,使用的浏览器为Chrome

思路:识别从探寻规律入手。规律:此验证码的四个宫格一定是有连线经过的,每一条连线上都会相应的指示箭头,连线的形状多样,包括C 型、Z 型、X 型等,如下所示:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第11张图片

其中同一类型的连线轨迹是相同的,唯一不同的就是连线的方向:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第12张图片

这两种验证码的连线轨迹是相同的。但是由于连线上面的指示箭头不同,导致滑动的宫格顺序有所不同。

这时我们可以考虑用模板匹配的方法,就是将一些识别目标提前保存并做好标记,这称作模板。这里将验证码图片做好拖动顺序的标记当做模板。对比要新识别的目标和每一个模板,如果找到匹配的模板,则就成功识别出要新识别的目标。在图像识别中,模板匹配也是常用的方法。

要收集到足够多的模板,模板匹配方法的效果才会好。而对于微博宫格验证码来说, 宫格只有4 个,验证码的样式最多4×3×2×1=24种,则我们可以将所有模板都收集下来。

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第13张图片

1、获取模板

准备工作:先保存24张验证码全图。因为验证码是随机的, 一共有24种。先批量保存验证码图片,然后从中筛选出需要的图片,代码如下:

import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

USERNAME = '********'  #你的用户名
PASSWORD = '********'  #你的密码

TEMPLATES_FOLDER = 'templates/'

class CrackWeiboSlide():
    def __init__(self):
        self.url = 'https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.username = USERNAME
        self.password = PASSWORD

    def __del__(self):
        self.browser.close()

    def open(self):
        """
        打开网页输入用户名密码并点击
        :return: None
        """
        self.browser.get(self.url)
        username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword')))
        submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction')))
        username.send_keys(self.username)
        password.send_keys(self.password)
        submit.click()

    def get_position(self):
        """
        获取验证码位置
        :return: 验证码位置元组
        """
        try:
            img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
        except TimeoutException:
            print('未出现验证码')
            self.open()
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)

    def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot

    def get_image(self, name='captcha.png'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()
        print('验证码位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha
    
    def main(self):
        """
        批量获取验证码
        :return:图片对象
        """
        count=0
        while True:
            self.open()
            self.get_image(str(count)+'.png')
            count=count+1

if __name__ == '__main__':
    crack = CrackWeiboSlide()
    crack.main()

这里需要将USERNAME 和PASSWORD 修改为自己微博的用户名和密码。运行一段时间后,本地多了很多以数字命名的验证码,如所示:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第14张图片

这里我们只需要挑选出不同的24 张验证码图片并命名保存。名称可以直接取作宫格的滑动的顺序:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第15张图片将图片命名为4132.p吨,代表滑动顺序为4-1-3-2。

按照这样的规则,我们将验证码整理为如下24张图:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第16张图片

如上24张图就是模板。接下来,识别过程只需要遍历模板进行匹配即可。

2、模板匹配

调用get_image()方法,得到验证码图片对象。然后,对验证码图片对象进行模板匹配,代码如下:

from os import listdir
def detect_image(self, image):
    """
    匹配图片
    :param image: 图片
    :return: 拖动顺序
    """
    for template_name in listdir(TEMPLATES_FOLDER):
        print('正在匹配', template_name)
        template = Image.open(TEMPLATES_FOLDER + template_name)
        if self.same_image(image, template):
            # 返回顺序
            numbers = [int(number) for number in list(template_name.split('.')[0])]
            print('拖动顺序', numbers)
            return numbers

TEMPLATES_FOLDER 就是模板所在的文件夹。这里通过listdir()方法获取所有模板的文件名称,然后对其进行遍历,通过same_image()方法对验证码和模板进行比对。如果匹配成功,那么就将匹配到的模板文件名转换为列表。如果模板文件3124.png匹配到了,则返回结果:[3,1,2,4]。

对比的方法如下:

def is_pixel_equal(self, image1, image2, x, y):
    """
    判断两个像素是否相同
    :param image1: 图片1
    :param image2: 图片2
    :param x: 位置x
    :param y: 位置y
    :return: 像素是否相同
    """
    # 取两个图片的像素点
    pixel1 = image1.load()[x, y]
    pixel2 = image2.load()[x, y]
    threshold = 20
    if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                    pixel1[2] - pixel2[2]) < threshold:
        return True
    else:
        return False

def same_image(self, image, template):
    """
    识别相似验证码
    :param image: 待识别验证码
    :param template: 模板
    :return:
    """
    # 相似度阈值
    threshold = 0.99
    count = 0
    for x in range(image.width):
        for y in range(image.height):
            # 判断像素是否相同
            if self.is_pixel_equal(image, template, x, y):
                count += 1
    result = float(count) / (image.width * image.height)
    if result > threshold:
        print('成功匹配')
        return True
    return False

在这里比对图片也利用了遍历像素的方法。same image()方法接收两个参数,image为待检测的验证码图片对象,template是模板对象。由于二者大小是完全一致的,所以在这里我们遍历了图片的所有像素点。比对二者同一位置的像素点,如果像素点相同,计数就加1。最后计算相同的像素点占总像素的比例。如果该比例超过一定阁值,那就判定图片完全相同,则匹配成功。这里阔值设定为0.99,即如果二者有0.99 以上的相似比,则代表匹配成功。

通过上面的方法,依次匹配24个模板。如果验证码图片正常,总能找到一个匹配的模板,这样就可以得到宫格的滑动顺序了。

3、模拟滑动

接下来,根据滑动顺序拖动鼠标,连接各个宫格,方法实现如下所示:

def move(self, numbers):
    """
    根据顺序拖动
    :param numbers:
    :return:
    """
    # 获得四个按点
    circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ')
    dx = dy = 0
    for index in range(4):
        circle = circles[numbers[index] - 1]
        # 如果是第一次循环
        if index == 0:
            # 点击第一个按点
            ActionChains(self.browser) \
                .move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) \
                .click_and_hold().perform()
        else:
            # 小幅移动次数
            times = 30
            # 拖动
            for i in range(times):
                ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform()
                time.sleep(1 / times)
        # 如果是最后一次循环
        if index == 3:
            # 松开鼠标
            ActionChains(self.browser).release().perform()
        else:
            # 计算下一次偏移
            dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x']
            dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y']

这里方法接收的参数就是宫格的点按顺序,如[ 3, 1 ,2, 4 ] 。首先我们利用find _ elements_by_ css_selector()方法获取到4个宫格元素,它是一个列表形式,每个元素代表一个宫格。接下来遍历宫格的点按顺序,做一系列对应操作。

其中如果当前遍历的是第一个宫格,那就直接鼠标点击并保持动作,否则移动到下一个宫格。如果当前遍历的是最后一个宫格,那就松开鼠标,如果不是最后一个宫格, 则计算移动到下一个宫格的偏移量。

通过4 次循环,我们便可以成功操作浏览器完成宫格验证码的拖拽填充,松开鼠标之后即可识别成功,运行效果:

4-验证码的识别【针对特定类型-->4种常见类型】--【重要!】_第17张图片

鼠标会慢慢从起始位置移动到终止位置。最后一个宫格松开之后,验证码的识别便完成了。之后验证码窗口会向动关闭。直接点击登录按钮即可登录微博

极验验证码的识别完整代码如下:

import os
import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from os import listdir

USERNAME = '**********'
PASSWORD = '**********'
TEMPLATES_FOLDER = 'templates/'

class CrackWeiboSlide():
    def __init__(self):
        self.url = 'https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/'
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)
        self.username = USERNAME
        self.password = PASSWORD
    
    def __del__(self):
        self.browser.close()
    
    def open(self):
        """
        打开网页输入用户名密码并点击
        :return: None
        """
        self.browser.get(self.url)
        username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword')))
        submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction')))
        username.send_keys(self.username)
        password.send_keys(self.password)
        submit.click()
    
    def get_position(self):
        """
        获取验证码位置
        :return: 验证码位置元组
        """
        try:
            img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow')))
        except TimeoutException:
            print('未出现验证码')
            self.open()
        time.sleep(2)
        location = img.location
        size = img.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return (top, bottom, left, right)
    
    def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        screenshot = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(screenshot))
        return screenshot
    
    def get_image(self, name='captcha.png'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()
        print('验证码位置', top, bottom, left, right)
        screenshot = self.get_screenshot()
        captcha = screenshot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha
    
    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素是否相同
        :param image1: 图片1
        :param image2: 图片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取两个图片的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 20
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False
    
    def same_image(self, image, template):
        """
        识别相似验证码
        :param image: 待识别验证码
        :param template: 模板
        :return:
        """
        # 相似度阈值
        threshold = 0.99
        count = 0
        for x in range(image.width):
            for y in range(image.height):
                # 判断像素是否相同
                if self.is_pixel_equal(image, template, x, y):
                    count += 1
        result = float(count) / (image.width * image.height)
        if result > threshold:
            print('成功匹配')
            return True
        return False
    
    def detect_image(self, image):
        """
        匹配图片
        :param image: 图片
        :return: 拖动顺序
        """
        for template_name in listdir(TEMPLATES_FOLDER):
            print('正在匹配', template_name)
            template = Image.open(TEMPLATES_FOLDER + template_name)
            if self.same_image(image, template):
                # 返回顺序
                numbers = [int(number) for number in list(template_name.split('.')[0])]
                print('拖动顺序', numbers)
                return numbers
    
    def move(self, numbers):
        """
        根据顺序拖动
        :param numbers:
        :return:
        """
        # 获得四个按点
        circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ')
        dx = dy = 0
        for index in range(4):
            circle = circles[numbers[index] - 1]
            # 如果是第一次循环
            if index == 0:
                # 点击第一个按点
                ActionChains(self.browser) \
                    .move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) \
                    .click_and_hold().perform()
            else:
                # 小幅移动次数
                times = 30
                # 拖动
                for i in range(times):
                    ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform()
                    time.sleep(1 / times)
            # 如果是最后一次循环
            if index == 3:
                # 松开鼠标
                ActionChains(self.browser).release().perform()
            else:
                # 计算下一次偏移
                dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x']
                dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y']
    
    def crack(self):
        """
        破解入口
        :return:
        """
        self.open()
        # 获取验证码图片
        image = self.get_image('captcha.png')
        numbers = self.detect_image(image)
        self.move(numbers)
        time.sleep(10)
        print('识别结束')

if __name__ == '__main__':
    crack = CrackWeiboSlide()
    crack.crack()

微博宫格验证码的识别完成。该方法适合宫格验证码的使用!

模板匹配的方法!

转载于:https://my.oschina.net/pansy0425/blog/3001120

你可能感兴趣的:(python,爬虫,测试)