近段時間 12306 訂票網站驗證碼升級爲用戶識別圖像內容,然後選取符合條件的圖片爲驗證碼,比如這樣:
不少媒體新聞大呼搶票工具集體失效、12306終極驗證碼等新聞,這種驗證碼的推出有好同樣也有壞處:機器識別困難,同樣人眼識別也輕鬆不到哪裏去。
用這種方式作爲驗證碼最大的擔憂就是怕腳本或人工對其圖片進行爬蟲遍歷,然後將所有的圖片保存後與關鍵字進行對比並關聯入庫,當然前提是這些圖片都是靜態的。
12306 驗證碼究竟是靜態還是動態,昨晚對這個疑問進行了實踐:12306 新版驗證碼 ,簡單的說測試後發現這整張圖片是在服務器後端動態生成的,所以不難理解爲什麼生成驗證碼頁面時會比較慢。
同樣上午我們又進行了第二個實踐,將整張驗證碼中的八張圖像拆分爲8張小圖然後進行感知hash處理,獲得樣本總數72225張,不重複的圖庫爲15478張,重複最高爲869次,繪製成圖如下:
既然不是靜態的圖像(對比過近10w條圖像hash),那我們就不浪費功夫爬取靜態圖片進行數據關聯入庫了,但我們仍然需要“破”掉這個驗證碼,沒有什麼理由。
最後,下文出現的所有的片段代碼將會開源,無需擔心。
關鍵字識別
驗證碼流程:
- 驗證碼提問
- 選擇答案(多選)
例如上面的驗證碼圖,他是一整張圖片,識別其關鍵字首先要對關鍵字區域進行圖像截取,隨後識別成文字。
這裏使用 Python 的 PIL 圖像處理庫來進行區域的選擇:
def imgCut():
pic_file = downloadImg()
pic_path = "./12306_pic/%s.jpg" % pic_file
pic_text_path = './12306_pic/%s_text.jpg' % pic_file
pic_obj = Image.open(pic_path)
box = (120,0,290,25)
region = pic_obj.crop(box)
region.save(pic_text_path)
print '[*] Picture Text Picture: {}'.format(pic_text_path)
return pic_path, pic_text_path
imgGut函數首先會下載這張驗證碼大圖(其中包括提示字、關鍵字、8張圖片等),然後保存至 ./12306_pic/ 目錄進行存儲,隨後使用 PIL 庫對圖像的 (120,0,290,25) 區域切割,也就是獲取關鍵字圖像區域。
現在我們已經能夠將驗證碼下載並切割出想要的關鍵字區域了,下面我們要識別關鍵字,然後轉換爲文本文字。
使用一些開源的光學字符識別模塊應該就能進行識別,但這不方便使用者運行,所以我選擇了一款在線網站OCR識別,他能夠對你上傳的圖像(我們剛剛切割好的圖像)進行文字識別轉換,當然準確率並沒有那麼高,一定得記住這一點!
這裏貼出部分代碼,功能實現(傳入圖像返回關鍵字的文本內容):
upload_pic_url = "http://cn.docs88.com/pdftowordupload2.php"
filename_tmp = filename.split('/')[-1]
pic_text_content = open(filename).read()
para = {'Filename': filename_tmp,
'sourcename': filename_tmp,
'sourcelanguage': 'cn',
'desttype': 'txt',
'Upload': 'Submit Query',}
upload_pic = requests.post(upload_pic_url, data=para, files={"Filedata" : open(filename, 'rb')})
text_result_url = 'http://cn.docs88.com/' + upload_pic.content[3:]
text_result = requests.get(text_result_url)
return text_result.content
我們運行試試效果:
[+] Download Picture: https://kyfw.12306.cn/otn/passcode...
[*] Picture Text Picture: ./12306_pic/1426580454_text.jpg
[*] Text: 襯 衫
[+] Download Picture: https://kyfw.12306.cn/otn/passcod...
[*] Picture Text Picture: ./12306_pic/1426580454_text.jpg
[*] Text: )帽子
[+] Download Picture: https://kyfw.12306.cn/otn/passcod...
[*] Picture Text Picture: ./12306_pic/1426580454_text.jpg
[*] Text: 春聯
效果還不錯,足夠我們測試使用,還記得他的準確率嗎?
巧妙的圖像識別
之前關於圖像識別我在 Buzz 發表過相關文章:使用CloudSight API進行圖像識別的Python腳本,這次我們不使用這個腳本,原因是雖然識別準確度較高但速度略慢,所以我並不是很鍾愛這一套,恰巧知乎上有位朋友寫了一篇利用百度識圖來進行圖像識別的文章及代碼,Google識圖當然也不錯,但剛好在這我們會用到,所以不必糾結。
- 分割驗證碼圖像
- 丟進百度識圖API函數
- 返回百度識圖結果
橫向兩行,每行四個,然後對其進行圖像識別並返回:
dict_list = {}
count = 0
for y in range(2):
for x in range(4):
count += 1
im2 = get_sub_img(pic_path, x, y)
result = baidu_stu_lookup(im2)
dict_list[count] = result
print (y,x), result
其中函數因文章長度原因暫不在這貼出,識別效果如下:
(0, 0) 冰雕|建築夜景
(0, 1) 炸暑條|快餐
(0, 2) 燈塔|高塔
(0, 3) 漢堡|麥當勞薯條|開店
(1, 0) 運動外套|防護服|運動服
(1, 1) 銀灰色|手機|移動版
(1, 2) 標書製作|規劃
(1, 3) 手機
好,現在我們能夠識別出關鍵字,也能識別出驗證碼8個圖像了,我們還需要機器幫助我們確認,究竟選擇哪幾個圖。
可能是它
前面兩次提到使用的 OCR 在線識別準確度並沒有那麼高,所以爲了方便程序能夠聰明的幫我們思考這道選擇題,我們進行結果僞分詞對比。
首先將關鍵字進行拆分,然後循環對比結果,這樣就能將未準確識別的文字忽略並識別相應識圖結果,這裏我將8個圖像結果按照1-8區分,第一行從左到右(1-4),第二行(5-8):
if captcha_text.strip() > 2:
print '\n[*] Maybe the result of the:'
maybe_result = []
for v in dict_list:
for c in range(len(unicode(captcha_text.strip(), 'utf8'))):
text = unicode(captcha_text, 'utf8')[c]
if text in dict_list[v]:
_str_res = '%s --- %s' % (v, dict_list[v])
maybe_result.append(_str_res)
for r in list(set(maybe_result)):
print r
else:
print '[-] False'
好了,這樣一來就算識別率沒有那麼高我們也能儘可能的將答案尋找出來了,看下效果:
未結束
結束了嗎? 其實沒有。
我們使用腳本進行了大量的測試,成功率可喜的足夠令一些邪惡的人做些事兒了,但驗證碼對抗一直在進行,當然也越來越有趣:)
文中完整代碼鏈接:https://gist.github.com/Evi1m0/fbbdb1ba7c66cc4e1bb2
———————————————
發自知乎專欄「微信公衆號:Evil-say」