難度: ★☆☆☆☆ 1星
一、目標
看到羣裏有人發了一個網址:
http://gcxm.hunanjs.gov.cn/dataservice.html
這是個查詢的頁面,打開是這個樣子的:
如果想查詢東西,單擊“搜索”按鈕的時候就會彈出一個滑塊驗證碼:
本次要破解的就是這個滑塊驗證碼。
二、分析
首先確定一下這個滑塊是如何提交的,打開開發者工具,切換到Network清空無關請求,然後故意拖動失敗一次,觀察到頁面驗證碼圖片刷新,同時捕捉到了四個請求:
先看第一個請求,這個請求的url是:
http://gcxm.hunanjs.gov.cn/AjaxHandler/PersonHandler.ashx?method=GetListPage&type=1&corptype_1=&corpname_1=&licensenum_1=&Province_1=430000&City_1=&county_1=&persontype=&persontype_2=&personname_2=&idcard_2=&certnum_2=&corpname_2=&prjname_3=&corpname_3=&prjtype_3=&cityname_3=&year_4=2020&jidu_4=3&corpname_4=&corpname_5=&corpcode_5=&legalman_5=&cityname_5=&SafeNum_6=&corpname_6=&corpname_7=&piciname_7=&corptype_7=&corpname_8=&corpcode_8=&legalman_8=&cityname_8=&pageSize=30&pageIndex=1&xypjcorptype=3&moveX=167&verifyid=87c6f516515346a69f4eec019b43faa7
這樣不是很好閱讀,還是看Chrome中解析的query string:
最上面的method表明這是個查詢列表的請求,然後是一些過濾條件忽略,最下面這兩個參數是和驗證碼相關的,經過幾次試驗後證明,moveX是我們拖動的距離,verifyid應該是表示本次驗證的id,是每次刷新驗證碼的時候從服務器返回的。
然後看這個接口的響應:
沒什麼好說的,驗證失敗...
然後繼續看剛剛捉到的第二個請求:
http://gcxm.hunanjs.gov.cn/AjaxHandler/PersonHandler.ashx?method=GetVerifyImg
明顯這是個獲取新的驗證碼的請求,看下它的響應:
是個數組類型,數組中有4個元素,數組中的前兩個元素看上去像是base64編碼的圖片,複製出來加上“data:image/png;base64,”前綴就能在chrome的地址欄中直接查看:
然後是第二張圖:
這兩張圖片和頁面上剛剛刷新的驗證碼是一致的:
然後是數組中的第3個元素,猜測這個應該就是上一個提交請求裏的verifyid,要驗證這點只需要再提交一次失敗的請求就可以了:
然後對比下這個提交請求裏的verifyid,發現是一樣的。
但是數組中的第四個元素是什麼呢,猜測也許是滑塊相對於背景圖的垂直位置,但是沒有證據,我們爲這個請求打一個xhr斷點:
http://gcxm.hunanjs.gov.cn/AjaxHandler/PersonHandler.ashx?method=GetVerifyImg
然後刷新頁面,看到進入了斷點,然後格式化代碼:
觀察調用棧,發現有個叫getVerifyImg的棧幀,點擊跳到那個棧幀,然後看到了接口success後的回調方法:
接下來就是閱讀一下相關的代碼,data是接口服務器響應的數據,然後調用了一個_this.createVerifyContent方法並把data傳了進入:
然後跟進入這個方法:
這個方法好長,都是拼接html的:
我們只關心data[3]是如何被使用的,只有一個地方使用到了data[3]:
這是將將其作爲一個id爲cutImg的元素的top屬性,切換到Element,搜索定位這個元素:
然後雙擊top: 24,進入編輯模式,鍵盤上下鍵調整發現頁面上的滑塊位置隨之上下移動,確定就是滑塊的垂直距離。
接下來就是如何獲取背景圖上缺口的位置,我們注意到背景圖上的缺口的顏色總是白色的,因此我們只需要掃描背景圖,檢測垂直位置上出現了連續n個白色像素即認爲是缺口所在的位置就可以了,不過這裏有個坑,我開始在實現的時候以爲背景圖是純白色的,因爲我的肉眼看上去那就是白僧僧的...然後發現程序的識別率不高,然後我下載了一張圖片拖到ps中看了一下:
這個看上去老實巴交的白色缺口實際上並不是純色的,還好我有編碼實踐的習慣不然看一眼就過可能就發現不了這個壞壞的白塊了,不是純白色也沒關係,至少是接近的,因此在判斷顏色的時候取個近似的cutoff就可以了,然後是缺口的高度,拿參考線量了一下發現是40px,爲了保險認爲垂直方向上連續出現30個近似白色的像素就認爲是發現了缺口的位置。
好了,問題分析的差不多了,接下來就是編碼實現。
三、編碼實現
#!/usr/bin/env python3 # encoding: utf-8 """ @author: CC11001100 """ import base64 import io import numpy import requests from PIL import Image session = requests.session() def crawl(): image_matrix, top, verify_id = get_verify_img() # 滑塊缺口的位置 lack_x = find_lack(image_matrix, top) print(lack_x) submit(verify_id, lack_x) def get_verify_img(): url = "http://gcxm.hunanjs.gov.cn/AjaxHandler/PersonHandler.ashx?method=GetVerifyImg" response = session.get(url) # 是這麼用的嗎?我很懵逼... session.cookies.update(response.cookies) response_json = response.json() # 滑塊背景圖片 image_base64 = response_json[1] image_bytes = base64.b64decode(image_base64) image = Image.open(io.BytesIO(image_bytes)) image.save("a.png") image_matrix = numpy.asarray(image) # 本次驗證的id verify_id = response_json[2] # 滑塊距離頂部的距離 top = int(response_json[3]) print(response_json) return image_matrix, top, verify_id def submit(verify_id, lack_x): url = "http://gcxm.hunanjs.gov.cn/AjaxHandler/PersonHandler.ashx" params = { "method": "GetListPage", "type": 1, "corptype_1": 2, "corpname_1": "", "licensenum_1": "", "Province_1": 430000, "City_1": "", "county_1": "", "persontype": "", "persontype_2": "", "personname_2": "", "idcard_2": "", "certnum_2": "", "corpname_2": "", "prjname_3": "", "corpname_3": "", "prjtype_3": "", "cityname_3": "", "year_4": 2020, "jidu_4": 3, "corpname_4": "", "corpname_5": "", "corpcode_5": "", "legalman_5": "", "cityname_5": "", "SafeNum_6": "", "corpname_6": "", "corpname_7": "", "piciname_7": "", "corptype_7": "", "corpname_8": "", "corpcode_8": "", "legalman_8": "", "cityname_8": "", "pageSize": 30, "pageIndex": 1, "xypjcorptype": 3, "moveX": lack_x, "verifyid": verify_id } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36", "X-Requested-With": "XMLHttpRequest", "Referer": "http://gcxm.hunanjs.gov.cn/dataservice.html" } r = session.get(url, params=params, headers=headers) print(r.text) def find_lack(image_matrix, top): """ 拿到缺塊所在的列的x座標 :param image_matrix: :param top: :return: """ for i in range(0, len(image_matrix[0])): count = 0 for j in range(top, len(image_matrix)): point = image_matrix[j][i] cutoff = 230 if point[0] >= cutoff and point[1] >= cutoff and point[2] >= cutoff: count += 1 if count >= 30: return i return -1 if __name__ == "__main__": crawl()
程序運行效果:
本文相關代碼內容託管至:
https://github.com/CC11001100/misc-crawler-public/tree/master/003-captcha/01-001-gcxm.hunanjs.gov.cn
請注意爬蟲文章具有時效性,本文寫於2020-11-14日。