今天要給大家介紹的是驗證碼的爬取和識别,不過隻涉及到最簡單的圖形驗證碼,也是現在比較常見的一種類型。
運作平台:Windows
Python版本:Python3.6
IDE: Sublime Text
其他:Chrome浏覽器
簡述流程:
步驟1:簡單介紹驗證碼
步驟2:爬取少量驗證碼圖檔
步驟3:介紹百度文字識别OCR
步驟4:識别爬取的驗證碼
步驟5:簡單圖像處理
目前,很多網站會采取各種各樣的措施來反爬蟲,驗證碼就是其中一種,比如當檢測到通路頻率過高時會彈出驗證碼讓你輸入,确認通路網站的不是機器人。但随着爬蟲技術的發展,驗證碼的花樣也越來越多,從最開始簡單的幾個數字或字母構成的圖形驗證碼(也就是我們今天要涉及的)發展到需要點選倒立文字字母的、與文字相符合的圖檔的點觸型驗證碼,需要滑動到合适位置的極驗滑動驗證碼,以及計算題驗證碼等等,總之花樣百出,讓人頭秃。驗證碼其他的相關知識大家可以看下這個網站:
captcha.org
再來簡單說下圖形驗證碼吧,就像這張: 
由字母和數字組成,再加上一些噪點,但為了防止被識别,簡單的圖形驗證碼現在也變得複雜,有的加了幹擾線,有的加噪點,有的加上背景,字型扭曲、粘連、镂空、混用等等,甚至有時候人眼都難以識别,隻能默默點選“看不清,再來一張”。
驗證碼難度的提高随之帶來的就是識别的成本也需要提高,在接下來的識别過程中,我會先直接使用百度文字識别OCR,來測試識别準确度,再确認是否選擇轉灰階、二值化以及去幹擾等圖像操作優化識别率。
接下來我們就來爬取少量驗證碼圖檔存入檔案。
首先打開Chrome浏覽器,通路剛剛介紹的網站,裡面有一個captcha圖像樣本連結:
https://captcha.com/captcha-examples.html?cst=corg
,網頁裡有60張不同類型的圖形驗證碼,足夠我們用來識别試驗了。
import requests
import os
import time
from lxml import etree
def get_Page(url,headers):
response = requests.get(url,headers=headers)
if response.status_code == 200:
# print(response.text)
return response.text
return None
def parse_Page(html,headers):
html_lxml = etree.HTML(html)
datas = html_lxml.xpath('.//div[@class="captcha_images_left"]|.//div[@class="captcha_images_right"]')
item= {}
# 建立儲存驗證碼檔案夾
file = 'D:/******'
if os.path.exists(file):
os.chdir(file)
else:
os.mkdir(file)
os.chdir(file)
for data in datas:
# 驗證碼名稱
name = data.xpath('.//h3')
# print(len(name))
# 驗證碼連結
src = data.xpath('.//div/img/@src')
# print(len(src))
count = 0
for i in range(len(name)):
# 驗證碼圖檔檔案名
filename = name[i].text + '.jpg'
img_url = 'https://captcha.com/' + src[i]
response = requests.get(img_url,headers=headers)
if response.status_code == 200:
image = response.content
with open(filename,'wb') as f:
f.write(image)
count += 1
print('儲存第{}張驗證碼成功'.format(count))
time.sleep(1)
def main():
url = 'https://captcha.com/captcha-examples.html?cst=corg'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36'}
html = get_Page(url,headers)
parse_Page(html,headers)
if __name__ == '__main__':
main()
仍然使用Xpath爬取,在右鍵檢查圖檔時可以發現,網頁分為兩欄,如下圖紅框所示,根據class分為左右兩欄,驗證碼分别位于兩欄中。
datas = html_lxml.xpath('.//div[@class="captcha_images_left"]|.//div[@class="captcha_images_right"]')
這裡我使用了Xpath中的路徑選擇,在路徑表達式中使用
“|”
表示選取若幹路徑,例如這裡表示的就是選取
class
為
"captcha_images_left"
或者
"captcha_images_right"
的區塊。再來看下運作結果:
由于每爬取一張驗證碼圖檔都強制等待了1秒,最後這個運作時間确實讓人絕望,看樣子還是需要多線程來加快速度的,關于多程序多線程我們下次再說,這裡我們先來看下爬取到的驗證碼圖檔。
圖檔到手了,接下來就是調用百度文字識别的OCR來識别這些圖檔了,在識别之前,先簡單介紹一下百度OCR的使用方法,因為很多識别驗證碼的教程用的都是tesserocr庫,是以一開始我也嘗試過,安裝過程中就遇到了很多坑,後來還是沒有繼續使用,而是選擇了百度OCR來識别。百度OCR接口提供了自然場景下圖檔文字檢測、定位、識别等功能。文字識别的結果可以用于翻譯、搜尋、驗證碼等代替使用者輸入的場景。另外還有其他視覺、語音技術方面的識别功能,大家可以直接閱讀文檔了解:百度OCR-API文檔
https://ai.baidu.com/docs#/OCR-API/top
使用百度OCR的話,首先注冊使用者,然後下載下傳安裝接口子產品,直接終端輸入
pip install baidu-aip
即可。然後建立文字識别應用,擷取相關
Appid
,
API Key
以及
Secret Key
,需要了解一下的是百度AI每日提供50000次免費調用通用文字識别接口的使用次數,足夠我們揮霍了。
然後就可以直接調用代碼了。
from aip import AipOcr
# 你的 APPID AK SK
APP_ID = '你的 APP_ID '
API_KEY = '你的API_KEY'
SECRET_KEY = '你的SECRET_KEY'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
# 讀取圖檔
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
return fp.read()
image = get_file_content('test.jpg')
# 調用通用文字識别, 圖檔參數為本地圖檔
result = client.basicGeneral(image)
# 定義參數變量
options = {
# 定義圖像方向
'detect_direction' : 'true',
# 識别語言類型,預設為'CHN_ENG'中英文混合
'language_type' : 'CHN_ENG',
}
# 調用通用文字識别接口
result = client.basicGeneral(image,options)
print(result)
for word in result['words_result']:
print(word['words'])
這裡我們識别的是這張圖
可以看一下識别結果
上面是識别後直接輸出的結果,下面是單獨提取出來的文字部分。可以看到,除了破折号沒有輸出外,文字部分都全部正确輸出了。這裡我們使用的圖檔是jpg格式,文字識别傳入的圖像支援jpg/png/bmp格式,但在技術文檔中有提到,使用jpg格式的圖檔上傳會提高一定準确率,這也是我們爬取驗證碼時使用jpg格式儲存的原因。
輸出結果中,各字段分别代表:
● log_id : 唯一的log id,用于定位問題
● direction : 圖像方向,傳入參數時定義為true表示檢測,0表示正向,1表示逆時針90度,2表示逆時針180度,3表示逆時針270度,-1表示未定義。
● words_result_num : 識别的結果數,即word_result的元素個數
● word_result : 定義和識别元素數組
● words : 識别出的字元串
還有一些非必選字段大家可以去文檔裡熟悉一下。
接下來,我們要做的,就是将我們之前爬取到的驗證碼用剛介紹的OCR來識别,看看究竟能不能得到正确結果。
from aip import AipOcr
import os
i = 0
j = 0
APP_ID = '你的 APP_ID '
API_KEY = '你的API_KEY'
SECRET_KEY = '你的SECRET_KEY'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
# 讀取圖檔
file_path = 'D:\******\驗證碼圖檔'
filenames = os.listdir(file_path)
# print(filenames)
for filename in filenames:
# 将路徑與檔案名結合起來就是每個檔案的完整路徑
info = os.path.join(file_path,filename)
with open(info, 'rb') as fp:
# 擷取檔案夾的路徑
image = fp.read()
# 調用通用文字識别, 圖檔參數為本地圖檔
result = client.basicGeneral(image)
# 定義參數變量
options = {
'detect_direction' : 'true',
'language_type' : 'CHN_ENG',
}
# 調用通用文字識别接口
result = client.basicGeneral(image,options)
# print(result)
if result['words_result_num'] == 0:
print(filename + ':' + '----')
i += 1
else:
for word in result['words_result']:
print(filename + ' : ' +word['words'])
j += 1
print('共識别驗證碼{}張'.format(i+j))
print('未識别出文本{}張'.format(i))
print('已識别出文本{}張'.format(j))
和識别圖檔一樣,這裡我們将檔案夾驗證碼圖檔裡的圖檔全部讀取出來,依次讓OCR識别,并依據
“word_result_num”
字段判斷是否成功識别出文本,識别出文本則列印結果,未識别出來的用
“----”
代替,并結合檔案名對應識别結果 。最後統計識别結果數量,再來看下識别結果。
看到結果,隻能說Amazing!60張圖檔居然識别出了65張,并且還有27張為未識别出文本的,這不是我想要的結果~先來簡單看下問題出在哪裡,看到
“Vertigo Captcha Image.jpg"
這張圖名出現了兩次,懷疑是在識别過程中由于被幹擾,是以識别成兩行文字輸出了,這樣就很好解釋為什麼多出來5張驗證碼圖檔了。可是!為什麼會有這麼多未識别出文本呢,而且英文數字組成的驗證碼識别成中文了,看樣子,不對驗證碼圖檔進行去幹擾處理,僅靠OCR來識别的想法果然還是行不通啊。那麼接下來我們便使用圖像處理的方法來重新識别驗證碼吧。
還是介紹驗證碼時用的這張圖
這張圖也沒能被識别出來,讓人頭秃。接下來就對這張圖檔進行一定處理,看能不能讓OCR正确識别
from PIL import Image
filepath = 'D:\******\驗證碼圖檔\AncientMosaic Captcha Image.jpg'
image = Image.open(filepath)
# 傳入'L'将圖檔轉化為灰階圖像
image = image.convert('L')
# 傳入'1'将圖檔進行二值化處理
image = image.convert('1')
image.show()
這樣子轉化後再來看下圖檔變成什麼樣了?
确實有些不同了,趕緊拿去試試能不能識别,還是失敗了~~繼續修改
from PIL import Image
filepath = 'D:\******\驗證碼圖檔\AncientMosaic Captcha Image.bmp'
image = Image.open(filepath)
# 傳入'L'将圖檔轉化為灰階圖像
image = image.convert('L')
# 傳入'l'将圖檔進行二值化處理,預設二值化門檻值為127
# 指定門檻值進行轉化
count= 170
table = []
for i in range(256):
if i < count:
table.append(0)
else:
table.append(1 )
image = image.point(table,'1')
image.show()
這裡我将圖檔儲存成了bmp模式,然後指定二值化的門檻值,不指定的話預設為127,我們需要先轉化原圖為灰階圖像,不能直接在原圖上轉化。然後将構成驗證碼的所需像素添加到一個table中,然後再使用point方法建構新的驗證碼圖檔。
現在已經識别到文字了,雖然我不知道為啥識别成了“珍”,分析之後發現是因為z我在設定參數設定了
“language_type”
“CHN_ENG”
,中英文混合模式,于是我修改成“ENG”英文類型,發現可以識别成字元了,但依然沒有識别成功,嘗試其他我所知道的方法後,我表示很無語,我決定繼續嘗試PIL庫的其他方法試試。
# 找到邊緣
image = image.filter(ImageFilter.FIND_EDGES)
# image.show()
# 邊緣增強
image = image.filter(ImageFilter.EDGE_ENHANCE)
image.show()
還是不能正确識别,我決定換個驗證碼試試。。。。。。
我找了這張帶有陰影的
from PIL import Image,ImageFilter
filepath = 'D:\******\驗證碼圖檔\CrossShadow2 Captcha Image.jpg'
image = Image.open(filepath)
# 傳入'L'将圖檔轉化為灰階圖像
image = image.convert('L')
# 傳入'l'将圖檔進行二值化處理,預設二值化門檻值為127
# 指定門檻值進行轉化
count= 230
table = []
for i in range(256):
if i < count:
table.append(1)
else:
table.append(0)
image = image.point(table,'1')
image.show()
簡單處理後,得到這樣的圖檔:
識别結果為:
識别成功了,老淚縱橫!!!看樣子百度OCR還是可以識别出驗證碼的,不過識别率還是有點低,需要對圖像進行一定處理,才能增加識别的準确率。不過百度OCR對規範文本的識别還是很準确的。
那麼與其他驗證碼相比,究竟是什麼讓這個驗證碼更容易被OCR讀懂呢?
● 字母沒有互相疊加在一起,在水準方向上也沒有彼此交叉。也就是說,可以在每一個字 母外面畫一個方框,而不會重疊在一起。
● 圖檔沒有背景色、線條或其他對 OCR 程式産生幹擾的噪點。
● 白色背景色與深色字母之間的對比度很高。
這樣的驗證碼相對識别起來較容易,另外,像識别圖檔時的白底黑字就屬于很标準的規範文本了,是以識别的準确度較高。至于更複雜的圖形驗證碼,就需要更深的圖像處理技術或者訓練好的OCR來完成了,如果隻是簡單識别一個驗證碼的話,不如人工檢視圖檔輸入,更多一點的話,也可以交給打碼平台來識别。
原文釋出時間為:2018-11-9
本文作者:HDMI
本文來自雲栖社群合作夥伴“
Python中文社群”,了解相關資訊可以關注“
”。