使用Python + Selenium破解滑塊驗證碼


在前面一篇博客《使用 Python + Selenium 打造瀏覽器爬蟲》中,我介紹了 Selenium 的基本用法和爬蟲開發過程中經常使用的一些小技巧,利用這些寫出一個瀏覽器爬蟲已經完全沒有問題了。看了前一篇博客,可能有人會有疑惑,瀏覽器爬蟲的優勢感覺並不比傳統爬蟲多多少啊,特別是通過遍歷頁面元素來獲取爬蟲數據的方式和傳統爬蟲解析 HTML 文檔結構的方式如出一轍。為了體現瀏覽器爬蟲的優越性,我特意准備了這篇博客,來看看如果要破解滑塊驗證碼,瀏覽器爬蟲比傳統爬蟲要容易多少。

  一、滑塊驗證碼簡述

  有爬蟲,自然就有反爬蟲,就像病毒和殺毒軟件一樣,有攻就有防,兩者彼此推進發展。反爬技術歷經多年,從最簡單的檢測 UserAgent 或者 Referrer 等頭部,到限制訪問頻率封 IP 等手段,到關鍵路徑的行為識別,到前端頁面的混淆和加密,到目前最流行的驗證碼技術,可以說,為了防止網絡上大量爬蟲的肆意妄為,特別是一些垃圾機器人,技術人員真的是絞盡腦汁。但是道高一尺魔高一丈,直到目前為止,也並沒有完全無懈可擊的反爬方案。

  目前最流行的反爬技術是驗證碼,幾乎所有網站的注冊頁面都會用到驗證碼技術,為了防止爬蟲自動注冊,批量生成垃圾賬號。驗證碼技術從一誕生,就是黑客們最感興趣的話題,驗證碼的英文為 CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart),翻譯成中文就是 全自動區分計算機和人類的公開圖靈測試,它是一種可以區分用戶是計算機還是人的測試,只要能通過 CAPTCHA 測試,該用戶就可以被認為是人類。使用計算機模擬人類的行為一直以來都是黑客們最熱衷的事情,也是黑客們夢寐以求的理想。所以驗證碼技術從一提出,就有大量的人嘗試破解,其實這些人並不是為了制造垃圾爬蟲,他們只是相信計算機可以和人一樣,阿西莫夫的機器人世界在未來是可能的。

  最初的驗證碼只是一張圖片,圖片上顯示扭曲變形的文字和數字,這樣的驗證碼通過圖像處理和識別的技術可以達到很高的識別率。后來驗證碼技術又在圖片上加入了各種干擾項,並且將字符粘連在一起,增加了字符切割和識別的難度,但是很快人們就想出了很多種不同的去噪方法,並使用骨架算法切割粘連字符,還有些人提出使用機器學習算法來切割字符。和圖片驗證碼類似的是語音驗證碼,不過這種驗證碼只是在表現形式上有所區別,實質上和圖片並沒有太大的變化,采用語音識別技術破解也不是難事。而且語音和圖片比起來缺乏交互,花樣要少很多,識別難度也要低一些,所以只有在給盲人或者對顏色分辨有障礙的人提供服務時才可能會使用語音驗證碼,一般情況下使用的比較少。在靜態的圖片驗證碼被破解之后,又出現了動態的圖片驗證碼,將字符動態的顯示在 gif 動畫上,不過這也沒什么用,通過圖像識別技術一樣可以破解,實在破解不了的,還可以通過網上一些廉價的打碼平台來人肉識別。

  打碼平台的誕生可以說是驗證碼領域的一件大事,它雖然不是什么高科技,只是把全世界廉價的勞動力匯集在了一起,就這樣,再復雜的驗證碼都不在話下。這雖然不是什么光榮的事,但是它推動了驗證碼技術的發展,交互式驗證碼被開發出來。傳統的圖片驗證碼采用一問一答的形式,只要答案正確,就認為驗證通過,它並不關心答案是怎么來的,所以出現了一些人工打碼平台,你提供一個問題,它們提供一個答案,僅此而已。如果不僅僅關注答案的正確性,還將提交答案的過程記錄下來,通過分析提交答案的過程,完全可以識別出這是不是一個人在操作,這就是交互式驗證碼的基本思路。這種驗證碼很難通過打碼平台來破解,因為你必須對着瀏覽器,使用鼠標對驗證碼進行一系列的交互操作。

  最耳熟能詳的交互驗證碼莫過於 12306 的了,這種驗證碼叫做 圖中點選 式驗證碼,同時提供多個圖像,讓用戶根據條件點擊選擇。也有些驗證碼是同時顯示 N 個變形的漢字讓你選,原理與 12306 的類似,但這種驗證碼以其極差的用戶體驗遭到很多人的唾棄,這也是大多數產品不願意選用的一個原因。滑塊驗證碼 比圖中點選體驗好很多,它只需要用戶使用鼠標將滑塊從某個位置拖動到另一個位置即可。程序通過記錄用戶拖動滑塊的軌跡,這一串的軌跡數據采用模式識別的手段就可以判斷出這是否是真人在操作。最簡單的滑塊驗證碼是用戶拖動滑塊從左拖到右即可,后來又出現了 拼圖式 的滑塊,滑塊作為圖的一部分,然后背景圖中有一個缺口剛好和滑塊相同形狀,需要用戶將滑塊拖到缺口中拼成一張完整的圖片。現在比較流行的滑塊驗證碼有 極驗 和 網易雲易盾,本篇博客以極驗的滑塊驗證碼為例,其他的滑塊驗證碼原理是類似的。

  最新的交互式驗證碼甚至只需要用戶點擊一個按鈕即可驗證,不需要做任何其他的操作,譬如 極驗的第三代行為驗證技術 和 易盾的智能無感知驗證碼。這種驗證碼的破解方式和滑塊驗證碼不一樣,我目前也沒有太多的了解,后面有時間再研究研究吧。

  最后不得不說的是,還有一種交互式驗證碼為短信或電話驗證碼,通過將驗證碼以短信的形式發送到你的手機,或者使用語音機器人自動打電話播報驗證碼,更有甚者,需要用戶自己編輯短信將驗證碼發送到某個號碼。對於這種驗證碼我認為並不能算作是 CAPTCHA,因為它利用的是用戶的有限資源(手機號)這個客觀限制,而並非是從技術角度來區分人和機器人的區別。如果某個人擁有大量的手機號(其實,黑產中確實也有專門養卡賣卡的),這種驗證手段就形同虛設了。

  二、破解思路

  目前,極驗正在推廣其第三代行為驗證技術,滑塊驗證碼貌似已經沒有前兩年那么流行了,不過仍然有很多網站還在使用滑塊驗證碼。譬如我這篇博客就以 春秋航空的會員注冊頁面 為例。

 

  好了,上面講了那么多,下面就開始我們的破解之旅吧。

  2.1 傳統爬蟲

  如果采用傳統爬蟲的方式來破解,首先我們需要測試下正常驗證的情況是什么樣的。在 Chrome 瀏覽器中按 F12 打開開發者工具,然后拖動滑塊到正確位置,可以觀察到 Network 面板發送的 Ajax 請求。

 

  可以看到這個請求的參數非常復雜,每個參數的含義也完全沒有頭緒,如果要破解這個驗證碼,則必須模擬發送這個請求,這個請求的每個參數都必須弄清楚,於是我們在代碼中搜索發送這個請求的地方。但事實上到這里我們就遇到了困難,ajax.php 這個請求根本就搜不到,甚至在瀏覽器中下 XHR 斷點也不行(很顯然它並不是一個 Ajax 請求),這是因為極驗的核心代碼經過了代碼混淆。

 

  這樣的代碼也就只有機器能讀懂,大多數人肯定是直接放棄了。不過網上也有大量的分析文章,如果你感興趣可以自己研究下,譬如 Windows應用開發 的知乎專欄上就有幾篇介紹 極驗驗證碼破解的系列文章,還有 FanhuaandLuomu 的 這篇破解文章 也寫得很好,推薦。

  我在這里跳過對混淆代碼的分析,總結下破解這樣的滑塊驗證碼的思路:

  1、捕獲所有關鍵請求;

  2、分析調試混淆的代碼,弄懂每個請求每個參數的含義,其中肯定會有一個參數,是拖動滑塊的軌跡;

  3、驗證碼圖片是打亂的,需要解析頁面上的樣式,並使用圖像處理方法還原出原始圖像;

  4、根據原始的圖像和滑塊位置得到缺口的偏移量;

  5、滑塊軌跡的模擬;

  6、如果參數有加密處理,還需要模擬它的加密過程;實在不行可以直接在代碼里模擬執行頁面上的 JS;

  7、...

  可見這里的工作量非常大,破解難度可想而知,而且混淆的代碼隨時可能會發布新的版本,一旦版本升級,參數都有可能發生變化,之前的所有分析工作都可能前功盡棄。

  除非實在是迫不得已,我並不推薦傳統的這種破解方法。首先這樣的破解方法太脆弱,不夠通用,隨時可能失效;其次這樣的破解工作費時費力,就算破解出來也得不到成就感和滿足感,對程序員的打擊太大,他可能再也不會玩第二次了(除非他是極客中的極客,就以破解混淆代碼為樂)。所以,還是讓我們來看看瀏覽器爬蟲如何。

  2.2 瀏覽器爬蟲

  由於瀏覽器爬蟲完全是以人為第一視角,你所看到的,就是瀏覽器爬蟲看到的,甚至,它能比你看到更多。我們可以大概的總結下瀏覽器爬蟲的破解思路:

  • 圖像識別,找到滑塊的位置和缺口的位置;

  • 模擬鼠標拖動,將滑塊拖到缺口位置;

  沒錯,就兩步。雖然其中會遇到一些坑,但真的就這兩步。使用上一篇博客中介紹的 Selenium 技巧,可以很快的寫下下面的代碼:

  chrome_options = webdriver.ChromeOptions()

  chrome_options.add_argument("--start-maximized")

  browser = webdriver.Chrome(

  executable_path="./drivers/chromedriver.exe",

  chrome_options=chrome_options

  )

  browser.get('https://account.ch.com/NonRegistrations-Regist')

  Wait(browser, 60).until(

  Expect.visibility_of_element_located((By.CSS_SELECTOR, "div[data-target='account-login']"))

  )

  email = browser.find_element_by_css_selector("div[data-target='account-login']")

  email.click()

  Wait(browser, 60).until(

  Expect.visibility_of_element_located((By.ID, "emailRegist"))

  )

  register = browser.find_element_by_id("emailRegist")

  register.click()

  offset = get_gap_offset(browser)

  drag_and_drop(browser, offset)

  關鍵就在於最后兩個方法 get_gap_offset() 和 drag_and_drop(),下面就來看下這兩個方法的實現。

  三、驗證碼圖片處理

  審查驗證碼圖片元素,可以看到下面這樣的 HTML 代碼:

  <div class="gt_cut_fullbg_slice" style="background-image: url('https://static.geetest.com/pictures/gt/3999642ae/3999642ae.webp'); background-position: -157px -58px;"></div>

  這樣的代碼一共有 52 行,每一個 div 都是 10px * 58px 的小塊。我們打開這個 background-image 對應的圖片可以看出這是一張亂序的圖片,這里的 background-position 用於顯示出正確的圖片。在代碼上面,可以發現和這里完全類似的代碼,background-position 都完全一樣,只是 background-image 不一樣,我們打開對應的圖片,也是亂序的,但和上一張圖片對比,可以猜測出,這是帶有缺口的背景圖片。

  <div class="gt_cut_bg_slice" style="background-image: url('https://static.geetest.com/pictures/gt/3999642ae/bg/fbdb18152.webp'); background-position: -157px -58px;"></div>

  一個很自然的想法就是把這兩張亂序的圖片根據 background-position 重組成兩張看得懂的圖片,然后對比兩張圖片,得到缺口的偏移量,然后將缺口偏移量減去滑塊偏移量,就可以得到要拖動的偏移量。如下圖所示:

 

  其中滑塊的偏移量可以通過下面的代碼得到(其中,left: 12px 就是滑塊的偏移量):

  <div class="gt_slice gt_show" style="left: 12px; background-image: url('https://static.geetest.com/pictures/gt/e6e7e0440/slice/fa2d5bbd8.png'); width: 55px; height: 55px; top: 20px;"></div>

  這里需要用到一點點圖像處理的知識,我們采用大名鼎鼎的 Pillow,Pillow 是 Python 里的圖像處理庫(PIL:Python Image Library),在開始之前,可以先看下它的官網教程,這里有一份中文文檔也可以參考。計算缺口偏移量的關鍵代碼如下:

  def get_slider_offset(image_url, image_url_bg, css):

  image_file = io.BytesIO(requests.get(image_url).content)

  im = Image.open(image_file)

  image_file_bg = io.BytesIO(requests.get(image_url_bg).content)

  im_bg = Image.open(image_file_bg)

  # 10*58 26/row => background image size = 260*116

  captcha = Image.new('RGB', (260, 116))

  captcha_bg = Image.new('RGB', (260, 116))

  for i, px in enumerate(css):

  offset = convert_css_to_offset(px)

  region = im.crop(offset)

  region_bg = im_bg.crop(offset)

  offset = convert_index_to_offset(i)

  captcha.paste(region, offset)

  captcha_bg.paste(region_bg, offset)

  diff = ImageChops.difference(captcha, captcha_bg)

  return get_slider_offset_from_diff_image(diff)

  代碼很好理解,就是根據 css 將兩張背景圖片重新排序生成兩張新圖片,然后通過 ImageChops.difference() 方法得到兩張圖片的差值圖像,最后通過差值圖像得到缺口的偏移量。其中有一點要注意的是,Pillow 的 Image.open() 方法只支持文件,不支持 URL,所以將圖片轉換為 BytesIO 對象,BytesIO 和 StringIO 一樣,是 Python 提供的在內存中操作 bytes 和 str 的類,並且和讀寫文件具有一致的接口。

  這種計算缺口位置的方法需要解析頁面源碼以及圖片的 CSS 樣式,其實還有一種更簡單的方法:在顯示驗證碼圖片時對瀏覽器進行截圖,這個時候的圖像是完整的背景圖像;然后再點擊滑塊,這個時候滑塊和缺口都會顯示出來,再對瀏覽器進行截圖;分析兩次的截圖也可以計算出拖動的偏移量。有興趣的同學可以一試。

......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM