上節咱們瞭解了圖形驗證碼的識別,簡單的圖形驗證碼咱們能夠直接利用 Tesserocr 來識別,可是近幾年又出現了一些新型驗證碼,如滑動驗證碼,比較有表明性的就是極驗驗證碼,它須要拖動拼合滑塊才能夠完成驗證,相對圖形驗證碼來講識別難度上升了幾個等級,本節來說解下極驗驗證碼的識別過程。web
本節咱們的目標是用程序來識別並經過極驗驗證碼的驗證,其步驟有分析識別思路、識別缺口位置、生成滑塊拖動路徑,最後模擬實現滑塊拼合經過驗證。算法
本次咱們使用的 Python 庫是 Selenium,使用的瀏覽器爲 Chrome,在此以前請確保已經正確安裝好了 Selenium 庫、Chrome瀏覽器並配置好了 ChromeDriver,相關流程能夠參考第一章的說明。學習過程當中有不懂的能夠加入咱們的學習交流秋秋圈784中間758後面214,與你分享Python企業當下人才需求及怎麼從零基礎學習Python,和學習什麼內容。相關學習視頻資料、開發工具都有分享canvas
極驗驗證碼其官網爲:http://www.geetest.com/,它是一個專一於提供驗證安全的系統,主要驗證方式是拖動滑塊拼合圖像,若圖像徹底拼合,則驗證成功,便可以成功提交表單,不然須要從新驗證,樣例如圖8-5 和 8-6 所示:瀏覽器
圖 8-5 驗證碼示例安全
圖 8-6 驗證碼示例網絡
如今極驗驗證碼已經更新到了 3.0 版本,截至 2017 年 7 月全球已有十六萬家企業正在使用極驗,天天服務響應超過四億次,普遍應用於直播視頻、金融服務、電子商務、遊戲娛樂、政府企業等各大類型網站,下面是鬥魚、魅族的登陸頁面,能夠看到其都對接了極驗驗證碼,如圖 8-7 和 8-8 所示:app
圖 8-7 鬥魚登陸頁面機器學習
圖 8-8 魅族登陸頁面ide
這種驗證碼相較於圖形驗證碼來講識別難度更大,極驗驗證碼首先須要在前臺驗證經過,對於極驗 3.0,咱們首先須要點擊按鈕進行智能驗證,若是驗證不經過,則會彈出滑動驗證的窗口,隨後須要拖動滑塊拼合圖像進行驗證,驗證以後會生成三個加密參數,參數隨後經過表單提交到後臺,後臺還會進行一次驗證。函數
另外極驗還增長了機器學習的方法來識別拖動軌跡,官方網站的安全防禦說明以下:
惡意程序模仿人類行爲軌跡對驗證碼進行識別。針對模擬,極驗擁有超過 4000 萬人機行爲樣本的海量數據。利用機器學習和神經網絡構建線上線下的多重靜態、動態防護模型。識別模擬軌跡,界定人機邊界。
惡意程序經過僞造設備瀏覽器環境對驗證碼進行識別。針對僞造,極驗利用設備基因技術。深度分析瀏覽器的實際性能來辨識僞造信息。同時根據僞造事件不斷更新黑名單,大幅提升防僞造能力。
惡意程序短期內進行密集的***,對驗證碼進行暴力識別
針對暴力,極驗擁有多種驗證形態,每一種驗證形態都有利用神經網絡生成的海量圖庫儲備,每一張圖片都是獨一無二的,且圖庫不斷更新,極大程度提升了暴力識別的成本。
另外極驗的驗證相對於普通驗證方式更加方便,體驗更加友好,其官方網站說明以下:
極驗始終專一於去驗證化實踐,讓驗證環節再也不打斷產品自己的交互流程,最終達到優化用戶體驗和提升用戶轉化率的效果。
極驗兼容全部主流瀏覽器甚至古老的IE6,也能夠輕鬆應用在iOS和Android移動端平臺,知足各類業務需求,保護網站資源不被濫用和盜取。
極驗在保障安全同時不斷致力於提高用戶體驗,精雕細琢的驗證面板,流暢順滑的驗證動畫效果,讓驗證過程再也不枯燥乏味。
所以,相較於通常驗證碼,極驗的驗證安全性和易用性有了很是大的提升。
可是對於應用了極驗驗證碼的網站,識別並非沒有辦法的。若是咱們直接模擬表單提交的話,加密參數的構造是個問題,參數構造有問題服務端就會校驗失敗,因此在這裏咱們採用直接模擬瀏覽器動做的方式來完成驗證,在 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 差距超過此閾值的像素點位置,那麼此位置就是缺口的位置。
第三步操做看似簡單,可是其中的坑比較多,極驗驗證碼增長了機器軌跡識別,勻速移動、隨機速度移動等方法都是不行的,只有徹底模擬人的移動軌跡才能夠經過驗證,而人的移動軌跡通常是先加速後減速的,這又涉及到物理學中加速度的相關問題,咱們須要模擬這個過程才能成功。
有了基本的思路以後就讓咱們用程序來實現一下它的識別過程吧。
首先此次咱們選定的連接爲:https://account.geetest.com/login,也就是極驗的管理後臺登陸頁面,在這裏咱們首先初始化一些配置,如 Selenium 對象的初始化及一些參數的配置:
EMAIL = 'test@test.com' 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 就是登陸極驗須要的用戶名和密碼,若是沒有的話能夠先註冊一下。
隨後咱們須要實現第一步的操做,也就是模擬點擊初始的驗證按鈕,因此咱們定義一個方法來獲取這個按鈕,利用顯式等待的方法來實現:
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()
到這裏咱們第一步的工做就完成了。
接下來咱們須要識別缺口的位置,首先咱們須要將先後的兩張比對圖片獲取下來,而後比對兩者的不一致的地方即爲缺口。首先咱們須要獲取不帶缺口的圖片,利用 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,也就是在滑塊的右側開始識別,這樣識別出的結果就是缺口的位置了。
到如今爲止,咱們就能夠獲取缺口的位置了,剩下最後一步模擬拖動就能夠完成驗證了。
模擬拖動的這個過程說複雜並不複雜,只是其中的坑比較多。如今咱們已經獲取到了缺口的位置,接下來只須要調用拖動的相關函數將滑塊拖動到對應位置不就行了嗎?然而事實很殘酷,若是勻速拖動,極驗必然會識別出來這是程序的操做,由於人是沒法作到徹底勻速拖動的,極驗利用機器學習模型篩選出此類數據,歸類爲機器操做,驗證碼識別失敗。
隨後我又嘗試了分段模擬,將拖動過程劃分幾段,每段設置一個平均速度,同時速度圍繞該平均速度小幅度隨機抖動,一樣沒法完成驗證。
最後嘗試了徹底模擬加速減速的過程經過了驗證,在前段滑塊須要作勻加速運動,後面須要作勻減速運動,在這裏利用物理學的加速度公式便可完成。
設滑塊滑動的加速度用 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的網站,原理都是相同的。
本節咱們分析並實現了極驗驗證碼的識別,其關鍵在於識別的思路,如怎樣識別缺口位置,怎樣生成運動軌跡等,學會了這些思路後之後咱們再遇到相似原理的驗證碼一樣能夠完成識別過程。