美文网首页Python
Python 滑块验证码

Python 滑块验证码

作者: WrpZKB | 来源:发表于2020-10-15 09:47 被阅读0次
    image

    看了滑块验证码(滑动验证码)相比图形验证码,破解难度如何?中《Python3网络爬虫开发实战》作者 崔庆才丨静觅 的一个回答,里面有详细介绍如何对抗滑块验证码,因此学习一下,对此进行记录。

    正文

    [1]流程


    • 利用 Python selenium 自动化测试工具 直接拟人行为来完成滑块验证码验证
    • 分析页面,找出滑块验证码的完整图片,缺口图片。
    • 对比原始的图片和带缺口的图片的像素,像素不同的地方就是缺口位置
    • 计算出滑块缺口的位置,得到所需要滑动的偏移量
    • 使用物理加速度位移公式计算出移动轨迹
    • 最后利用 Selenium 进行对滑块的拖拽

    image

    [2]分析页面

    B站,是一个不错的学习网站 image

    ,记得很久之前第一次碰到滑块验证码登录时候就是在B站看见的,所以拿它来练手。:smiley:

    哔哩哔哩登录页面

    F12,打开开发者工具,找出登录框中有用的信息。

    用户名输入:  id="login-username"
    密码输入框:  id="login-passwd"
    登录按钮:    class="btn btn-login"
    带有缺口的验证码图片: class="geetest_canvas_bg geetest_absolute"
    需要滑动的验证码图片: class="geetest_canvas_slice geetest_absolute"
    完整的验证码图片:    class="geetest_canvas_fullbg geetest_fade geetest_absolute"
    滑块按钮:           class="geetest_slider_button"
    

    [3]编写代码

    导入库文件

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    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
    import time
    import random
    from PIL import Image
    
    

    <details>

    <summary ><font color=4B0082>USER_AGENT_LIST</font></summary>

    USER_AGENT_LIST = [
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
            "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
            "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10",
            "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
            "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14",
            "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14",
            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14",
            "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02",
            "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00",
            "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00",
            "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00",
            "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00",
            "Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0",
            "Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62",
            "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.10.229 Version/11.62",
            "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
            "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52",
            "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51",
            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51",
            "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50",
            "Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50",
        ]
    

    </details>

    初始化函数

    def init(self):
            """
            初始化变量
            :return:
            """
            global url, browser, username, password, wait
    
            url = 'https://passport.bilibili.com/login'
    
            path = r'G:\Python3\Scripts\chromedriver.exe'
            chrome_options = Options()
            #随机选择一个User_Agent
            user_agent = random.choice(USER_AGENT_LIST)
            #全屏
            chrome_options.add_argument('--start-maximized')
            chrome_options.add_argument('user-agent=%s'%user_agent)
            #开启开发者模式,可以进一步防止selenium被反爬虫识别
            chrome_options.add_experimental_option("excludeSwitches", ["ignore-certificate-errors","enable-automation"])
            browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    
            username = '用户名'
    
            password = '密码'
            wait = WebDriverWait(browser, 20)
    

    <code>global </code>关键字 定义了 url, browser, username, password, wait等全局变量,随后定义了chrome 的路径。

    登录函数

    def login(self):
            """
            输入帐号密码登录
            :return:
            """
            browser.get(url)
    
            user = wait.until(EC.presence_of_element_located((By.ID,'login-username')))
    
            passwd = wait.until(EC.presence_of_element_located((By.ID,'login-passwd')))
    
            user.send_keys(username)
            passwd.send_keys(password)
            #contains 就是包含,根据上面分析得知,登录按钮是的class是 btn btn-login 所以用XPATH 的contains函数就可以只选择其中之一。
            login_btn = wait.until(EC.presence_of_element_located((By.XPATH,"//a[contains(@class,'btn-login')]")))
            ran_time = random.random() * 2
            print("随机睡眠时间: ",ran_time)
            time.sleep(ran_time)
    
            login_btn.click()
    

    等待用户名输入框和密码输入框对应的 ID 节点加载出来

    获取这两个节点,用户名输入框 id="login-username",密码输入框 id="login-passwd"

    调用 send_keys() 方法输入用户名和密码

    获取登录按钮 class="btn btn-login"

    随机产生一个数并将其扩大两倍作为暂停时间

    最后调用 click() 方法实现登录按钮的点击


    【4】验证码处理模块

    验证码元素查找函数

    def find_code(self):
            """
            查找 验证码图片
            :return:
            """
            #带有缺口的图片
            code_bg = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_bg.geetest_absolute'))
            )
            #需要滑动的图片
            code_slice = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_slice.geetest_absolute'))
            )
            #完整的图片
            code_fullbg = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))
            )
            #隐藏验证码
            self.hide_element(code_slice)
    
            #保存带有缺口的验证码图片
            self.save_screenshot(code_bg,'bg')
    
            #显示需要滑动的验证码图片
            self.show_element(code_slice)
    
            #保存需要滑动的验证码图片
            self.save_screenshot(code_slice,"slice")
    
            #显示完整验证码图片
            self.show_element(code_fullbg)
    
            #保存完整验证码图片
            self.save_screenshot(code_fullbg,"full")
    

    获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片

    分析页面代码,三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片

    定位三张图片的 class 分别为:带有缺口的图片(code_bg):geetest_canvas_bg geetest_absolute、需要滑动的图片(code_slice):geetest_canvas_slice geetest_absolute、完整图片(code_fullbg):geetest_canvas_fullbg geetest_fade geetest_absolute

    最后传值给 save_screenshot() 函数,进一步对验证码进行处理

    image

    隐藏展示函数

    def hide_element(self,element):
            """
            隐藏属性
            :return:
            """
            browser.execute_script("arguments[0].style=arguments[1]",element,"display:none;")
    
    def show_element(self,element):
            """
            显示属性
            :return:
            """
            browser.execute_script("arguments[0].style=arguments[1]",element,"display:block;")
    

    设置元素可见,或隐藏功能。

    网页截图函数

     def save_screenshot(self,obj,name):
            """
            网页截图,获取验证码图片
            :param name:图片名字
            :return:截图对象
            """
            #obj 需要 截图的 位置  name 文件名称
            try:
                #save_screenshot 对整个网页进行截图
                pic_url = browser.save_screenshot('./bilibili.png')
                print("%s截图成功"%pic_url)
                left,top,right,bottom = obj.location['x'],obj.location['y'],obj.location['x'] + obj.size['width'],obj.location['y'] + obj.size['height']
                print('图:' + name)
                print('Left %s' % left)
                print('Top %s' % top)
                print('Right %s' % right)
                print('Bottom %s' % bottom)
                print('')
                # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存
                im = Image.open('./bilibili.png')
                im = im.crop((left, top, right, bottom)) #对浏览器截图进行裁剪
                file_name = 'bili_' + name + '.png'
                im.save(file_name)
            except BaseException as msg:
                print("%s:截图失败"%msg)
    

    save_screenshot方法把网页截图保存为bilibili.png图片,

    obj为三张验证码图片对象,获取图片的x,y,宽,高

    接着打开网页的截图,通过三张验证码图片的坐标,

    调用crop()方法将其裁剪出来,在进行保存。


    【5】验证码滑动模块

    滑动模块主函数

    def slide(self):
            """
            :return:
            """
            distance = self.get_distance(Image.open('./bili_back.png'),Image.open('./bili_full.png'))
            print('计算偏移量:%s Px'% distance)
            trace = self.get_trace(distance - 5)
            self.move_to_gap(trace)
            time.sleep(3)
    

    get_distance方法传入缺口图片和完整图片,计算滑块偏移量

    distance通过 get_distance方法返回值,获取验证码缺口偏移量

    在把偏移量传入 get_trace方法中,通过物理加速度位移公式,构造出滑块的移动轨迹。 distance -5是减去滑块缺口偏移

    在把传回来的值传入move_to_gap方法实现拟人操作。

    获取缺口偏移量

    def get_distance(self,bg_image,fullbg_image):
            """
            获取缺口偏移量
            :param bg_image:带缺口图片
            :param fullbg_image:不带缺口图片
            :return:
            """
            #坐标设为60起始位置
            distance = 60
            for i in range(distance,fullbg_image.size[0]):
                for j in range(fullbg_image.size[1]):
                    if not self.is_pixel_equal(fullbg_image,bg_image,i,j):
                        distance = i
                        return distance
            return distance
        
    def is_pixel_equal(self,bg_image,fullbg_image,x,y):
            """
            判断两个像素是否相同
            :param bg_image:带缺口图片
            :param fullbg_image:不带缺口图片
            :param x:位置x
            :param y:位置y
            :return:像素是否相同
            """
            #获得两章图片对应像素点的RGB数据
            bg_pixel = bg_image.load()[x,y]
            fullbg_pixel = fullbg_image.load()[x,y]
            #设定一个阈值,像素也许存在误差, 60作为容差范围
            threshold = 60
            #比较两张图 RGB 的 绝对值是否小于定义的阈值
            for i in range(0,3):
                if (abs(bg_pixel[i] - fullbg_pixel[i]<threshold)):
                    return True
            # if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold)and abs(bg_pixel[1] - fullbg_pixel[1] <threshold) and abs(bg_pixel[2] - fullbg_pixel[2] <threshold)):
                #return True
            return False
    

    get_distance方法获取缺口偏移量,就是获取缺口位置,此方法的两个参数为两张图片,一张为带缺口图片,一张为完整图片,在这里遍历两张图片的每个像素,然后利用is_pixel_equal方法判断两张图片同一位置的像素是否相同,比对时候,比较了两张图片的RGB的绝对值是否均小于阈值threshold,如果均在阈值之内,则像素点相同,继续遍历,否则遇到不相同的像素点就是缺口的位置。

    需要滑动的图片:

    image

    完整的图片:

    image

    通过观察,其实可以发现,滑块位置会出现在图片左边位置,缺口的位置通常处于图片的右边位置,缺口和滑块会处于同一水平线上,所以要寻找缺口的话,直接从右侧开始寻找即可,所以在遍历开始时候,直接设置了遍历的起始坐标为distance 60,也就是从滑块的右侧开始识别。

    模拟拖动

    多次试验发现,模拟拖动这个操作不难,但是按照实际操作来说,人为拖动这个模块,是无法做到完全匀速拖动。

    人手会因为距离的变短而减慢速度确认位置,可能会出现抖动,往回拖拉等操作,所以如果出现匀速操作,就会被识别出是程序在操作,检测机制会根据其机器学习模型筛选出此类数据,归类为机器操作,就会出现该图片被怪兽吃掉的情况。

    要让程序根据距离长短,来为其加速或者减速操作,可以利用物理学的加速度位移公式来完成:
    速度公式 V=V0+at,V=at

    位移公式:X=v0t+1/2at2,X=1/2at2

    用Python 来表示就是:

    #运用物理加速度位移相关公式  X=v0 * t+ 1/2 * a * t*t
    #a 加速度 X 位移 v0 初速度
    X = v0 * t +1/2 * a * t * t
    #当前时刻的速度
    v = v0 + a * t
    

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

    #模拟人工拉动滑块
    def get_trace(self,distance):
            """
            根据偏移量获取移动轨迹
            :param distance:偏移量
            :return:滑动轨迹
            """
            trace = []
            # 设置加速距离为总距离的 4 /5
            mid = distance * (4 / 5)
            #设置当前位移 , 初始速度、时间间隔
            current,v0 ,t = 0,0,0.1
    
            while current< distance:
                if current< mid:
                  #加速度为正10
                    a =10
                else:
                  #减速度为负10
                    a = -10
                #运用物理加速度位移相关公式  X=v0 * t+ 1/2 * a * t*t
                #a 加速度 X 位移 v0 初速度
                X = v0 * t +1/2 * a * t * t
                #当前时刻的速度
                v = v0 + a * t
                v0 =v
                current +=X
                #记录每个时间间隔移动的多少位移
                trace.append(round(X))
            return trace
    

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

    定义了一个变量mid,用来控制减速的阈值,既模拟前 4/5的路程是加速路程,后1/5是减速路程,但是如果偏移量过大时候,会被检测出,可能是前面4/5的路程过于匀速。所以可以设置为 7/8.

    在定义当前位移 current,初始为0,随后进入while循环,循环条件是当前位移小于偏移量,在循环里分段定义了加速度,其中加速过程加速度定义为10,减速度定义为负10,随后再套用物理学加速位移公式算出某个时间段内的位移,同时将该位移更新并记录到轨迹trace里。

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

    然后在按照该运动轨迹传入move_to_gap方法实现拖动模块

    def move_to_gap(self,trace):
            """
            拖动滑块到缺口处
            :param trace:轨迹
            :return:
            """
            slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'div.geetest_slider_button')))
            ActionChains(browser).click_and_hold(slider).perform()
    
            for x in trace:
                ActionChains(browser).move_by_offset(xoffset=x,yoffset=0).perform()
    
            time.sleep(0.5)
    
            ActionChains(browser).release().perform()
    

    在这里传入的参数为运动轨迹

    定义了slider获取滑块对象

    调用ActionChainsclick_and_hold方法按住拖动底部滑块,随后遍历运动轨迹获取每小段位移距离

    在调用move_by_offset方法移动此位移,最后移动完成之后调用release方法松开鼠标。


    【6】完整代码

    # =============================================
    # -*- coding: utf-8 -*-
    # @Time    : 2020-02-06
    # @Author  : KeyboArd
    # @Blog    : www.wrpzkb.cn
    # @FileName: bilibili_login.py
    # @Software: PyCharm
    # =============================================
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    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
    import time
    import random
    from PIL import Image
    USER_AGENT_LIST = [
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
            "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
            "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10",
            "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
            "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14",
            "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14",
            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14",
            "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02",
            "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00",
            "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00",
            "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00",
            "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00",
            "Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0",
            "Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62",
            "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.10.229 Version/11.62",
            "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
            "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52",
            "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51",
            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51",
            "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50",
            "Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50",
        ]
    
    class bilibili_code():
        def init(self):
            """
            初始化变量
            :return:
            """
            global url, browser, username, password, wait
    
            url = 'https://passport.bilibili.com/login'
    
            path = r'G:\Python3\Scripts\chromedriver.exe'
            chrome_options = Options()
            user_agent = random.choice(USER_AGENT_LIST)
            #全屏
            chrome_options.add_argument('--start-maximized')
            chrome_options.add_argument('user-agent=%s'%user_agent)
           # chrome_options.add_argument(user_agent)
            chrome_options.add_experimental_option("excludeSwitches", ["ignore-certificate-errors","enable-automation"])
            browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    
            username = '用户名'
    
            password = '密码'
            wait = WebDriverWait(browser, 20)
    
        def login(self):
            """
            输入帐号密码登录
            :return:
            """
            browser.get(url)
    
            user = wait.until(EC.presence_of_element_located((By.ID,'login-username')))
    
            passwd = wait.until(EC.presence_of_element_located((By.ID,'login-passwd')))
    
            user.send_keys(username)
            passwd.send_keys(password)
    
            login_btn = wait.until(EC.presence_of_element_located((By.XPATH,"//a[contains(@class,'btn-login')]")))
            ran_time = random.random() * 2
            print("随机睡眠时间: ",ran_time)
            time.sleep(ran_time)
    
            login_btn.click()
    
    
        def find_code(self):
            """
            查找 验证码图片
            :return:
            """
            #带有缺口的图片
            code_bg = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_bg.geetest_absolute'))
            )
            #需要滑动的图片
            code_slice = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_slice.geetest_absolute'))
            )
            #完整的图片
            code_fullbg = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))
            )
            #隐藏验证码
            self.hide_element(code_slice)
    
            #保存带有缺口的验证码图片
            self.save_screenshot(code_bg,'bg')
    
            #显示需要滑动的验证码图片
            self.show_element(code_slice)
    
            #保存需要滑动的验证码图片
            self.save_screenshot(code_slice,"slice")
    
            #显示完整验证码图片
            self.show_element(code_fullbg)
    
            #保存完整验证码图片
            self.save_screenshot(code_fullbg,"full")
    
    
        def hide_element(self,element):
            """
            隐藏属性
            :return:
            """
            browser.execute_script("arguments[0].style=arguments[1]",element,"display:none;")
    
        def show_element(self,element):
            """
            显示属性
            :return:
            """
            browser.execute_script("arguments[0].style=arguments[1]",element,"display:block;")
    
        def save_screenshot(self,obj,name):
            """
            网页截图,获取验证码图片
            :param name:图片名字
            :return:截图对象
            """
            #obj 需要 截图的 位置  name 文件名称
            try:
                #save_screenshot 对整个网页进行截图
                pic_url = browser.save_screenshot('./bilibili.png')
                print("%s截图成功"%pic_url)
                left,top,right,bottom = obj.location['x'],obj.location['y'],obj.location['x'] + obj.size['width'],obj.location['y'] + obj.size['height']
                print('图:' + name)
                print('Left %s' % left)
                print('Top %s' % top)
                print('Right %s' % right)
                print('Bottom %s' % bottom)
                print('')
                # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存
                im = Image.open('./bilibili.png')
                im = im.crop((left, top, right, bottom)) #对浏览器截图进行裁剪
                file_name = 'bili_' + name + '.png'
                im.save(file_name)
            except BaseException as msg:
                print("%s:截图失败"%msg)
    
        def slide(self):
            """
            :return:
            """
            distance = self.get_distance(Image.open('./bili_back.png'),Image.open('./bili_full.png'))
            print('计算偏移量:%s Px'% distance)
            trace = self.get_trace(distance - 5)
            self.move_to_gap(trace)
            time.sleep(3)
    
        def get_distance(self,bg_image,fullbg_image):
            """
            获取缺口偏移量
            :param bg_image:带缺口图片
            :param fullbg_image:不带缺口图片
            :return:
            """
            distance = 60
            for i in range(distance,fullbg_image.size[0]):
                for j in range(fullbg_image.size[1]):
                    if not self.is_pixel_equal(fullbg_image,bg_image,i,j):
                        distance = i
                        return distance
            return distance
    
        def is_pixel_equal(self,bg_image,fullbg_image,x,y):
            """
            判断两个像素是否相同
            :param bg_image:带缺口图片
            :param fullbg_image:不带缺口图片
            :param x:位置x
            :param y:位置y
            :return:像素是否相同
            """
            #获得两章图片对应像素点的RGB数据
            bg_pixel = bg_image.load()[x,y]
            fullbg_pixel = fullbg_image.load()[x,y]
            #设定一个阈值,像素也许存在误差, 60作为容差范围
            threshold = 60
            #比较两张图 RGB 的 绝对值是否小于定义的阈值
            for i in range(0,3):
                if (abs(bg_pixel[i] - fullbg_pixel[i]<threshold)):
                    return True
            # if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold)and abs(bg_pixel[1] - fullbg_pixel[1] <threshold) and abs(bg_pixel[2] - fullbg_pixel[2] <threshold)):
                #return True
            return False
    
        #模拟人工拉动滑块
        def get_trace(self,distance):
            """
            根据偏移量获取移动轨迹
            :param distance:偏移量
            :return:滑动轨迹
            """
            trace = []
            # 设置加速距离为总距离的 4 /5
            mid = distance * (4 / 5)
            #设置当前位移, 初始速度、时间间隔
            current,v0 ,t = 0,0,0.1
    
            while current< distance:
                if current< mid:
                    #加速度为正10
                    a =10
                else:
                    #减速度为负10
                    a = -10
                #运用物理加速度位移相关公式  X=v0 * t+ 1/2 * a * t*t
                #a 加速度 X 位移 v0 初速度
                X = v0 * t +1/2 * a * t * t
                #当前时刻的速度
                v = v0 + a * t
                v0 =v
                current +=X
                #记录每个时间间隔移动的多少位移
                trace.append(round(X))
            return trace
    
        def move_to_gap(self,trace):
            """
            拖动滑块到缺口处
            :param trace:轨迹
            :return:
            """
            slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'div.geetest_slider_button')))
            ActionChains(browser).click_and_hold(slider).perform()
    
            for x in trace:
                ActionChains(browser).move_by_offset(xoffset=x,yoffset=0).perform()
    
            time.sleep(0.5)
    
            ActionChains(browser).release().perform()
    
        def crack(self):
            self.init()
            self.login()
            self.find_code()
            self.slide()
    
            success = browser.current_url
            if success == "https://www.bilibili.com/":
                print("登录成功")
                browser.close()
            else:
                self.crack()
    
    
    
    if __name__ == '__main__':
        bi = bilibili_code()
        bi.crack()
    
    

    【7】效果实现动画

    image

    相关文章

      网友评论

        本文标题:Python 滑块验证码

        本文链接:https://www.haomeiwen.com/subject/undnpktx.html