某B某站selenium破解登录页面的滑动验证码

郑重声明:
本项目的所有代码和相关文章,仅用于经验技术交流分享,禁止将相关技术应用到不正当途径,因为滥用技术产生的风险与本人无关。
文章仅源自个人兴趣爱好,不涉及他用,侵权联系删

前面文章介绍了一些关于破解验证码的文章,包括借助百度智能云Api识别简单文字数字https://blog.csdn.net/Owen_goodman/article/details/106071249

还有借助打码平台破解验证码https://blog.csdn.net/Owen_goodman/article/details/105053448

这次简单介绍一下如何解决遇到的滑动验证码问题(成功率95%+)。

1、介绍

 滑动验证码需要拖动拼合滑块才能完成验证,相对于图形验证码来说识别难度上升了几个等级。制作滑动验证码的公司叫做GEETEST,官网是https://www.geetest.com/。主要验证方式是拖动滑块破解图像。若图像完全拼合,则验证成功,即表单成功提交,否则需要重新验证。

2、特点

滑动验证码相较于图形验证码来说识别难度更大。现在极验验证码已经更新到3.0版本,对于极验验证码 3.0 版本,
我们首先点击按钮进行智能验证。如果验证不通过,则会弹出滑动验证的窗口,拖动滑块拼合图像进行验证
之后三个加密参数会生成,通过表单提交到后台,后台还会进行一次验证。
极验验证码还增加了机器学习的方法来识别拖动轨迹。官方网站的安全防护有如下几点说明:

    “三角防护之防模拟:恶意程序模仿人类行为轨迹对验证码进行识别。针对模拟,极验验证码拥有超过4000万人机行为样本的海量数据。
    利用机器学习和神经网络,构建线上线下的多重静态、动态防御模型。识别模拟轨迹,界定人机边界。
    三角防护之防伪造:恶意程序通过伪造设备浏览器环境对验证码进行识别。针对伪造,极验验证码利用设备基因技术。
    深度分析浏览器的实际性能来辨识伪造信息。同时根据伪造事件不断更新黑名单,大幅提高防伪造能力。
    三角防护之防暴力:恶意程序短时间内进行密集的攻击,对验证码进行暴力识别。针对暴 力,极验验证码拥有多种验证形态,
    每一种验证形态都有利用神经网络生成的海蓝图库储备,每一张图片都是独一无二的,且图库不断更新,极大程度提高了暴力识别的成本。”

另外,极验验证码的验证相对于普通验证方式更方便,体验更友好,其官方网站说明如下:

    “点击一下,验证只需要0.4秒。极验验证码始终专注于去验证化实践,让验证环节不再打断产品本身的交互流程,最终达到优化用户体验和提高用户转化率的效果。
    全平台兼容 ,适用各种交互场景。极验验证码兼容所有主流浏览器甚至于古老的IE6,也可以轻松应用在iOS和Android移动端平台,满足各种业务需求,保护网站资源不被滥用和监取。
    面向未来,懂科技,更懂人性。极验验证码在保障安全同时不断致力于提升用户体验、精雕细琢的验证面板、流畅顺滑的验证动画效果,让验证过程不再枯燥乏味。”

用户体验是爽了,我们就很难过了啊。下面开始详细讲解怎么破解极验验证码:

3、实现思路

对于应用了极验验证码的网站,如果我们直接模拟表单提交,加密参数的构造是个问题,需要分析其加密和校验逻辑,非常的复杂。但是我们如果采用模拟浏览器动作的方式来完成验证,就会变得很简单了。在python中,我们可以使用selenium来模拟人的行为来完成验证、此验证成本相对与直接去识别加密算法少得多。
背景:B站,链接为:https://passport.bilibili.com/login。输入账号密码点击登录,极验验证码就会弹出来。

环境:win10+selenium+webdriver

所以我们这个识别验证案例 完成需要三步:
1.输入账号密码,点击登录
2.识别滑动缺口的位置
3.模拟拖动滑块

第一步操作最简单,我们可以直接用selenium完成。
第二步操作识别缺口的位置比较关键,这里需要用到图像的相关处理方法。首先观察缺口的样子。captcha1.png

某B某站selenium破解登录页面的滑动验证码_第1张图片

缺口的四周边缘又明显的断裂边缘,边缘和边缘周围又明显的区别。我们可以实现一个边缘检测算法来找出缺口的位置。
对于极验验证码来说,我们可以利用和原图对比检测的方式来识别缺口的位置,因为在没有滑动滑块之前, 缺口并没有呈现。captcha2.png

某B某站selenium破解登录页面的滑动验证码_第2张图片
我们可以同时获取两张图片。设定一个对比阔值,然后遍历两张图片,找出相同位置像素RGB差距超过此阔值的像素点,那么此像素点的位置就是缺口的位置 。
第三步操作看似简单,但是其中的坑特别多。极验验证码增加了机器轨迹识别,匀速移动、随机速度移动等方法都不能通过验证,只有完全模拟人的移动轨迹才可以通过验证。人的移动轨迹一般是先加速后减速,我们需要模拟这个过程才能成功。

有了思路后,我们就用代码来实现极验验证码的识别过程吧。

4、代码实现

4.1 初始化

from PIL import Image
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"}
chrome_options = webdriver.ChromeOptions()
chrome_options.add_experimental_option('w3c', False)
caps = DesiredCapabilities.CHROME
caps['loggingPrefs'] = {'performance': 'ALL'}

chrome_options.add_experimental_option('w3c', False) 

详情见https://wizardforcel.gitbooks.io/selenium-doc/content/official-site/selenium-web-driver.html
 暂时也没搞明白是什么意思,大概是这个?所有我们要禁用。测试时发现,这个和我们后面用到的匀加减速有关系,应该就是为了完全模拟真人的操作,将W3C标准直接置为false。

       “JavaScript in the HtmlUnit Driver没有任何一个主流浏览器支持 HtmlUnit 使用的 JavaScript 引擎(Rhino)。如果你使用 HtmlUnit,测试结果可能和真实在浏览器中跑的很不一样。当我们说到 “JavaScript” 时通常是指 “JavaScript 和 DOM”。虽然 DOM 由 W3C 组织定义,但是每个浏览器在 DOM 和 JavaScript 的交互的实现方面都有一些怪异和不同的地方。HtmlUnit 完全实现了 DOM 规范,并且对 JavaScript 提供了良好的支持,但它的实现和真实的浏览器都不一样:虽然它模拟了浏览器中的实现,但既不同于 W3C 指定的标准,也不同于其他主流浏览器的实现。”

caps = DesiredCapabilities.CHROME
caps['loggingPrefs'] = {'performance': 'ALL'} #必须有这一句,才能在后面获取到performance,这个是为了获取浏览器请求的信息,详情见https://www.cnblogs.com/justaman/p/11503805.html

4.2 输入账号密码,点击登录

class SliderVerificationCode(object):
    # 初始化一些信息
    def __init__(self):
        self.left = 60 # 定义一个左边的起点 缺口一般离图片左侧有一定的距离 有一个滑块
        self.url = 'https://passport.bilibili.com/login'
        self.driver = webdriver.Chrome(desired_capabilities=caps,options=chrome_options)
        self.wait = WebDriverWait(self.driver, 20) # 设置等待时间20秒
        self.phone = "***" #your phone
        self.passwd = "***" #your passwd
        # phone和passwd就是登录B站的账号和密码。

    # 输入用户名和密码
    def input_name_password(self):
        self.driver.get(self.url)
        self.driver.maximize_window() # 窗口最大化
        self.user = self.wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
        #self.user = self.driver.find_element_by_xpath("//input[@id='login-username']")
        self.passwd = self.wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
        #self.passwd = self.driver.find_element_by_xpath("//input[@id='login-passwd']")
        self.user.send_keys(self.username)
        self.passwd.send_keys(self.password)

    # 点击登录按钮,出现验证码图片
    def click_login_button(self):
        login_button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'btn-login')))
        login_button.click()
        sleep(3)

到这里,第一步的工作就完成了

4.3 识别滑动缺口的位置

接下来识别缺口的位置,首先获取前后两张图片进行对比,两张不一样的地方即为缺口。
现在的这个版本的极验验证码应该都可以使用selenium截取缺口图和完整图进行比较,然后找到缺口的位置。
(对于极验验证码来说,我们可以利用和原图对比检测的方式来识别缺口的位置,因为在没有滑动滑块之前, 缺口并没有呈现。
我们可以同时获取两张图片。设定一个对比阔值,然后遍历两张图片,找出相同位置像素RGB差距超过此阔值的像素点,那么此像素点的位置就是缺口的位置 。)

# 获取验证码图片
def get_geetest_image(self):
    # print(self.driver.page_source)
    gapimg = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_bg')))
    sleep(2)
    # 将class为geetest_canvas_bg的区域截屏保存(这里就是缺口图)
    gapimg.screenshot(r'./captcha1.png')
    # 通过js代码修改标签样式 显示图片2(这里用到了通过js代码修改图片标签的属性,从而拿到完整图片)
    # 但当我们将上图源代码中的类别为"geetest_canvas_fullbg geetest_fade geetest_absolute"的style设置为空(或者设置成block),即可显示没有缺口的原图。
    js = 'var change = document.getElementsByClassName("geetest_canvas_fullbg");change[0].style = "display:block;"'
    # selenium调用js的方法 execute_script()
    self.driver.execute_script(js)
    sleep(2)
    fullimg = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_fullbg')))
    # 将class为geetest_canvas_fullbg的区域截屏保存(这里是完整图片)
    fullimg.screenshot(r'./captcha2.png')


# 对比两张图片,判断像素相似点,找到缺口位置
def is_similar(self, image1, image2, x, y):
    '''
    判断两张图片 各个位置的像素是否相同
    image1:带缺口的图片
    image2:不带缺口的图片
    x: 位置x
    y: 位置y
    return: (x,y)位置的像素是否相同
    '''
    # 获取两张图片指定位置的像素点
    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

到这里,第二步的工作就完成了

4.4 模拟拖动滑块

模拟拖动过程不复杂,但其中的坑特别多。现在我们只需要调用拖动的相关函数将滑块拖动到相应位置。
如果是匀速拖动,极验必然就会识别出它是程序的操作,因为人无法做到完全匀速拖动。极验验证码利用机器学习模型,筛选此类数据为机器操作,验证码识别失败。

我们尝试分段模拟将拖动过程划分几段,前段滑块做匀加速运动,后段滑块做匀减速运动,利用物理学的加速度公示即可完成验证。
滑块滑动的加速度用a来表示,当前速度用v表示,初速度用v0表示,位移用x表示,所需时间用t表示,关系如下:

x = v0 * t +0.5 * a * t * t
v = v0 + a * t
利用这两个公示可以构造轨迹移动算法,计算出先加速后减速的运动轨迹,代码如下

# 获取缺口图起点
def get_diff_location(self):
    captcha1 = Image.open('captcha1.png')
    captcha2 = Image.open('captcha2.png')
    # 从左到右 x方向
    for x in range(self.left, captcha1.size[0]):
        # 从上到下 y方向
        for y in range(captcha1.size[1]):
            if not self.is_similar(captcha1, captcha2, x, y):
                return x  # 找到缺口的左侧边界 在x方向上的位置


# 获取移动轨迹
def get_move_track(self, gap):
    track = []  # 移动轨迹
    current = 0  # 当前位移
    # 减速阈值
    mid = gap * 4 / 5  # 前4/5段加速 后1/5段减速
    t = 0.2  # 计算间隔
    v = 0  # 初速度
    while current < gap:
        if current < mid:
            a = 2.5  # 加速度为+2.5(可调)
        else:
            a = -3.5  # 加速度为-3.5
        v0 = v  # 初速度v0
        v = v0 + a * t  # 当前速度
        move = v0 * t + 1 / 2 * a * t * t  # 移动距离
        current += move  # 当前位移
        track.append(round(move))  # 加入轨迹
    return track


# 开始移动
def move_slider(self, track):
    slider = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_slider_button')))
    ActionChains(self.driver).click_and_hold(slider).perform()
    for x in track:  # 只有水平方向有运动 按轨迹移动
        ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform()
        sleep(1)
        ActionChains(self.driver).release().perform()  # 松开鼠标
        self.driver.save_screenshot('全屏截图.jpg')



def main(self):
    self.input_name_password()
    self.click_login_button()
    self.get_geetest_image()
    gap = self.get_diff_location()  # 缺口左起点位置
    gap = gap - 6  # 减去滑块左侧距离图片左侧在x方向上的距离 即为滑块实际要移动的距离
    track = self.get_move_track(gap)
    print("移动轨迹", track)
    self.move_slider(track)

到这里,第三步的工作就完成了

全部代码:

# -*- coding: UTF-8 -*-
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from lxml.html import etree
from PIL import Image
from time import sleep
import re, requests
from urllib.request import urlretrieve
from bs4 import BeautifulSoup
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
}
chrome_options = webdriver.ChromeOptions()
chrome_options.add_experimental_option('w3c', False)
caps = DesiredCapabilities.CHROME
caps['loggingPrefs'] = {'performance': 'ALL'}

class SliderVerificationCode(object):
    def __init__(self):  # 初始化一些信息
        self.left = 60  # 定义一个左边的起点 缺口一般离图片左侧有一定的距离 有一个滑块
        self.url = 'https://passport.bilibili.com/login'
        self.driver = webdriver.Chrome(options=chrome_options)
        self.driver = webdriver.Chrome(desired_capabilities=caps, options=chrome_options)
        self.wait = WebDriverWait(self.driver, 20)  # 设置等待时间20秒
        self.username = "***"
        self.password = "***"

    def input_name_password(self):  # 输入用户名和密码
        self.driver.get(self.url)
        self.user = self.wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
        self.passwd = self.wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
        self.user.send_keys(self.username)
        self.passwd.send_keys(self.password)

    def click_login_button(self):  # 点击登录按钮,出现验证码图片
        login_button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'btn-login')))
        login_button.click()
        sleep(1)

    def get_geetest_image(self):  # 获取验证码图片
        # print(self.driver.page_source)
        gapimg = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_bg')))
        sleep(2)
        gapimg.screenshot(r'./captcha1.png')  # 将class为geetest_canvas_bg的区域截屏保存
        # 通过js代码修改标签样式 显示图片2
        js = 'var change = document.getElementsByClassName("geetest_canvas_fullbg");change[0].style = "display:block;"'
        self.driver.execute_script(js)
        sleep(2)
        fullimg = self.wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_fullbg')))
        fullimg.screenshot(r'./captcha2.png')  # 将class为geetest_canvas_fullbg的区域截屏保存

    def is_similar(self, image1, image2, x, y):
        '''判断两张图片 各个位置的像素是否相同
        #image1:带缺口的图片
        :不带缺口的图片
        :x: 位置x
        :y: 位置y
        :return: (x,y)位置的像素是否相同
        '''
        # 获取两张图片指定位置的像素点
        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_diff_location(self):  # 获取缺口图起点
        captcha1 = Image.open('captcha1.png')
        captcha2 = Image.open('captcha2.png')
        for x in range(self.left, captcha1.size[0]):  # 从左到右 x方向
            for y in range(captcha1.size[1]):  # 从上到下 y方向
                if not self.is_similar(captcha1, captcha2, x, y):
                    return x  # 找到缺口的左侧边界 在x方向上的位置

    def get_move_track(self, gap):
        track = []  # 移动轨迹
        current = 0  # 当前位移
        # 减速阈值
        mid = gap * 4 / 5  # 前4/5段加速 后1/5段减速
        t = 0.2  # 计算间隔
        v = 0  # 初速度
        while current < gap:
            if current < mid:
                a = 2.5  # 加速度为+2.5
            else:
                a = -3.5  # 加速度为-3.5
            v0 = v  # 初速度v0
            v = v0 + a * t  # 当前速度
            move = v0 * t + 1 / 2 * a * t * t  # 移动距离
            current += move  # 当前位移
            track.append(round(move))  # 加入轨迹
        return track

    def move_slider(self, track):
        slider = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_slider_button')))
        ActionChains(self.driver).click_and_hold(slider).perform()
        for x in track:  # 只有水平方向有运动 按轨迹移动
            ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform()
        sleep(1)
        ActionChains(self.driver).release().perform()  # 松开鼠标
        self.driver.save_screenshot('成功截图.jpg')

    def main(self):
        self.input_name_password()
        self.click_login_button()
        self.get_geetest_image()
        gap = self.get_diff_location()  # 缺口左起点位置
        gap = gap - 6  # 减去滑块左侧距离图片左侧在x方向上的距离 即为滑块实际要移动的距离
        track = self.get_move_track(gap)
        print("移动轨迹", track)
        self.move_slider(track)


if __name__ == "__main__":
    bilibiliSlider = SliderVerificationCode()
    bilibiliSlider.main()





成功截图.png

某B某站selenium破解登录页面的滑动验证码_第3张图片

参考:https://www.xin3721.com/Python/python16447_2.html

你可能感兴趣的:(验证码,Python)