突破漢字點選驗證碼,實現熊貓TV自動領竹子

前言

聲明:此文章僅用於學習交流,禁止用於任何商業目的,將不提供完整程序的源代碼。

經常在熊貓TV觀看的朋友可能知道,領竹子在PC端是需要一個滑動認證的,在一段時間以前熊貓採用的是GeeTest作為驗證碼提供方,在被宣布破解後採用了阿里雲的一套驗證碼滑動系統,根據用戶滑鼠滑動軌跡RTC信息等綜合來判斷是不是機器人,比較複雜。從PC端下手是比較困難了,於是我把目標轉向移動端:移動端是一張隨機生成的8選N(2<N<4)的驗證碼,需要依次點擊文字做驗證。

我們要做的大概有以下幾個步驟:

1.摸清驗證碼驗證走的網路請求流程

2.對圖片進行降噪處理。分離圖片中的問題和候選文字

3.對問題進行識別

4.對候選文字進行切割並識別

5.依次返回候選文字中點坐標


驗證流程

在Android手機上使用 Packet Capture輕鬆截取到驗證碼驗證過程:

驗證請求:

http://captcha.m.panda.tv/index.php?rid=*******n&sign=********n&__version=3.2.12.5855&__plat=androidn&__channel=yingyongbaon&pt_sign=********&pt_time=1514988491n

得到回復如下::

{"errno":0,"errmsg":"","data":{"captcha_key":"73347df61be6eea1660e766ef94317b7d4862ee3","img":"一段base64","num":"4"},"rn":1514988501092}n

提交驗證碼請求如下

/ajax_task_rewards_get?nmy_task_id=4n&appkey=pandaren_time_taskn&captcha_key=cc71f9********n&img_data=[368,313,151,29]n&__plat=android&__version=3.2.12.5855&__channel=yingyongbaon&pt_time=1514988491&pt_sign=********n

其中 captcha_key 對應獲取驗證碼時候帶的captcha_key參數,img_data參數為點擊的橫縱坐標。所以我們只要把圖像的base64轉化成bytes,通過一系列處理將坐標序列傳上去即可。

部分代碼:

r=requests.get("http://captcha.m.panda.tv/index.php?[params]")nj=r.json()["data"]["img"]nbimg=base64.b64decode(j)ncaptcha_key=r.json()["data"]["captcha_key"]n#f=open("temp.png","wb")n#f.write(bimg)n#f.close()n


圖片降噪

一開始使用二值化降噪但發現效果不夠理想,仔細觀察可以發現其實圖片背景就兩個,Photoshop扣之:

將圖片轉為矩陣,然後將圖片與這張圖進行矩陣相差運算:

def PurizeImg(im,threshold):n pixels=im.getpixel((1,1))n if(sum(pixels)>400):n bgim=Image.open("bg1.png")n else:n bgim =Image.open("bg2.png")n diff = ImageChops.difference(im,bgim)n diff = ImageOps.invert(diff)n #diff.show()n im= BinarizeImg(diff, 230)n #im.show()n Lim = im.convert(L)n table = []n for i in range(256):n if i < threshold:n table.append(0)n else:n table.append(1)n bim = Lim.point(table, 1)n return bimn

再二值化,已經將大部分背景去除了,Bingo~

返回原先的彩色圖,仔細觀察發現圖片中,題目字都是純黑的,其他地方很少有純黑的顏色,可以考慮閾值設置的很低進行二值化轉換導出問題圖片:

bim1=BinarizeImg(im,40)n

再做差,這樣就能分離出不帶問題的候選項了,可是效果沒那麼理想:

可以看到上方有明顯的噪點存在,將極大地影響我們後續對候選項的聚類操作,考慮將問題和候選項分離度很大,可以直接把問題裁掉,所以我們首要問題是識別問題位置和內容,採用tesserart-ocr進行識別,識別結果:

[{right: 743, left: 114, bottom: 87, top: 41, name: 請依次點擊糖聖凹陶}]n

然後再將問題裁掉:

draw.rectangle((rect["left"],rect["top"],rect["right"],rect["bottom"]),outline = "black")ndraw.rectangle((rect["left"],rect["top"],rect["right"],rect["bottom"]),fill = 0)n

嗯,已經很完美,可以開始進行下一步操作了!

候選項分割

正好想起了之前用來做步態分析的DBSCAN密度分析演算法,將點集輸入進去,嘗試調節參數之後效果很好:

img=np.array(bim3)narr=[]nfor x in range(1, bim3.width):n for y in range(1, bim3.height):n if (img[y,x] == True):n arr.append([x,y])nX = np.array(arr)ndb = DBSCAN(eps=13, min_samples=8).fit(X)ncore_samples_mask = np.zeros_like(db.labels_, dtype=bool)ncore_samples_mask[db.core_sample_indices_] = Truenlabels = db.labels_nn_clusters_ = len(set(labels)) - (1 if -1 in labels else 0)nprint(分類個數: %d % n_clusters_)n

跑完如果分類結果個數是8那就沒問題了。將不同點集保存後可以得到切割後的圖片類似這樣:

目前的問題就在於文字旋轉方向矯正和單個文字識別,在這裡提供幾個思路:

1.通過旋轉候選框實現任意方向的場景文本檢測

2.識字體網站

在矯正後通過識字體網站識別字體後收集熊貓TV涉及到的字體,然後利用PIL的DrawFont生成相應文字,再利用pHash進行漢明距離計算,每次取最近的一個對文字和圖片進行匹配。如果對於第一篇文章不熟悉可以嘗試預先對生成的文字進行旋轉後再匹配。

可是候選字怎麼獲得呢?

我們要利用其中流程的一個漏洞:熊貓TV程序員在驗證碼的一個步驟偷了懶,使用的是1500常用字列表並且沒有進行打亂,能夠直接分析出八個候選字是什麼,這就是生成字的關鍵:

def find(text):n f=open("word.txt",encoding="utf-8")n c=f.read()n return c[c.find(text[0]):c.find(text[0])+8]n


總結

取得對應文字的中點坐標後,調用/ajax_task_rewards_get,這樣就完成了整個識別驗證碼的步驟,識別率在60%左右,最多嘗試數次後就能夠自動領竹子。如果有更好更優的演算法歡迎大家在評論區交流~

推薦閱讀:

來編寫你的 setup 腳本(一)
基於Git的文件自動同步的思考和實現
Python在unpacking上的一個小陷阱

TAG:机器学习 | Python | 验证码识别 |