python版 —— 驗證碼校正 打碼兔平台的使用介紹
1. 背景
驗證碼(CAPTCHA)的全稱是全自動區分電腦和人類的圖靈測試(Completely Automated Public Turing Test to tell Computers and Humans Apart),是一種用於區分人與電腦Bot的挑戰應答系統測試。CAPTCHA可通過設定一些人類很容易執行而Bot很難完成的任務來區分人類和Bot。
CAPTCHA經常被用來阻止Bot使用部落格來影響搜尋引擎排名、簽署電子郵箱帳戶發送垃圾郵件或參與網上投票等。
通常,CAPTCHA是一個輕微扭曲的字母數字字元影像檔,人通常可以很容易讀取映像中的字元。而Bot能夠識別該內容包含一個映像,但不知道是什麼映像。考慮到弱視群體,一些CAPTCHA使用音頻檔案。在這樣一個系統中,人可以聽到一個字母或短句並打出他所聽到的,從而證明他不是Bot。 一般來說,自動化處理驗證碼有兩種方式:
光學字元辨識(Optical Character Recognition, OCR),從映像中抽取文本。主要的庫有pytesser,tesseract。一般來說,對於複雜的驗證碼映像,需要先修改驗證碼映像,去除其中的背景雜音,只保留文本部分,再送入庫中進行解析。而且識別率不是很高,想提高識別率,需要通過長時間機器學習的訓練,代價較大。而且對於極其複雜的驗證碼,OCR甚至無法使用。 使用驗證碼處理服務(藉助於專業的打碼平台)。付費調用他們提供的API。當把驗證碼圖片傳給他們的API時,會有人進行人工查看,並在HTTP響應中給出解析後的常值內容。一般來說,整個解析過程在10s(打碼兔平台),最長60s,而且價位不高,1元 = 50~100個驗證碼(根據驗證碼類型進行收費)。如果驗證碼不多,為了提高識別效率和簡化操作,就可以選擇這種方式。 2. 環境 python 3.6.1 系統:win7 IDE:pycharm 安裝過chrome瀏覽器 配置好chromedriver selenium 3.7.0 3. 打碼兔平台的使用流程 3.1. 原理 將驗證碼圖片,打碼平台帳號,密碼等按照指定格式調用API(訪問URL),得到返回的結果。 打碼兔平台地址:http://www.dama2.com/ 打碼兔平台開發人員文檔:http://wiki.dama2.com/index.php 3.2. 流程
第一步:註冊一個開發人員帳號。
第二步:產生使用驗證碼的軟體的ID和key
登入開發人員帳號,進入 我的軟體,點擊建立軟體,填好資料提交就會產生一個軟體key的。
備忘: 把你的軟體key填進相應的代碼參數裡面,然後把軟體key發給打碼兔的線上客服,他會幫你釋放保留,你就可以用帳號test,密碼test測試了。 如圖所示,這就是軟體ID,軟體名稱,軟體KEY,都是程式會用到的參數。 當別人使用你的軟體ID和軟體Key進行驗證碼消費時,作為軟體的開發人員是能拿到分成的。這也是搶票軟體的賺錢的方式之一。也就是說,開發人員帳號用於軟體開發,賺錢。 軟體不需要公開。而且開發人員帳號不能進行付費打碼。
第三步:註冊一個普通使用者的帳號,並進行儲值,用這個帳號進行付費打碼。
也就是說,開發人員帳號用於賺錢,普通使用者帳號用於付費打碼 4. 自訂方法 原始文檔請參考官網:http://wiki.dama2.com/index.php?n=ApiDoc.Http
# 對打碼兔平台提供的代碼進行了一些改動,更加適應本軟體的需求import hashlibimport urllib.requestimport urllibimport jsonimport base64import requests# md5加密字串def md5str(str): m = hashlib.md5(str.encode(encoding="utf-8")) return m.hexdigest()# md5加密bytedef md5(byte): m = hashlib.md5(byte) return m.hexdigest()# 打碼兔API類class DamatuApi(): # 關聯的開發人員帳號 # 此帳號關聯的驗證碼類型,預設是1~8位隨機英文和數字組合 ID = '51773' # 開發人員帳號 軟體的AppID KEY = 'fbfb9022a1499b0c6436f223f98b714e' # 開發人員帳號 軟體的key, 我的軟體 產生的Key # 打碼平台host HOST = 'http://api.dama2.com:7766/app/' def __init__(self, username, password, limitCount): self.username = username self.password = password # 限制每個執行個體請求驗證碼的次數,防止意外,導致請求驗證碼過多,消費不可控 self.limitCount = limitCount # 用於統計驗證碼請求次數 self.count = 0 # 計算使用者簽名,按照一定的規則對 key和userName 進行加密 def getSign(self, param=b''): return (md5(bytes(self.KEY, encoding="utf8") + bytes(self.username, encoding="utf8") + param))[:8] # 獲得加密後的密鑰 key , userName, password def getPwd(self): return md5str(self.KEY + md5str(md5str(self.username) + md5str(self.password))) # 向打碼平台提交請求 def post(self, urlPath, formData = {}): ''' :param urlPath: 用於構造url,向不同的地址請求不同的資料 :param formData: 提交的資料 :return: ''' url = self.HOST + urlPath try: response = requests.request(method='post', url=url, data=formData, timeout=60 ) print(f"text = {response.text}") # 餘額,balance, text = {"ret":0,"balance":"9957","sign":"2428e5f9"} # 驗證碼,captcha, text = {"ret":0,"id":558899326,"result":"230876","sign":"8d55bdb1"} return response.text except Exception as e: print(f"postRequest error. exception = {e}, urlPath = {urlPath}, formData = {formData}") return {"ret": -1} # 查詢餘額 return 是正數為餘額, 如果為負數 則為錯誤碼 def getBalance(self): data = { 'appID': self.ID, 'user': self.username, 'pwd': self.getPwd(), 'sign': self.getSign() } res = self.post('d2Balance', data) jres = json.loads(res) if jres['ret'] == 0: return (True, jres['balance']) else: return (False, jres['ret']) # 上傳驗證碼圖片 def decode(self, filePath, type): ''' :param filePath: 驗證碼圖片路徑 如d:/1.jpg :param type: 驗證碼類型,查看http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc :return: 元組,result[0] = True為成功,False為錯誤碼 ''' if self.count >= self.limitCount: print(f"decode: 請求驗證碼數量超過限制自訂數量。") return (False, False) # 拿到驗證碼圖片的資料 f = open(filePath, 'rb') fdata = f.read() filedata = base64.b64encode(fdata) f.close() data = { 'appID': self.ID, 'user': self.username, 'pwd': self.getPwd(), 'type': type, 'fileDataBase64': filedata, 'sign': self.getSign(fdata) } res = self.post('d2File', data) jres = json.loads(res) self.count += 1 if jres['ret'] == 0: # 注意這個json裡面有ret,id,result,cookie,根據自己的需要擷取 return (True, jres['result']) else: return (False, jres['ret']) # url地址打碼,提供驗證碼連結 def decodeUrl(self, url, type): ''' :param url: 驗證碼地址 :param type: 驗證碼類型 http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc :return: 元組,result[0] = True為成功,False為錯誤碼 ''' if self.count >= self.limitCount: print(f"decodeUrl: 請求驗證碼數量超過限制自訂數量。") return (False, False) data = { 'appID': self.ID, 'user': self.username, 'pwd': self.getPwd(), 'type': type, 'url': urllib.parse.quote(url), 'sign': self.getSign(url.encode(encoding="utf-8")) } res = self.post('d2Url', data) jres = json.loads(res) self.count += 1 if jres['ret'] == 0: # 注意這個json裡面有ret,id,result,cookie,根據自己的需要擷取 return (True, jres['result']) else: return (False, jres['ret']) # 報錯,暫時先不關心。 參數id(string類型)由上傳打碼函數的結果獲得 return 0為成功 其他見錯誤碼 def reportError(self, id): data = { 'appID': self.ID, 'user': self.username, 'pwd': self.getPwd(), 'id': id, 'sign': self.getSign(id.encode(encoding="utf-8")) } res = self.post('d2ReportError', data) res = str(res, encoding="utf-8") jres = json.loads(res) return jres['ret']
5. 內嵌程式碼中使用
驗證碼截圖,請參考文章:http://blog.csdn.net/zwq912318834/article/details/78605486 第一步,確保餘額充足
# 打碼兔API類 執行個體化,參數是打碼兔使用者帳號和密碼. 最後一個是限制驗證碼的次數,防止出故障時,無限刷驗證碼,消費過大# dmt=DamatuApi("test","test")# 目前本軟體驗證碼定位為: 1~8位元字英文混搭, 題分21分dmt = DamatuApi("ancode", "ancode2017", 200)# 先查看餘額是否充足# BalanceRes = (True, '9931')balanceRes = dmt.getBalance() # 查詢餘額if balanceRes[0] == True and int(balanceRes[1]) > 0: # 餘額充足, 可以放心爬取 print(f"main: balanceRes = {balanceRes}") # 開始爬取資料 #...... 第二步,驗證碼校正。
檢測驗證碼是否出現。 對驗證碼進行截圖。 發送驗證碼圖片進行解碼。 向網站提交驗證碼解析結果。 檢測驗證碼是否校正成功。 注意:每一次頁面的變動,都可能讓之前擷取的元素失焦,需要重新擷取。
# 部分代碼# 通過Image處理映像,截取驗證碼圖片imgCaptcha.save('clawerImgs/captcha.png')# 將驗證碼送往打碼兔進行解碼# codeRes = (True, 'FMAE')# 56 代表驗證碼類型,1~8位元字英文組合# 參考:http://wiki.dama2.com/index.php?n=ApiDoc.PricedescdamatuRes = DamatuInstance.decode('clawerImgs/captcha.png', 56)while(damatuRes[0] == False): if damatuRes[1] == False: raise Exception(f"打碼兔超出自訂最大限制數,終止軟體. damatuRes = {damatuRes}") else: # 驗證碼請求失敗,查詢下餘額,看是否充足 balanceRes = DamatuInstance.getBalance() if balanceRes[0] == True and int(balanceRes[1]) > 0: damatuRes = DamatuInstance.decode('clawerImgs/captcha.png', 56) time.sleep(2) else: # 打碼兔 餘額不足, 拋出異常,終止軟體 raise Exception(f"打碼兔餘額不足,或者超出自訂最大限制數,終止軟體. balanceRes = {balanceRes}")# 點擊提交按鈕input = browser.find_element_by_xpath("//div[@class='input']/input[@id='J_CodeInput']")input.clear()input.send_keys(damatuRes[1])print(f"發送驗證碼為: damatuRes = {damatuRes}")time.sleep(3)# 提交submit = browser.find_element_by_xpath("//button[@id='J_submit']")submit.click()time.sleep(5)################################################# 處理完,提交完驗證碼之後,迴歸到首頁面,繼續抓取商品browser.switch_to.default_content()time.sleep(2)browser.refresh() # 處理完之後進行頁面重新整理time.sleep(5)# 然後重新檢測驗證碼是否存在,如果還在就迴圈重複處理,如果不在,下面這個流程也不會繼續captchaHandler(browser, DamatuInstance)