前言
前几天,有同学来问,想接入腾讯AI开放平台,怎么搞?然后就扔了个链接:ai.qq.com/doc/auth.sh…
打开一看,接口鉴权?签名算法?第一反应是,是不是给错链接了,接入个api还要那么麻烦?
后来仔细看了看,的确是腾讯AI开放平台的官网,真没打错;
本来是放到收费OCR那边的,但是想着这个边幅有点长,就单独弄一篇了,仅此而已;
既然如此,就那试试吧;
(非广告贴,非广告贴)
介绍
既然要接入别人,那就先了解下这个平台是干嘛的吧,进入官网;
第一时间就去看技术引擎,发现支持3大类: 自然语言处理、视觉识别、智能语音其中,情感分析是吸引jb眼光了,因为之前看到过类似的脚本,根据某人发的微博内容做情感分析,从而通知接收者要怎么处理,好玩,哈哈哈哈~
这种类似的平台,肯定是有收费跟免费的,对于普通使用者来说,免费就够了;
接入流程
获取app_id&app_key
既然要接入,那就挑一个通用识别吧,点击下,跳转;
会跳转到一个优图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个步骤。
- 将
请求参数对按key进行字典升序排序,得到有序的参数对列表N - 将列表N中的参数对按URL键值对的格式拼接成字符串,得到字符串T(如:key1=value1&key2=value2),URL键值拼接过程value部分需要URL编码,URL编码算法用大写字母,例如%E8,而不是小写%e8
- 将应用密钥以app_key为键名,组成URL键值拼接到字符串T末尾,得到字符串S(如:key1=value1&key2=value2&app_key=密钥)
- 对字符串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格式) | ... | 待识别图片 |
这里有几个需要实现的功能:
- 原始图片的base64编码数据
- 随机字符串
- 请求时间戳(秒级)
- 签名信息
其实,重点还是在第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个字符来表示任意二进制数据的方法。
用记事本打开exe
、jpg
、pdf
这些文件时,都会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符,
所以,如果要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。
Base64是一种最常见的二进制编码方法。
base64原理
Base64的原理很简单,首先,准备一个包含64个字符的数组:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
然后,对二进制数据进行处理,每3个字节一组,一共是3x8=24
bit,划为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)常用方法
随机字符串功能
所以,随机字符串的功能代码如下:
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个步骤。
- 将
请求参数对按key进行字典升序排序,得到有序的参数对列表N - 将列表N中的参数对按URL键值对的格式拼接成字符串,得到字符串T(如:key1=value1&key2=value2),URL键值拼接过程value部分需要URL编码,URL编码算法用大写字母,例如%E8,而不是小写%e8
- 将应用密钥以app_key为键名,组成URL键值拼接到字符串T末尾,得到字符串S(如:key1=value1&key2=value2&app_key=密钥)
- 对字符串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也不能处理,所以,还是找收费打码平台吧,完~
谢谢大家~