【极验验证码】selenium极验滑动验证 模拟登录

此博客仅为我业余记录文章所用,发布到此,仅供网友阅读参考,如有侵权,请通知我,我会删掉。

补充

!!!有问题
最新版本的ChromeFirefox浏览器在进行模拟滑动时候,都会出现逐帧滑动的现象(即滑动很慢)。
通过降低浏览器版本可解决这一问题,即安装低版本的ChromeFirefox浏览器。具体原因未知也无从而知。

重点!!!
成功率并不喜人,识别缺口距离 与 模拟拖动不咋精准,勿喷。。。


文中有几处是借鉴到别人的,在这里就不贴出其他博文地址了,无他,网上一搜都是同一个方法!!!

在这里插入图片描述

前言:

登录bilibili时候,发现有滑动验证码,本着纸上得来终觉浅,绝知此事要躬行的态度,我对极验滑动拼图型验证码进行了研究,遂有此文。

先打一个预防针,本文章会用到seleniumImage的一些知识,文中不会详细讲解。

这里对里面用到的几个知识模块做一下简单介绍。详细的建议看selenium的源码。

#导入模块
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

方法 描述 方法
WebDriverWait( driver, timeout).until(method) 调用驱动程序作为参数提供的方法,直到返回值不为False selenium
EC.element_to_be_clickable 判断元素是否可点击 selenium
EC.presence_of_element_located 只要符合条件的元素加载出来就通过 selenium
EC.text_to_be_present_in_element 以判断某段文本是否出现在某元素中 selenium
document.querySelectorAll(“element”)[2].style=“display:block” 获取element[2]的.style="display:block"元素 js
Image.open(img) 返回一个Image对象 Image
Image.open(img).size 返回图片的宽度和高度(width, height) Image
Image.open(img).load()[x,y] 获取图片指定位置的像素点 Image
Image.open(img).crop((box)).save(‘name.png’) 裁剪矩形区域 Image

1. 思路

案例目标:极验demo示例
Sunrisecai
看到图片的都知道,我们要做的就是拖动滑块到缺口位置。这是人为的操作。
如果是用机器来操作呢?主要以下三个步骤!!

  1. 下载背景图片
  2. 求出缺口位置的距离
  3. 拖动滑块到缺口位置

其中最繁琐的是步骤1,最轻松的是步骤2,最重要是步骤3

方法 作用
下载背景图片 通过图片对比出缺口位置距离
求缺口位置距离 模拟滑块滑动的实际距离

但是这里还要做一下细分,如何去下载背景图片,要下载几个背景图片等等。。。

下面将一一讲解。

可能会有一种情况是,看起来不知所谓,这是我的功底和文笔所致,但是请耐心看下去,你定会有所收获。


2. 分析

2.1 下载背景图片

下图标出了重要的块,但是只凭借着这一张图,可能看不出什么,下面接着看
在这里插入图片描述
来到完整背景图,在右边的element.style,设置display:block,看到缺口滑块都不见了。
在这里插入图片描述
最终的目的,是获取到下面的这两张图,接下来用代码去实现!!!
在这里插入图片描述


先来看一下,下载图片需要经过以下几个步骤:

步骤 描述
1 selenium打开网页
2 点击按钮,弹出滑动验证码
3 网页全屏截图
4 获取验证码坐标
5 输入坐标在网页全屏中截图,获取完整的验证码图片
6 执行js代码,隐藏滑块 + 缺口
7 输入坐标在网页全拼中截图,获取去除滑块+缺口验证码图片
  • 下载图片这里,我想到两方法,一是用Imagecrop截图,二是使用selenium截图
  • 这里选择方法一使用Imagecrop截图,因为比较快(虽然有些麻烦,小声bb)。
  • 为什么是下载两张图片呢?因为利用两张图片的像素点进行对比,就可以知道缺口的距离。

首先导入模块

import time

from PIL import Image
from io import BytesIO
from selenium import webdriver
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

1. selenium打开网页

class SliderSpider(object):
    def __init__(self):
        self.url = 'https://www.geetest.com/demo/slide-popup.html'
        options = webdriver.ChromeOptions()
        options.add_argument("--start-maximized")	# 最大化运行窗口 可不添加
        self.browser = webdriver.Chrome(options=options)
        self.wait = WebDriverWait(self.browser, 20)
        
     def login(self):
        """
        登录网页
        :return: 
        """
        self.browser.get(self.url)

2. 点击按钮,弹出滑动验证码
这里可以看到按钮的class为:geetest_radar_tip,先获取它,再点击它。

在这里插入图片描述

def click_button(self):
    """
    点击按钮,弹出滑动验证码
    :return: 
    """
    button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
    button.click()

3. 网页全屏截图
调用seleniumget_screenshot_as_png() 获取网页全屏截图,并用BytesIO打开它,操作二进制文件要使用BytesIO

def get_page_screenshot(self):
    """
    获取网页全屏截图
    :return: 图片对象
    """
    pageScreenshot = self.browser.get_screenshot_as_png()
    # self.browser.save_screenshot('name.png') 可保存为图片文件
    pageScreenshot = Image.open(BytesIO(pageScreenshot))
    return pageScreenshot

4. 获取验证码坐标

这里主要用到selenium的locationsize对图片进行定位,

方法 描述 返回值(示例)
location 返回该图片对象在浏览器中的位置,以字典的形式返回 {‘x’: 100, ‘y’: 100}
size 返回元素的大小 {‘height’: 160, ‘width’: 260}

其中location返回值为图片的左上角坐标,size返回值为图片的长宽。在知道了图片一个坐标点以及图片的长宽后,求出其他坐标也就很简单了。


看到源码中验证码坐标的class为geetest_canvas_bg geetest_absolute
在这里插入图片描述

def get_position(self):
    """
    获取验证码背景图的坐标位置
    :return:    返回左上右下四个值。左上,右下分别是图片左上角和图片右下角的坐标
    """
    image = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_bg')))
    location = image.location
    size = image.size
    left, upper, right, lower = location['x'], location['y'], location['x'] + size['width'], location['y'] + size['height']
    return left, upper, right, lower

5. 输入坐标在网页全屏中截图,获取完整的验证码图片

def get_bg_picture(self, name):
    """
    传入坐标,在网页全屏截图中截取图片
    :param name: 保存图片的名字
    :return: 图片对象
    """
    left, upper, right, lower = self.get_position()
    pageScreenshot = self.get_page_screenshot()
    bg_img = pageScreenshot.crop((left, upper, right, lower))
    bg_img.save(name)
    return bg_img

执行上述代码后,带滑块的验证码图片已经下载好了。

6. 执行js代码,隐藏滑块 + 缺口
看到完整的验证码图的源码如下图标注,
在这里插入图片描述
Console窗口执行 document.querySelectorAll(“canvas”)[2].style=“display:block” 后,可以看到滑块和缺口都不见了。
上面也说了另一种方法,就是修改element.styledisplay: none,也是一样的效果,但有有些不同,感兴趣的可以尝试以下。
在这里插入图片描述

def hide_slider(self):
    """
    执行js代码,隐藏滑块;两句js代码用法一样,但有些小差别
    :return:
    """
    js = 'document.querySelectorAll("canvas")[2].style=""'  # 获取文档中 第二个class="canvax" 的style=''元素
    # js = 'document.getElementsByClassName("geetest_canvas_slice geetest_absolute")[0].style="display:none;"'
    self.browser.execute_script(js)

执行该代码后,就会发现滑块 和 缺口都被隐藏起来了。

7. 输入坐标在网页全拼中截图,获取去除滑块+缺口验证码图片

这一步只需重复5. 操作即可。


2.2 求出缺口位置

求缺口,这一步,主要用的是Image模块,通过两张图片的像素点的对比得出缺口位置距离。
这里主要传入两张图片,便可求出缺口位置的距离了
在这里插入图片描述
在这里插入图片描述
嗯,上图说这个660有什么用呢??

  • 6是滑块与边框的距离,60是缺口与边框的距离
  • 所以,咱们求出来的缺口位置距离是不是应该减6呢??
def get_gap(self, img1, img2):
    """
    获取验证码图片中的缺口偏移量
    left设置60是因为缺口位置不会出现在60像素之前
    :param img1: 带滑块图片
    :param img2: 不带滑块图片
    :return: 返回缺口偏移量(缺口的左侧边界,x方向上的位置)
    """
    left = 60
    for i in range(left, img1.size[0]):
        for j in range(img1.size[1]):
            if not self.is_pixel_equal(img1, img2, i, j):
                left = i
                return left
    return left

def is_pixel_equal(self, img1, img2, x, y):
    """
    判断两个像素是否相同,对比两张图片的同一位置的像素点,若阈值相差超过60,则判断不相同
    :param img1: 不带缺口图片
    :param img2: 带缺口图
    :param x: 位置x
    :param y: 位置y
    :return: 像素点是否相同
    """
    # 获取图片指定位置的像素点
    pixel1 = img1.load()[x, y]
    pixel2 = img2.load()[x, y]
    threshold = 50
    # 允许图片的像素点的值有50以内的误差,对比两张图片的像素值,找出差距过大的位置
    if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
            pixel1[2] - pixel2[2]) < threshold and abs(pixel1[3] - pixel2[3]) < threshold:
        return True
    else:
        return False

执行以上代码,即可获得缺口的缺口偏移量。

2.3 模拟人为拖动

终于到了这最重要的一步了,模拟人为的滑动,有什么注意事项呢,这里来说一下:

  1. 人为拖动有快有慢,一般是先快后慢(这个套加速度公式可以模拟滑动轨迹)
  2. 人为拖动时候一般会超过缺口,然后再往回拖动滑块
  3. 人为拖动的滑块到缺口位置不一定完全贴切,网站方也会允许有2~3像素的差距
  4. 人为拖动会抖动,即滑动到要释放鼠标时候,手可能会轻微抖动,即前后可能会偏差2~3像素。
解决方法 方案
1 套用匀加速公式 S = Vot + 1/2at^2 ,先是加速度为正,再加速度为负
2 在缺口偏移量添加15,然后滑动时候再往回滑动15
3 随机设置0~3的随机值,为每次拖动的滑块位置与缺口的最终差距
4 模拟往前滑动2像素,再往后滑动2像素

在这里步骤3我没有实现,相当于留一点作业给你们做。


1.模拟人为拖动的轨迹

def get_track(self, distance):
    """
    根据偏移量获取移动轨迹
    :param distance: 缺口偏移量
    :return: 移动轨迹
    """
    # 移动轨迹
    track = []
    # 当前位移
    current = 0
    # 减速阈值
    mid = distance * 4 / 5
    # 计算间隔
    t = 0.2
    # 初速度
    v = 0
    # 这里对distance做了+15的操作,是模拟人为滑动超出了缺口,然后往回拖动
    distance += 15
    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

2.模拟拖动超出缺口,然后往回拖动轨迹

def get_back_track(self):
    """
    模拟滑动超出15像素后的往回滑动轨迹
    :return: 
    """
    tracks = [-1, -1, -1, -2, -2, -3, -2, -2, -1]
    for x in tracks:
        ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()

3.模拟释放鼠标手抖了一个机灵

def mouse_shake(self):
    """
    模拟释放鼠标手抖了一个机灵
    :return: 
    """
    ActionChains(self.browser).move_by_offset(xoffset=3, yoffset=0).perform()
    ActionChains(self.browser).move_by_offset(xoffset=-3, yoffset=0).perform()

4.selenium模拟拖动

def move_slider(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)
    self.get_back_track()	# 模拟划过头又滑回来
    time.sleep(0.1)
    self.mouse_shake()	# 模拟释放鼠标手抖动
    time.sleep(0.2)
    ActionChains(self.browser).release().perform()	# 释放鼠标

3. 完整代码

完整代码请移步我的github:https://github.com/SunriseCai/geetest_slider

4. 后面的话

在这里写一下整个步骤:
为什么写在最后呢,我想,能看到这里的该看到他。

步骤 描述
1 selenium打开网页
2 点击按钮,弹出滑动验证码
3 网页全屏截图
4 获取验证码坐标
5 输入坐标在网页全屏中截图,获取完整的验证码图片
6 执行js代码,隐藏滑块 + 缺口
7 输入坐标在网页全拼中截图,获取去除滑块+缺口验证码图片
8 根据5和7步骤获取的图片,计算出滑块缺口的位置距离
9 根据缺口的位置距离模拟人为滑动轨迹
10 模拟滑动滑块超出缺口,然后往回拖动滑块轨迹
11 模拟释放鼠标时候手抖轨迹
12 模拟拖动

最后来总结一下:

  • 整个过程下来虽然繁琐,但是整理通透之后回大仙并不难。
  • 繁琐在下载图片,难点在模拟滑动。
  • 文章太长,接近1万字数,难免又错误之处,恳请各位指正我的错误。
  • 好了,本次的分享到此结束,有任何疑问欢迎在下方留言。
发布了45 篇原创文章 · 获赞 528 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/weixin_45081575/article/details/104295417
今日推荐