Python 滑塊驗證碼

image

看了滑塊驗證碼(滑動驗證碼)相比圖形驗證碼,破解難度如何?中《Python3網(wǎng)絡(luò)爬蟲開發(fā)實戰(zhàn)》作者 崔慶才丨靜覓 的一個回答,里面有詳細(xì)介紹如何對抗滑塊驗證碼,因此學(xué)習(xí)一下,對此進(jìn)行記錄。

正文

[1]流程


  • 利用 Python selenium 自動化測試工具 直接擬人行為來完成滑塊驗證碼驗證
  • 分析頁面,找出滑塊驗證碼的完整圖片,缺口圖片。
  • 對比原始的圖片和帶缺口的圖片的像素,像素不同的地方就是缺口位置
  • 計算出滑塊缺口的位置,得到所需要滑動的偏移量
  • 使用物理加速度位移公式計算出移動軌跡
  • 最后利用 Selenium 進(jìn)行對滑塊的拖拽

image

[2]分析頁面

B站,是一個不錯的學(xué)習(xí)網(wǎng)站
image

,記得很久之前第一次碰到滑塊驗證碼登錄時候就是在B站看見的,所以拿它來練手。:smiley:

嗶哩嗶哩登錄頁面

F12,打開開發(fā)者工具,找出登錄框中有用的信息。

用戶名輸入:  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]編寫代碼

導(dǎo)入庫文件

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>

初始化函數(shù)

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)
        #開啟開發(fā)者模式,可以進(jìn)一步防止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>關(guān)鍵字 定義了 url, browser, username, password, wait等全局變量,隨后定義了chrome 的路徑。

登錄函數(shù)

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 就是包含,根據(jù)上面分析得知,登錄按鈕是的class是 btn btn-login 所以用XPATH 的contains函數(shù)就可以只選擇其中之一。
        login_btn = wait.until(EC.presence_of_element_located((By.XPATH,"http://a[contains(@class,'btn-login')]")))
        ran_time = random.random() * 2
        print("隨機睡眠時間: ",ran_time)
        time.sleep(ran_time)

        login_btn.click()

等待用戶名輸入框和密碼輸入框?qū)?yīng)的 ID 節(jié)點加載出來

獲取這兩個節(jié)點,用戶名輸入框 id="login-username",密碼輸入框 id="login-passwd"

調(diào)用 send_keys() 方法輸入用戶名和密碼

獲取登錄按鈕 class="btn btn-login"

隨機產(chǎn)生一個數(shù)并將其擴(kuò)大兩倍作為暫停時間

最后調(diào)用 click() 方法實現(xiàn)登錄按鈕的點擊


【4】驗證碼處理模塊

驗證碼元素查找函數(shù)

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 為不可見,在分別獲取三張圖片時要將其他兩張圖片設(shè)置為 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() 函數(shù),進(jìn)一步對驗證碼進(jìn)行處理

image

隱藏展示函數(shù)

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;")

設(shè)置元素可見,或隱藏功能。

網(wǎng)頁截圖函數(shù)

 def save_screenshot(self,obj,name):
        """
        網(wǎng)頁截圖,獲取驗證碼圖片
        :param name:圖片名字
        :return:截圖對象
        """
        #obj 需要 截圖的 位置  name 文件名稱
        try:
            #save_screenshot 對整個網(wǎng)頁進(jìn)行截圖
            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('')
            # 在整個頁面截圖的基礎(chǔ)上,根據(jù)位置信息,分別剪裁出三張驗證碼圖片并保存
            im = Image.open('./bilibili.png')
            im = im.crop((left, top, right, bottom)) #對瀏覽器截圖進(jìn)行裁剪
            file_name = 'bili_' + name + '.png'
            im.save(file_name)
        except BaseException as msg:
            print("%s:截圖失敗"%msg)

save_screenshot方法把網(wǎng)頁截圖保存為bilibili.png圖片,

obj為三張驗證碼圖片對象,獲取圖片的x,y,寬,高

接著打開網(wǎng)頁的截圖,通過三張驗證碼圖片的坐標(biāo),

調(diào)用crop()方法將其裁剪出來,在進(jìn)行保存。


【5】驗證碼滑動模塊

滑動模塊主函數(shù)

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方法中,通過物理加速度位移公式,構(gòu)造出滑塊的移動軌跡。 distance -5是減去滑塊缺口偏移

在把傳回來的值傳入move_to_gap方法實現(xiàn)擬人操作。

獲取缺口偏移量

def get_distance(self,bg_image,fullbg_image):
        """
        獲取缺口偏移量
        :param bg_image:帶缺口圖片
        :param fullbg_image:不帶缺口圖片
        :return:
        """
        #坐標(biāo)設(shè)為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:像素是否相同
        """
        #獲得兩章圖片對應(yīng)像素點的RGB數(shù)據(jù)
        bg_pixel = bg_image.load()[x,y]
        fullbg_pixel = fullbg_image.load()[x,y]
        #設(shè)定一個閾值,像素也許存在誤差, 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方法獲取缺口偏移量,就是獲取缺口位置,此方法的兩個參數(shù)為兩張圖片,一張為帶缺口圖片,一張為完整圖片,在這里遍歷兩張圖片的每個像素,然后利用is_pixel_equal方法判斷兩張圖片同一位置的像素是否相同,比對時候,比較了兩張圖片的RGB的絕對值是否均小于閾值threshold,如果均在閾值之內(nèi),則像素點相同,繼續(xù)遍歷,否則遇到不相同的像素點就是缺口的位置。

需要滑動的圖片:

image

完整的圖片:

image

通過觀察,其實可以發(fā)現(xiàn),滑塊位置會出現(xiàn)在圖片左邊位置,缺口的位置通常處于圖片的右邊位置,缺口和滑塊會處于同一水平線上,所以要尋找缺口的話,直接從右側(cè)開始尋找即可,所以在遍歷開始時候,直接設(shè)置了遍歷的起始坐標(biāo)為distance 60,也就是從滑塊的右側(cè)開始識別。

模擬拖動

多次試驗發(fā)現(xiàn),模擬拖動這個操作不難,但是按照實際操作來說,人為拖動這個模塊,是無法做到完全勻速拖動。

人手會因為距離的變短而減慢速度確認(rèn)位置,可能會出現(xiàn)抖動,往回拖拉等操作,所以如果出現(xiàn)勻速操作,就會被識別出是程序在操作,檢測機制會根據(jù)其機器學(xué)習(xí)模型篩選出此類數(shù)據(jù),歸類為機器操作,就會出現(xiàn)該圖片被怪獸吃掉的情況。

要讓程序根據(jù)距離長短,來為其加速或者減速操作,可以利用物理學(xué)的加速度位移公式來完成:
速度公式?V=V0+at,V=at

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

用Python 來表示就是:

#運用物理加速度位移相關(guān)公式  X=v0 * t+ 1/2 * a * t*t
#a 加速度 X 位移 v0 初速度
X = v0 * t +1/2 * a * t * t
#當(dāng)前時刻的速度
v = v0 + a * t

運用這兩個公式可以構(gòu)造一個軌跡移動算法,計算出先加速后減速的運動軌跡:

#模擬人工拉動滑塊
def get_trace(self,distance):
        """
        根據(jù)偏移量獲取移動軌跡
        :param distance:偏移量
        :return:滑動軌跡
        """
        trace = []
        # 設(shè)置加速距離為總距離的 4 /5
        mid = distance * (4 / 5)
        #設(shè)置當(dāng)前位移 , 初始速度、時間間隔
        current,v0 ,t = 0,0,0.1

        while current< distance:
            if current< mid:
              #加速度為正10
                a =10
            else:
              #減速度為負(fù)10
                a = -10
            #運用物理加速度位移相關(guān)公式  X=v0 * t+ 1/2 * a * t*t
            #a 加速度 X 位移 v0 初速度
            X = v0 * t +1/2 * a * t * t
            #當(dāng)前時刻的速度
            v = v0 + a * t
            v0 =v
            current +=X
            #記錄每個時間間隔移動的多少位移
            trace.append(round(X))
        return trace

get_trace() 方法傳入的參數(shù)為移動的總距離,返回的是運動軌跡,用 trace 表示,它是一個列表,列表的每個元素代表每次移動多少距離。

定義了一個變量mid,用來控制減速的閾值,既模擬前 4/5的路程是加速路程,后1/5是減速路程,但是如果偏移量過大時候,會被檢測出,可能是前面4/5的路程過于勻速。所以可以設(shè)置為 7/8.

在定義當(dāng)前位移 current,初始為0,隨后進(jìn)入while循環(huán),循環(huán)條件是當(dāng)前位移小于偏移量,在循環(huán)里分段定義了加速度,其中加速過程加速度定義為10,減速度定義為負(fù)10,隨后再套用物理學(xué)加速位移公式算出某個時間段內(nèi)的位移,同時將該位移更新并記錄到軌跡trace里。

當(dāng)達(dá)到總距離時既停止循環(huán),最后得到的trace既記錄了每個時間間隔移動了多少位移,這樣滑塊的運動軌跡就得到了。

然后在按照該運動軌跡傳入move_to_gap方法實現(xiàn)拖動模塊

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()

在這里傳入的參數(shù)為運動軌跡

定義了slider獲取滑塊對象

調(diào)用ActionChainsclick_and_hold方法按住拖動底部滑塊,隨后遍歷運動軌跡獲取每小段位移距離

在調(diào)用move_by_offset方法移動此位移,最后移動完成之后調(diào)用release方法松開鼠標(biāo)。


【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,"http://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):
        """
        網(wǎng)頁截圖,獲取驗證碼圖片
        :param name:圖片名字
        :return:截圖對象
        """
        #obj 需要 截圖的 位置  name 文件名稱
        try:
            #save_screenshot 對整個網(wǎng)頁進(jìn)行截圖
            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('')
            # 在整個頁面截圖的基礎(chǔ)上,根據(jù)位置信息,分別剪裁出三張驗證碼圖片并保存
            im = Image.open('./bilibili.png')
            im = im.crop((left, top, right, bottom)) #對瀏覽器截圖進(jìn)行裁剪
            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:像素是否相同
        """
        #獲得兩章圖片對應(yīng)像素點的RGB數(shù)據(jù)
        bg_pixel = bg_image.load()[x,y]
        fullbg_pixel = fullbg_image.load()[x,y]
        #設(shè)定一個閾值,像素也許存在誤差, 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):
        """
        根據(jù)偏移量獲取移動軌跡
        :param distance:偏移量
        :return:滑動軌跡
        """
        trace = []
        # 設(shè)置加速距離為總距離的 4 /5
        mid = distance * (4 / 5)
        #設(shè)置當(dāng)前位移, 初始速度、時間間隔
        current,v0 ,t = 0,0,0.1

        while current< distance:
            if current< mid:
                #加速度為正10
                a =10
            else:
                #減速度為負(fù)10
                a = -10
            #運用物理加速度位移相關(guān)公式  X=v0 * t+ 1/2 * a * t*t
            #a 加速度 X 位移 v0 初速度
            X = v0 * t +1/2 * a * t * t
            #當(dāng)前時刻的速度
            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】效果實現(xiàn)動畫

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容