JB的Python之旅-爬蟲篇-圖形驗證碼(4)-- 騰訊AI開放平臺

前言

前幾天,有同窗來問,想接入騰訊AI開放平臺,怎麼搞?而後就扔了個連接:ai.qq.com/doc/auth.sh…php

打開一看,接口鑑權?簽名算法?第一反應是,是否是給錯連接了,接入個api還要那麼麻煩?html

後來仔細看了看,的確是騰訊AI開放平臺的官網,真沒打錯;git

原本是放到收費OCR那邊的,可是想着這個邊幅有點長,就單獨弄一篇了,僅此而已;算法

既然如此,就那試試吧;api

(非廣告貼,非廣告貼)數組

介紹

既然要接入別人,那就先了解下這個平臺是幹嗎的吧,進入官網;app

第一時間就去看技術引擎,發現支持3大類: 天然語言處理、視覺識別、智能語音

其中,情感分析是吸引jb眼光了,由於以前看到過相似的腳本,根據某人發的微博內容作情感分析,從而通知接收者要怎麼處理,好玩,哈哈哈哈~dom

這種相似的平臺,確定是有收費跟免費的,對於普通使用者來講,免費就夠了;函數

接入流程

獲取app_id&app_key

既然要接入,那就挑一個通用識別吧,點擊下,跳轉;post

會跳轉到一個優圖OCR的界面,直接點擊免費試用,而後就登陸,註冊,輸入一大堆信息,這塊就不介紹了;

最後根據要求執行,等建立應用完畢,最後網頁會彈出兩個信息:

App_ID  XXX
App_Key  XXX
複製代碼

app_id請求的時候須要帶上,app_key在弄簽名的時候須要,這兩個玩意很重要,固然也別泄露了;

而後點擊通用OCR的查看文檔,就跳轉到這裏

協議須知

官網上介紹蠻全的,基本總結是這樣:

規則 描述
傳輸方式 HTTPS
請求方法 POST
字符編碼 統一採用UTF-8編碼
響應格式 統一採用JSON格式
接口鑑權 簽名機制,詳情請點擊接口受權

請求參數

參數名稱 是否必選 數據類型 數據約束 示例數據 描述
app_id int 正整數 1000001 應用標識(AppId)
time_stamp int 正整數 1493468759 請求時間戳(秒級)
nonce_str string 非空且長度上限32字節 fa577ce340859f9fe 隨機字符串
sign string 非空且長度固定32字節 B250148B284956EC5218D4B0503E7F8A 簽名信息,詳見接口鑑權
image string 原始圖片的base64編碼數據(原圖大小上限1MB,支持JPG、PNG、BMP格式) ... 待識別圖片

這麼看上,請求的時候,body要帶上app_id、time_stamp、nonce_str、sign、image這幾個參數,

其中,sign是最難搞的。。

接口受權

這個是個重點,想看看介紹吧,不着急。。

騰訊AI開放平臺HTTP API使用簽名機制對每一個接口請求進行權限校驗,對於校驗不經過的請求,API將拒絕處理,並返回鑑權失敗錯誤

接口調用者在調用API時必須帶上接口請求籤名其中籤名信息由接口請求參數和應用密鑰根據本文提供的簽名算法生成。

那這個簽名算法,怎麼算?下面是官網給的步驟,也能夠點擊這裏查看;

計算步驟

用於計算簽名的參數在不一樣接口之間會有差別,但算法過程固定以下4個步驟。

  1. 將<key, value>請求參數對按key進行字典升序排序,獲得有序的參數對列表N
  2. 將列表N中的參數對按URL鍵值對的格式拼接成字符串,獲得字符串T(如:key1=value1&key2=value2),URL鍵值拼接過程value部分須要URL編碼,URL編碼算法用大寫字母,例如%E8,而不是小寫%e8
  3. 將應用密鑰以app_key爲鍵名,組成URL鍵值拼接到字符串T末尾,獲得字符串S(如:key1=value1&key2=value2&app_key=密鑰)
  4. 對字符串S進行MD5運算,將獲得的MD5值全部字符轉換成大寫,獲得接口請求籤名

注意事項

  • 不一樣接口要求的參數對不同,計算簽名使用的參數對也不同;
  • 參數名區分大小寫,參數值爲空不參與簽名;
  • URL鍵值拼接過程value部分須要URL編碼
  • 簽名有效期5分鐘,須要請求接口時刻實時計算簽名信息;
  • 更多注意事項,請查看常見問題

而後官網下面就是一個php的示範,嗯,不懂Php的,跪下吧~

demo下載

固然,官方提供demo下載,Python用的是2.7版本(嗯,沒看錯),php是5.3.0版本及以上; 若是剛好用Python 3.X的,再次跪下吧。。最新一次更新時間是18年4月,都不弄一個Python 3.X的版本,大寫的服~

demo的處理邏輯也很簡單,就是獲取請求須要的參數,發請求,完;

通用OCR業務

其實整個過程很簡單,就是獲取圖片,按照要求的參數請求,而後獲取響應便可,那咱們就一步步來擼代碼吧;

看回上面的請求參數,要這幾個參數:app_id、time_stamp、nonce_str、sign、image

參數名稱 是否必選 數據類型 數據約束 示例數據 描述
app_id int 正整數 1000001 應用標識(AppId)
time_stamp int 正整數 1493468759 請求時間戳(秒級)
nonce_str string 非空且長度上限32字節 fa577ce340859f9fe 隨機字符串
sign string 非空且長度固定32字節 B250148B284956EC5218D4B0503E7F8A 簽名信息,詳見接口鑑權
image string 原始圖片的base64編碼數據(原圖大小上限1MB,支持JPG、PNG、BMP格式) ... 待識別圖片

這裏有幾個須要實現的功能:

  1. 原始圖片的base64編碼數據
  2. 隨機字符串
  3. 請求時間戳(秒級)
  4. 簽名信息

其實,重點仍是在第4那裏,先同樣同樣來吧;

原始圖片的base64編碼數據

這個的話,沒什麼疑問,直接使用base64便可

def get_img_base64str(image):
    """
    原始圖片的base64編碼數據
    :param image:圖片路徑
    :return:圖片的base64數據
    """
    with open(image,"rb") as f:
        pic = f.read()
    pic_base64 = base64.b64encode(pic)
    return pic_base64
複製代碼

固然,jb也不懂什麼是base64,因而乎作了下筆記,會的同窗,直接跳過吧;

Base64是一種用64個字符來表示任意二進制數據的方法

用記事本打開exejpgpdf這些文件時,都會看到一大堆亂碼,由於二進制文件包含不少沒法顯示和打印的字符,
因此,若是要讓記事本這樣的文本處理軟件能處理二進制數據,就須要一個二進制到字符串的轉換方法。

Base64是一種最多見的二進制編碼方法。

base64原理

Base64的原理很簡單,首先,準備一個包含64個字符的數組:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']

而後,對二進制數據進行處理,每3個字節一組,一共是3x8=24bit,劃爲4組,每組正好6個bit:

這樣獲得4個數字做爲索引,而後查表,得到相應的4個字符,就是編碼後的字符串。

因此,Base64編碼會把3字節的二進制數據編碼爲4字節的文本數據,長度增長33%,
好處是編碼後的文本數據能夠在郵件正文、網頁等直接顯示。

若是要編碼的二進制數據不是3的倍數,最後會剩下1個或2個字節怎麼辦?Base64用\x00字節在末尾補足後,再在編碼的末尾加上1個或2個=號,表示補了多少字節,解碼的時候,會自動去掉。

Python內置的base64能夠直接進行base64的編解碼:

import base64
print(base64.b64encode(b'binary\x00string'))
print(base64.b64decode(b'YmluYXJ5AHN0cmluZw=='))

結果:
b'YmluYXJ5AHN0cmluZw=='
b'binary\x00string'
複製代碼

因爲標準的Base64編碼後可能出現字符+和/,在URL中就不能直接做爲參數,因此又有一種"url safe"的base64編碼,其實就是把字符+和/分別變成-和_:

import base64
print(base64.b64encode(b'i\xb7\x1d\xfb\xef\xff'))
print(base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff'))
print(base64.urlsafe_b64decode('abcd--__'))

運行的結果:
b'abcd++//'
b'abcd--__'
b'i\xb7\x1d\xfb\xef\xff'
複製代碼

小小結:

Base64是一種任意二進制到文本字符串的編碼方法,經常使用於在URL、Cookie、網頁中傳輸少許二進制數據。

隨機字符串

隨機字符串的話,random跟string是跑不掉的,百度下看看;

random方法

Python裏面生成隨機數的方法就是random,因此使用以前要記得import random;

random.random()用於生成

描述 方法
指定範圍內的隨機符點數 random.uniform(20, 10)
指定範圍內的整數 random.randint(12, 20)
從指定範圍內,按指定基數遞增的集合 random.randrange(0, 101, 2) 隨機選取0到100的偶數
隨機字符 random.choice('abcdefg&#%^*f')
多個字符中選取特定數量的字符 random.sample('abcdefghij',3)
多個字符中選取特定數量的字符組成新字符串 string.join(random.sample(['a','b','c','d','e','f','g','h','i','j'], 3)).replace(" ","")
隨機選取字符串 random.choice ( ['apple', 'pear', 'peach', 'orange', 'lemon'] )
洗牌/從新排序 items = [1, 2, 3, 4, 5, 6] random.shuffle(items)

順便說起到:
random() 方法返回隨機生成的一個實數,它在[0,1)範圍內。

import random

print(random.random())
複製代碼

string方法

string是Python內置的方法,這裏就來分兩部分介紹:
1)經常使用方法

2)字符串常量;

隨機字符串功能

因此,隨機字符串的功能代碼以下:

def get_random_str():
    """
    隨機字符串
    :return:
    rule就是小寫字母+數字0-9組成的字符串,而後用random.sample獲取16位
    """
    rule = string.ascii_lowercase + string.digits
    str = random.sample(rule, 16)
    return "".join(str)
複製代碼

請求時間戳

由於官網要求是請求時間戳(秒級),所以直接使用time處理便可,無需額外加工

def get_time_stamp():
    return str(int(time.time()))
複製代碼

簽名信息

這個簽名是最難的,再次貼一下官方的要求。。

用於計算簽名的參數在不一樣接口之間會有差別,但算法過程固定以下4個步驟。

  1. 將<key, value>請求參數對按key進行字典升序排序,獲得有序的參數對列表N
  2. 將列表N中的參數對按URL鍵值對的格式拼接成字符串,獲得字符串T(如:key1=value1&key2=value2),URL鍵值拼接過程value部分須要URL編碼,URL編碼算法用大寫字母,例如%E8,而不是小寫%e8
  3. 將應用密鑰以app_key爲鍵名,組成URL鍵值拼接到字符串T末尾,獲得字符串S(如:key1=value1&key2=value2&app_key=密鑰)
  4. 對字符串S進行MD5運算,將獲得的MD5值全部字符轉換成大寫,獲得接口請求籤名

請求數據準備

這裏要求將請求參數進行排序,那就先折騰一堆請求參數吧:

app_id="你的ID"
app_key="你的key"
file = '你的圖片路徑'

def ExecTecentAPI():
    Req_Dict = {}

    Req_Dict['app_id'] = app_id
    Req_Dict["image"] = get_img_base64str(file)
    Req_Dict['time_stamp'] = get_time_stamp()
    Req_Dict['nonce_str'] = get_random_str()
    #這樣的話,Req_Dict已經準備好了,那就能夠計算簽名了
    sign = gen_dict_md5(Req_Dict, app_key)
複製代碼

升序處理-sorted()

有請求數據了,那就進行排序吧。按照要求,升序處理;

這裏的話,採用sorted()函數,這裏來介紹下sorted()

描述
sorted() 函數對全部可迭代的對象進行排序操做

與sort的區別

sort是應用在list上的方法,sorted能夠對全部可迭代的對象進行排序的操做;
 list的sort方法返回值的是對已經存在的列表進行操做,無返回值,而內建函數sorted方法返回
 的是一個新的list,而不是在原來的基礎上進行的操做;
複製代碼

語法

sorted(iterable[, cmp[, key[, reverse]]])

  • iterable -- 可迭代對象。
  • cmp -- 比較的函數,這個具備兩個參數,參數的值都是從可迭代對象中取出,此函數必須遵照的規則爲,大於則返回1,小於則返回-1,等於則返回0。
  • key -- 主要是用來進行比較的元素,只有一個參數,具體的函數的參數就是取自於可迭代對象中,指定可迭代對象中的一個元素來進行排序。
  • reverse -- 排序規則,reverse = True 降序 , reverse = False 升序(默認)。

返回值
返回從新排序的列表

排序實現

代碼很簡單,這樣就能獲取到升序後的字符串:
sort_dict = sorted(req_dict.items(), key=lambda item: item[0], reverse=False)

req_dict.items() #以列表返回可遍歷的(鍵, 值) 元組數組。如[(key:value),(key:value)]的方式
reverse=False    #降序

key=lambda item: item[0] #利用key來排序,item入口,item[0]是函數體,意思就是用每一個item的第一個內容
來排序,好比('app_id', '2108258706'),('time_stamp', '1536312440')
複製代碼

URL鍵值拼接

按照官網描述,排序完,就先對參數拼接成字符串,而後編碼,再把app_key拼接到字符串的末尾,獲得新的字符串,可是嘛,爲了方便,咱們先添加app.key吧

#這個app_key就是你的app_key,本身定義
sort_dict.append(('app_key', app_key))
複製代碼

MD5加密的話,通常都用hashlib

sha = hashlib.md5()
複製代碼

而後就到了拼接URL鍵值對

rawtext = urlencode(sort_dict).encode()

urlencode能夠把key-value這樣的鍵值對轉換成咱們想要的格式,返回的是a=1&b=2這樣的字符串
複製代碼

而後就是獲取拼接後的URL md5

sha.update(rawtext)
複製代碼

而後把獲得的MD5值轉換成大寫

md5text = sha.hexdigest().upper()
此時,md5text就是請求籤名了

hash.digest()  返回摘要,做爲二進制數據字符串值
hash.hexdigest() 返回摘要,做爲十六進制數據字符串值
upper()  將字符串中的小寫字母轉爲大寫字母。
複製代碼

最後,在請求頭裏面新增sign:

req_dict['sign'] = md5text
複製代碼

這樣的話,所須要的請求參數都獲取到了,就發起請求吧

response = requests.post(url="https://api.ai.qq.com/fcgi-bin/ocr/ocr_generalocr",
data=Req_Dict,verify=False)
複製代碼

最後執行的結果:

源碼

import requests
import base64
import time
import random,string
import hashlib
from urllib.parse import urlencode

app_id="2108258706"
app_key="dIX8rxJFymoHipm7"
file = 'test.png'

def ExecTecentAPI():
    Req_Dict = {}

    Req_Dict['app_id'] = app_id
    Req_Dict["image"] = get_img_base64str(file)
    Req_Dict['time_stamp'] = get_time_stamp()
    Req_Dict['nonce_str'] = get_random_str()

    sign = gen_dict_md5(Req_Dict, app_key)
    response = requests.post(url="https://api.ai.qq.com/fcgi-bin/ocr/ocr_generalocr",data=Req_Dict,verify=False)
    print(response.text)

def gen_dict_md5(req_dict, app_key):
    try:
        # 方法,先對字典排序,排序以後,寫app_key,再urlencode
        sort_dict = sorted(req_dict.items(), key=lambda item: item[0], reverse=False)
        sort_dict.append(('app_key', app_key))
        sha = hashlib.md5()
        rawtext = urlencode(sort_dict).encode()
        sha.update(rawtext)
        md5text = sha.hexdigest().upper()
        # 字典能夠在函數中改寫
        if md5text:
            req_dict['sign'] = md5text
        return md5text
    except Exception as e:
        return None

def get_img_base64str(image):
    """
    原始圖片的base64編碼數據
    :param image:圖片路徑
    :return:圖片的base64數據
    """
    with open(image,"rb") as f:
        pic = f.read()
    pic_base64 = base64.b64encode(pic)
    return pic_base64

def get_time_stamp():
    """
    獲取請求時間戳(秒級)
    :return:
    """
    return str(int(time.time()))

def get_random_str():
    """
    隨機字符串
    :return:
    """
    rule = string.ascii_lowercase + string.digits
    str = random.sample(rule, 16)
    return "".join(str)

if __name__ == "__main__":
    # 通用ocr
    rest = ExecTecentAPI()
    print(rest)
複製代碼

對了,測試的二維碼是這個:

小結

嗯,騰訊的接入真的有點噁心的感受,識別率也沒有太特別的地方,文中也沒有什麼特別的地方,都是涉及到一些基礎知識,包括md5,sorted等功能;

最重要是,識別率並無明顯的增加,對於百度不能處理的驗證碼,騰訊AI也不能處理,因此,仍是找收費打碼平臺吧,完~

謝謝你們~

相關文章
相關標籤/搜索