【Python】Python3網絡爬蟲實戰-43、極驗滑動驗證碼的識別

上節我們瞭解了圖形驗證碼的識別,簡單的圖形驗證碼我們可以直接利用 Tesserocr 來識別,但是近幾年又出現了一些新型驗證碼,如滑動驗證碼,比較有代表性的就是極驗驗證碼,它需要拖動拼合滑塊纔可以完成驗證,相對圖形驗證碼來說識別難度上升了幾個等級,本節來講解下極驗驗證碼的識別過程。

1. 本節目標

本節我們的目標是用程序來識別並通過極驗驗證碼的驗證,其步驟有分析識別思路、識別缺口位置、生成滑塊拖動路徑,最後模擬實現滑塊拼合通過驗證。

2. 準備工作

本次我們使用的 Python 庫是 Selenium,使用的瀏覽器爲 Chrome,在此之前請確保已經正確安裝好了 Selenium 庫、Chrome瀏覽器並配置好了 ChromeDriver,相關流程可以參考第一章的說明。學習過程中有不懂的可以加入我們的學習交流秋秋圈784中間758後面214,與你分享Python企業當下人才需求及怎麼從零基礎學習Python,和學習什麼內容。相關學習視頻資料、開發工具都有分享

3. 瞭解極驗驗證碼

極驗驗證碼其官網爲:http://www.geetest.com/,它是一個專注於提供驗證安全的系統,主要驗證方式是拖動滑塊拼合圖像,若圖像完全拼合,則驗證成功,即可以成功提交表單,否則需要重新驗證,樣例如圖8-5 和 8-6 所示:

圖 8-5 驗證碼示例

圖 8-6 驗證碼示例

現在極驗驗證碼已經更新到了 3.0 版本,截至 2017 年 7 月全球已有十六萬家企業正在使用極驗,每天服務響應超過四億次,廣泛應用於直播視頻、金融服務、電子商務、遊戲娛樂、政府企業等各大類型網站,下面是鬥魚、魅族的登錄頁面,可以看到其都對接了極驗驗證碼,如圖 8-7 和 8-8 所示:

圖 8-7 鬥魚登錄頁面

圖 8-8 魅族登錄頁面

4. 極驗驗證碼的特點

這種驗證碼相較於圖形驗證碼來說識別難度更大,極驗驗證碼首先需要在前臺驗證通過,對於極驗 3.0,我們首先需要點擊按鈕進行智能驗證,如果驗證不通過,則會彈出滑動驗證的窗口,隨後需要拖動滑塊拼合圖像進行驗證,驗證之後會生成三個加密參數,參數隨後通過表單提交到後臺,後臺還會進行一次驗證。

另外極驗還增加了機器學習的方法來識別拖動軌跡,官方網站的安全防護說明如下:

  • 三角防護之防模擬

惡意程序模仿人類行爲軌跡對驗證碼進行識別。針對模擬,極驗擁有超過 4000 萬人機行爲樣本的海量數據。利用機器學習和神經網絡構建線上線下的多重靜態、動態防禦模型。識別模擬軌跡,界定人機邊界。

  • 三角防護之防僞造

惡意程序通過僞造設備瀏覽器環境對驗證碼進行識別。針對僞造,極驗利用設備基因技術。深度分析瀏覽器的實際性能來辨識僞造信息。同時根據僞造事件不斷更新黑名單,大幅提高防僞造能力。

  • 三角防護之防暴力

惡意程序短時間內進行密集的攻擊,對驗證碼進行暴力識別
針對暴力,極驗擁有多種驗證形態,每一種驗證形態都有利用神經網絡生成的海量圖庫儲備,每一張圖片都是獨一無二的,且圖庫不斷更新,極大程度提高了暴力識別的成本。

另外極驗的驗證相對於普通驗證方式更加方便,體驗更加友好,其官方網站說明如下:

  • 點擊一下,驗證只需要 0.4 秒

極驗始終專注於去驗證化實踐,讓驗證環節不再打斷產品本身的交互流程,最終達到優化用戶體驗和提高用戶轉化率的效果。

  • 全平臺兼容,適用各種交互場景

極驗兼容所有主流瀏覽器甚至古老的IE6,也可以輕鬆應用在iOS和Android移動端平臺,滿足各種業務需求,保護網站資源不被濫用和盜取。

  • 面向未來,懂科技,更懂人性

極驗在保障安全同時不斷致力於提升用戶體驗,精雕細琢的驗證面板,流暢順滑的驗證動畫效果,讓驗證過程不再枯燥乏味。

因此,相較於一般驗證碼,極驗的驗證安全性和易用性有了非常大的提高。

5. 識別思路

但是對於應用了極驗驗證碼的網站,識別並不是沒有辦法的。如果我們直接模擬表單提交的話,加密參數的構造是個問題,參數構造有問題服務端就會校驗失敗,所以在這裏我們採用直接模擬瀏覽器動作的方式來完成驗證,在 Python 中我們就可以使用 Selenium 來通過完全模擬人的行爲的方式來完成驗證,此驗證成本相對於直接去識別加密算法容易不少。

首先我們找到一個帶有極驗驗證的網站,最合適的當然爲極驗官方後臺了,鏈接爲:https://account.geetest.com/login,首先可以看到在登錄按鈕上方有一個極驗驗證按鈕,如圖 8-9 所示:

圖 8-9 驗證按鈕

此按鈕爲智能驗證按鈕,點擊一下即可智能驗證,一般來說如果是同一個 Session,一小段時間內第二次登錄便會直接通過驗證,如果智能識別不通過,則會彈出滑動驗證窗口,我們便需要拖動滑塊來拼合圖像完成二步驗證,如圖 8-10 所示:

圖 8-10 拖動示例

驗證成功後驗證按鈕便會變成如下狀態,如圖 8-11 所示:

圖 8-11 驗證成功結果

接下來我們便可以進行表單提交了。

所以在這裏我們要識別驗證需要做的有三步:

  • 模擬點擊驗證按鈕
  • 識別滑動缺口的位置
  • 模擬拖動滑塊

第一步操作是最簡單的,我們可以直接用 Selenium 模擬點擊按鈕即可。

第二步操作識別缺口的位置比較關鍵,需要用到圖像的相關處理方法,那缺口怎麼找呢?首先來觀察一下缺口的樣子,如圖 8-12 和 8-13 所示:

圖 8-12 缺口示例

圖 8-13 缺口示例

可以看到缺口的四周邊緣有明顯的斷裂邊緣,而且邊緣和邊緣周圍有明顯的區別,我們可以實現一個邊緣檢測算法來找出缺口的位置。對於極驗來說,我們可以利用和原圖對比檢測的方式來識別缺口的位置,因爲在沒有滑動滑塊之前,缺口其實是沒有呈現的,如圖 8-14 所示:

圖 8-14 初始狀態

所以我們可以同時獲取兩張圖片,設定一個對比閾值,然後遍歷兩張圖片找出相同位置像素 RGB 差距超過此閾值的像素點位置,那麼此位置就是缺口的位置。

第三步操作看似簡單,但是其中的坑比較多,極驗驗證碼增加了機器軌跡識別,勻速移動、隨機速度移動等方法都是不行的,只有完全模擬人的移動軌跡纔可以通過驗證,而人的移動軌跡一般是先加速後減速的,這又涉及到物理學中加速度的相關問題,我們需要模擬這個過程才能成功。

有了基本的思路之後就讓我們用程序來實現一下它的識別過程吧。

6. 初始化

首先這次我們選定的鏈接爲:https://account.geetest.com/login,也就是極驗的管理後臺登錄頁面,在這裏我們首先初始化一些配置,如 Selenium 對象的初始化及一些參數的配置:

EMAIL  =  '[email protected]'

PASSWORD  =  '123456'

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

其中 EMAIL 和 PASSWORD 就是登錄極驗需要的用戶名和密碼,如果沒有的話可以先註冊一下。

7. 模擬點擊

隨後我們需要實現第一步的操作,也就是模擬點擊初始的驗證按鈕,所以我們定義一個方法來獲取這個按鈕,利用顯式等待的方法來實現:


def get_geetest_button(self):

    """

    獲取初始驗證按鈕

    :return: 按鈕對象

    """

    button  =  self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,  'geetest_radar_tip')))

    return  button

獲取之後就會獲取一個 WebElement 對象,調用它的 click() 方法即可模擬點擊,代碼如下:

# 點擊驗證按鈕

button  =  self.get_geetest_button()

button.click()

到這裏我們第一步的工作就完成了。

8. 識別缺口

接下來我們需要識別缺口的位置,首先我們需要將前後的兩張比對圖片獲取下來,然後比對二者的不一致的地方即爲缺口。首先我們需要獲取不帶缺口的圖片,利用 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_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))

    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,如果均在閾值之內,則像素點相同,繼續遍歷,否則遇到不相同的像素點就是缺口的位置。

在這裏比如兩張對比圖片如下,如圖 8-15 和 8-16 所示:

圖 8-15 初始狀態

圖 8-16 後續狀態

兩張圖片其實有兩處明顯不同的地方,一個就是待拼合的滑塊,一個就是缺口,但是滑塊的位置會出現在左邊位置,缺口會出現在與滑塊同一水平線的位置,所以缺口一般會在滑塊的右側,所以要尋找缺口的話,我們直接從滑塊右側尋找即可,所以在遍歷的時候我們直接設置了遍歷的起始橫座標爲 60,也就是在滑塊的右側開始識別,這樣識別出的結果就是缺口的位置了。

到現在爲止,我們就可以獲取缺口的位置了,剩下最後一步模擬拖動就可以完成驗證了。

9. 模擬拖動

模擬拖動的這個過程說複雜並不複雜,只是其中的坑比較多。現在我們已經獲取到了缺口的位置,接下來只需要調用拖動的相關函數將滑塊拖動到對應位置不就好了嗎?然而事實很殘酷,如果勻速拖動,極驗必然會識別出來這是程序的操作,因爲人是無法做到完全勻速拖動的,極驗利用機器學習模型篩選出此類數據,歸類爲機器操作,驗證碼識別失敗。

隨後我又嘗試了分段模擬,將拖動過程劃分幾段,每段設置一個平均速度,同時速度圍繞該平均速度小幅度隨機抖動,同樣無法完成驗證。

最後嘗試了完全模擬加速減速的過程通過了驗證,在前段滑塊需要做勻加速運動,後面需要做勻減速運動,在這裏利用物理學的加速度公式即可完成。

設滑塊滑動的加速度用 a 來表示,當前速度用 v 表示,初速度用 v0 表示,位移用 x 表示,所需時間用 t 表示,則它們之間滿足如下關係:


x  =  v0 *  t  +  0.5  *  a *  t *  t

v  =  v0  +  a *  t

接下來我們利用兩個公式可以構造一個軌跡移動算法,計算出先加速後減速的運動軌跡,代碼實現如下:


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,即減速的閾值,也就是加速到什麼位置就開始減速,在這裏定義爲 4/5,即模擬前 4/5 路程是加速過程,後 1/5 是減速過程。

隨後定義了當前位移的距離變量 current,初始爲 0,隨後進入 while 循環,循環的條件是當前位移小於總距離。在循環裏我們分段定義了加速度,其中加速過程加速度定義爲2,減速過程加速度定義爲 -3,隨後再套用位移公式計算出某個時間段內的位移,同時將當前位移更新並記錄到軌跡裏即可。

這樣直到運動軌跡達到總距離時即終止循環,最後得到的 track 即記錄了每個時間間隔移動了多少位移,這樣滑塊的運動軌跡就得到了。

最後我們只需要按照該運動軌跡拖動滑塊即可,方法實現如下:

def move_to_gap(self,  slider,  tracks):

    """

    拖動滑塊到缺口處

    :param slider: 滑塊

    :param tracks: 軌跡

    :return:

    """

    ActionChains(self.browser).click_and_hold(slider).perform()

    for  x  in  tracks:

        ActionChains(self.browser).move_by_offset(xoffset=x,  yoffset=0).perform()

    time.sleep(0.5)

    ActionChains(self.browser).release().perform()
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的聚集地,零基礎,進階,都歡迎

在這裏傳入的參數爲滑塊對象和運動軌跡,首先調用ActionChains 的 click_and_hold() 方法按住拖動底部滑塊,隨後遍歷運動軌跡獲取每小段位移距離,調用 move_by_offset() 方法移動此位移,最後移動完成之後調用 release() 方法鬆開鼠標即可。

這樣再經過測試,驗證就通過了,識別完成,效果圖 8-17 所示:

圖 8-17 識別成功結果

最後,我們只需要將表單完善,模擬點擊登錄按鈕即可完成登錄,成功登錄後即跳轉到後臺。

至此,極驗驗證碼的識別工作即全部完成,此識別方法同樣適用於其他使用極驗3.0的網站,原理都是相同的。

10. 結語

本節我們分析並實現了極驗驗證碼的識別,其關鍵在於識別的思路,如怎樣識別缺口位置,怎樣生成運動軌跡等,學會了這些思路後以後我們再遇到類似原理的驗證碼同樣可以完成識別過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章