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也不能处理,因此,仍是找收费打码平台吧,完~

谢谢你们~

相关文章
相关标签/搜索