Python3爬蟲關於識別檢驗滑動驗證碼的例項
上節我們瞭解了圖形驗證碼的識別,簡單的圖形驗證碼我們可以直接利用 Tesserocr 來識別,但是近幾年又出現了一些新型驗證碼,如滑動驗證碼,比較有代表性的就是極驗驗證碼,它需要拖動拼合滑塊才可以完成驗證,相對圖形驗證碼來說識別難度上升了幾個等級,本節來講解下極驗驗證碼的識別過程。
1. 本節目標
本節我們的目標是用程式來識別並通過極驗驗證碼的驗證,其步驟有分析識別思路、識別缺口位置、生成滑塊拖動路徑,最後模擬實現滑塊拼合通過驗證。
2. 準備工作
本次我們使用的 Python 庫是 Selenium,使用的瀏覽器為 Chrome,在此之前請確保已經正確安裝好了 Selenium 庫、Chrome瀏覽器並配置好了 ChromeDriver,相關流程可以參考第一章的說明。
3. 瞭解極驗驗證碼
極驗驗證碼其官網為:http://www.geetest.com/,它是一個專注於提供驗證安全的系統,主要驗證方式是拖動滑塊拼合影象,若影象完全拼合,則驗證成功,即可以成功提交表單,否則需要重新驗證,樣例如圖8-5 和 8-6 所示:
現在極驗驗證碼已經更新到了 3.0 版本,截至 2017 年 7 月全球已有十六萬家企業正在使用極驗,每天服務響應超過四億次,廣泛應用於直播視訊、金融服務、電子商務、遊戲娛樂、政府企業等各大型別網站,下面是鬥魚、魅族的登入頁面,可以看到其都對接了極驗驗證碼,如圖所示:
4. 極驗驗證碼的特點
這種驗證碼相較於圖形驗證碼來說識別難度更大,極驗驗證碼首先需要在前臺驗證通過,對於極驗 3.0,我們首先需要點選按鈕進行智慧驗證,如果驗證不通過,則會彈出滑動驗證的視窗,隨後需要拖動滑塊拼合影象進行驗證,驗證之後會生成三個加密引數,引數隨後通過表單提交到後臺,後臺還會進行一次驗證。
另外極驗還增加了機器學習的方法來識別拖動軌跡,官方網站的安全防護說明如下:
·三角防護之防模擬
惡意程式模仿人類行為軌跡對驗證碼進行識別。針對模擬,極驗擁有超過 4000 萬人機行為樣本的海量資料。利用機器學習和神經網路構建線上線下的多重靜態、動態防禦模型。識別模擬軌跡,界定人機邊界。
·三角防護之防偽造
惡意程式通過偽造裝置瀏覽器環境對驗證碼進行識別。針對偽造,極驗利用裝置基因技術。深度分析瀏覽器的實際效能來辨識偽造資訊。同時根據偽造事件不斷更新黑名單,大幅提高防偽造能力。
·三角防護之防暴力
惡意程式短時間內進行密集的攻擊,對驗證碼進行暴力識別
針對暴力,極驗擁有多種驗證形態,每一種驗證形態都有利用神經網路生成的海量相簿儲備,每一張圖片都是獨一無二的,且相簿不斷更新,極大程度提高了暴力識別的成本。
另外極驗的驗證相對於普通驗證方式更加方便,體驗更加友好,其官方網站說明如下:
·點選一下,驗證只需要 0.4 秒
極驗始終專注於去驗證化實踐,讓驗證環節不再打斷產品本身的互動流程,最終達到優化使用者體驗和提高使用者轉化率的效果。
·全平臺相容,適用各種互動場景
極驗相容所有主流瀏覽器甚至古老的IE6,也可以輕鬆應用在iOS和Android移動端平臺,滿足各種業務需求,保護網站資源不被濫用和盜取。
·面向未來,懂科技,更懂人性
極驗在保障安全同時不斷致力於提升使用者體驗,精雕細琢的驗證面板,流暢順滑的驗證動畫效果,讓驗證過程不再枯燥乏味。
因此,相較於一般驗證碼,極驗的驗證安全性和易用性有了非常大的提高。
5. 識別思路
但是對於應用了極驗驗證碼的網站,識別並不是沒有辦法的。如果我們直接模擬表單提交的話,加密引數的構造是個問題,引數構造有問題服務端就會校驗失敗,所以在這裡我們採用直接模擬瀏覽器動作的方式來完成驗證,在 Python 中我們就可以使用 Selenium 來通過完全模擬人的行為的方式來完成驗證,此驗證成本相對於直接去識別加密演算法容易不少。
首先我們找到一個帶有極驗驗證的網站,最合適的當然為極驗官方後臺了,連結為:https://account.geetest.com/login,首先可以看到在登入按鈕上方有一個極驗驗證按鈕,如圖所示:
此按鈕為智慧驗證按鈕,點選一下即可智慧驗證,一般來說如果是同一個 Session,一小段時間內第二次登入便會直接通過驗證,如果智慧識別不通過,則會彈出滑動驗證視窗,我們便需要拖動滑塊來拼合影象完成二步驗證,如圖 8-10 所示:
驗證成功後驗證按鈕便會變成如下狀態,如圖 8-11 所示:
接下來我們便可以進行表單提交了。
所以在這裡我們要識別驗證需要做的有三步:
·模擬點選驗證按鈕
·識別滑動缺口的位置
·模擬拖動滑塊
第一步操作是最簡單的,我們可以直接用 Selenium 模擬點選按鈕即可。
第二步操作識別缺口的位置比較關鍵,需要用到影象的相關處理方法,那缺口怎麼找呢?首先來觀察一下缺口的樣子,如圖 8-12 和 8-13 所示:
可以看到缺口的四周邊緣有明顯的斷裂邊緣,而且邊緣和邊緣周圍有明顯的區別,我們可以實現一個邊緣檢測演算法來找出缺口的位置。對於極驗來說,我們可以利用和原圖對比檢測的方式來識別缺口的位置,因為在沒有滑動滑塊之前,缺口其實是沒有呈現的,如圖所示:
所以我們可以同時獲取兩張圖片,設定一個對比閾值,然後遍歷兩張圖片找出相同位置畫素 RGB 差距超過此閾值的畫素點位置,那麼此位置就是缺口的位置。
第三步操作看似簡單,但是其中的坑比較多,極驗驗證碼增加了機器軌跡識別,勻速移動、隨機速度移動等方法都是不行的,只有完全模擬人的移動軌跡才可以通過驗證,而人的移動軌跡一般是先加速後減速的,這又涉及到物理學中加速度的相關問題,我們需要模擬這個過程才能成功。
有了基本的思路之後就讓我們用程式來實現一下它的識別過程吧。
6. 初始化
首先這次我們選定的連結為:https://account.geetest.com/login,也就是極驗的管理後臺登入頁面,在這裡我們首先初始化一些配置,如 Selenium 物件的初始化及一些引數的配置:
EMAIL='[email protected]' PASSWORD='123456' classCrackGeetest(): 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. 模擬點選
隨後我們需要實現第一步的操作,也就是模擬點選初始的驗證按鈕,所以我們定義一個方法來獲取這個按鈕,利用顯式等待的方法來實現:
defget_geetest_button(self): """ 獲取初始驗證按鈕 :return:按鈕物件 """ button=self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'geetest_radar_tip'))) returnbutton
獲取之後就會獲取一個 WebElement 物件,呼叫它的 click() 方法即可模擬點選,程式碼如下:
#點選驗證按鈕 button=self.get_geetest_button() button.click()
到這裡我們第一步的工作就完成了。
8. 識別缺口
接下來我們需要識別缺口的位置,首先我們需要將前後的兩張比對圖片獲取下來,然後比對二者的不一致的地方即為缺口。首先我們需要獲取不帶缺口的圖片,利用 Selenium 選取圖片元素,然後得到其所在位置和寬高,隨後獲取整個網頁的截圖,再從截圖中裁切出來即可,程式碼實現如下:
defget_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,right) defget_geetest_image(self,name='captcha.png'): """ 獲取驗證碼圖片 :return:圖片物件 """ top,right=self.get_position() print('驗證碼位置',top,right) screenshot=self.get_screenshot() captcha=screenshot.crop((left,right,bottom)) returncaptcha
在這裡 get_position() 函式首先獲取了圖片物件,然後獲取了它的位置和寬高,隨後返回了其左上角和右下角的座標。而 get_geetest_image() 方法則是獲取了網頁截圖,然後呼叫了 crop() 方法將圖片再裁切出來,返回的是 Image 物件。
隨後我們需要獲取第二張圖片,也就是帶缺口的圖片,要使得圖片出現缺口,我們只需要點選一下下方的滑塊即可,觸發這個動作之後,圖片中的缺口就會顯現,實現如下:
defget_slider(self): """ 獲取滑塊 :return:滑塊物件 """ slider=self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'geetest_slider_button'))) returnslider
利用 get_slider() 方法獲取滑塊物件,接下來呼叫其 click() 方法即可觸發點選,缺口圖片即可呈現:
#點按撥出缺口 slider=self.get_slider() slider.click()
隨後還是呼叫 get_geetest_image() 方法將第二張圖片獲取下來即可。
到現在我們就已經得到了兩張圖片物件了,分別賦值給變數 image1 和 image2,接下來對比圖片獲取缺口即可。要對比圖片的不同之處,我們在這裡遍歷圖片的每個座標點,獲取兩張圖片對應畫素點的 RGB 資料,然後判斷二者的 RGB 資料差異,如果差距超過在一定範圍內,那就代表兩個畫素相同,繼續比對下一個畫素點,如果差距超過一定範圍,則判斷畫素點不同,當前位置即為缺口位置,程式碼實現如下:
defis_pixel_equal(self,image1,image2,x,y): """ 判斷兩個畫素是否相同 :paramimage1:圖片1 :paramimage2:圖片2 :paramx:位置x :paramy:位置y :return:畫素是否相同 """ #取兩個圖片的畫素點 pixel1=image1.load()[x,y] pixel2=image2.load()[x,y] threshold=60 ifabs(pixel1[0]-pixel2[0])<thresholdandabs(pixel1[1]-pixel2[1])<thresholdandabs( pixel1[2]-pixel2[2])<threshold: returnTrue else: returnFalse defget_gap(self,image2): """ 獲取缺口偏移量 :paramimage1:不帶缺口圖片 :paramimage2:帶缺口圖片 :return: """ left=60 foriinrange(left,image1.size[0]): forjinrange(image1.size[1]): ifnotself.is_pixel_equal(image1,i,j): left=i returnleft returnleft
get_gap() 方法即為獲取缺口位置的方法,此方法的引數為兩張圖片,一張為帶缺口圖片,另一張為不帶缺口圖片,在這裡遍歷兩張圖片的每個畫素,然後利用 is_pixel_equal() 方法判斷兩張圖片同一位置的畫素是否相同,比對的時候比較了兩張圖 RGB 的絕對值是否均小於定義的閾值 threshold,如果均在閾值之內,則畫素點相同,繼續遍歷,否則遇到不相同的畫素點就是缺口的位置。
在這裡比如兩張對比圖片如下,如圖所示:
兩張圖片其實有兩處明顯不同的地方,一個就是待拼合的滑塊,一個就是缺口,但是滑塊的位置會出現在左邊位置,缺口會出現在與滑塊同一水平線的位置,所以缺口一般會在滑塊的右側,所以要尋找缺口的話,我們直接從滑塊右側尋找即可,所以在遍歷的時候我們直接設定了遍歷的起始橫座標為 60,也就是在滑塊的右側開始識別,這樣識別出的結果就是缺口的位置了。
到現在為止,我們就可以獲取缺口的位置了,剩下最後一步模擬拖動就可以完成驗證了。
9. 模擬拖動
模擬拖動的這個過程說複雜並不複雜,只是其中的坑比較多。現在我們已經獲取到了缺口的位置,接下來只需要呼叫拖動的相關函式將滑塊拖動到對應位置不就好了嗎?然而事實很殘酷,如果勻速拖動,極驗必然會識別出來這是程式的操作,因為人是無法做到完全勻速拖動的,極驗利用機器學習模型篩選出此類資料,歸類為機器操作,驗證碼識別失敗。
隨後我又嘗試了分段模擬,將拖動過程劃分幾段,每段設定一個平均速度,同時速度圍繞該平均速度小幅度隨機抖動,同樣無法完成驗證。
最後嘗試了完全模擬加速減速的過程通過了驗證,在前段滑塊需要做勻加速運動,後面需要做勻減速運動,在這裡利用物理學的加速度公式即可完成。
設滑塊滑動的加速度用 a 來表示,當前速度用 v 表示,初速度用 v0 表示,位移用 x 表示,所需時間用 t 表示,則它們之間滿足如下關係:
x=v0*t+0.5*a*t*t v=v0+a*t
接下來我們利用兩個公式可以構造一個軌跡移動演算法,計算出先加速後減速的運動軌跡,程式碼實現如下:
defget_track(self,distance): """ 根據偏移量獲取移動軌跡 :paramdistance:偏移量 :return:移動軌跡 """ #移動軌跡 track=[] #當前位移 current=0 #減速閾值 mid=distance*4/5 #計算間隔 t=0.2 #初速度 v=0 whilecurrent<distance: ifcurrent<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)) returntrack
在這裡我們定義了 get_track() 方法,傳入的引數為移動的總距離,返回的是運動軌跡,用 track 表示,它是一個列表,列表的每個元素代表每次移動多少距離。
首先定義了一個變數 mid,即減速的閾值,也就是加速到什麼位置就開始減速,在這裡定義為 4/5,即模擬前 4/5 路程是加速過程,後 1/5 是減速過程。
隨後定義了當前位移的距離變數 current,初始為 0,隨後進入 while 迴圈,迴圈的條件是當前位移小於總距離。在迴圈裡我們分段定義了加速度,其中加速過程加速度定義為2,減速過程加速度定義為 -3,隨後再套用位移公式計算出某個時間段內的位移,同時將當前位移更新並記錄到軌跡裡即可。
這樣直到運動軌跡達到總距離時即終止迴圈,最後得到的 track 即記錄了每個時間間隔移動了多少位移,這樣滑塊的運動軌跡就得到了。
最後我們只需要按照該運動軌跡拖動滑塊即可,方法實現如下:
defmove_to_gap(self,slider,tracks): """ 拖動滑塊到缺口處 :paramslider:滑塊 :paramtracks:軌跡 :return: """ ActionChains(self.browser).click_and_hold(slider).perform() forxintracks: ActionChains(self.browser).move_by_offset(xoffset=x,yoffset=0).perform() time.sleep(0.5) ActionChains(self.browser).release().perform()
在這裡傳入的引數為滑塊物件和運動軌跡,首先呼叫ActionChains 的 click_and_hold() 方法按住拖動底部滑塊,隨後遍歷運動軌跡獲取每小段位移距離,呼叫 move_by_offset() 方法移動此位移,最後移動完成之後呼叫 release() 方法鬆開滑鼠即可。
這樣再經過測試,驗證就通過了,識別完成,效果圖所示:
最後,我們只需要將表單完善,模擬點選登入按鈕即可完成登入,成功登入後即跳轉到後臺。
至此,極驗驗證碼的識別工作即全部完成,此識別方法同樣適用於其他使用極驗3.0的網站,原理都是相同的。
10. 本節程式碼
本節程式碼地址為:https://github.com/Python3WebSpider/CrackGeetest。
11. 結語
本節我們分析並實現了極驗驗證碼的識別,其關鍵在於識別的思路,如怎樣識別缺口位置,怎樣生成運動軌跡等,學會了這些思路後以後我們再遇到類似原理的驗證碼同樣可以完成識別過程。