新手向 Python 微信公众号开发(3)——订阅号回复图片等类型消息

浏览: 3990

毕业/失业的状态持续了两个月了,七月份以来每天沉迷屁股不能自拔,感觉自己已经是个废人了。今天突然发现之前的一篇专栏居然有2元的赞赏,花了五分钟寻找余额的入口又花了一分钟把一块九毛八揣进自己裤兜里,欣然获得了一小时网费以后,终于决定克服拖延症再花一小时写一篇公众号开发的记录,也算对得起知友的赞赏。(然而磨磨蹭蹭写了四个多小时)

老规矩,事先声明,技术水平很菜,文章也只是对微信官方文档的一点简单测试和个人 参考结合别处文章(后面会列参考链接) 给出的一种简单实现,纯属娱乐。另外,微信公众号开发真的不是多难的事情,一来官方接口丰富文档详尽,二来开发者众多,网络上随处能获得大量的教程乃至代码资源。之所以写这几篇文章,主要还是作为自己的学习笔记,理解微信的消息机制和开发流程;而且网上的资源大多是 PHP 或 Java 的,相比 Python 而言学习成本更高一些,所以还是一边尽力整合不多的已有的 Python 教程,一边自己测试,记录下来供大家参考。

提要:

0、明需求,读文档,划重点;

1、微信公众帐号开发者测试帐号初步体验高级接口;

2、没有认证的个人订阅号如何实现回复图片、语音等消息类型;

3、一些可能的简单玩法,抛砖引玉。

0

这篇文章所需要的基础在前两篇都写到了,其实也没什么太专业的要求,主要参考第一篇把平台搭起来,通过了token认证就行。

之前我们主要实现了:文字自动回复、接收用户的图片消息后调用微软 http://How-old.net 的人脸识别接口返回性别和年龄的结果、调用图灵机器人的 API 实现机器人聊天、接收用户的语音消息后通过提取微信自带的 Recognition 字段实现语音转文字并回复文字消息。‘’

可以发现,我们之前做的事情里,尽管分别处理了文字、图片、语音三种类型的消息,但是对用户的回复却都是文字消息。而在实际的应用中,我们可以在编辑模式和与关注者的私聊界面中是可以回复文字、图片、语音、视频和图文消息的(但是编辑模式和开发模式不能共存)。

于是我们考虑如何在开发者模式下回复其他类型的文字消息。查看官方文档中关于被动回复的说明。

被动回复用户消息

划重点:

现支持回复文本、图片、图文、语音、视频、音乐

请注意,回复图片等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

好的我们知道了,我们是可以回复图片等类型的消息的,然而需要使用素材管理接口。继续读文档。

公众号类型的接口权限说明

划重点:未认证订阅号号是<s>他喵的</s>没有素材管理的接口权限的。

好嘛,我就是<s>药丸</s>要玩怎么办,微信给开发者提供了测试帐号,读文档。

接口测试号申请

到此准备工作结束。

1

好了,站着别动。啊不,准备战斗。

微信公众平台 微信扫码登陆。

Clipboard Image.png

记下来 appID 和 appsecret ,后面要用来获取 access_token,下面的接口配置信息,可以按照我们在从零开始 Python 微信公众号开发中介绍的重新配一个,也可以直接把之前弄好现在用着的填过来,也是可以成功验证Token ,由此看出多个微信公众号其实是可以共用一个后台的。

然后我们根据文档进行图片的上传和获取已有素材列表。

新增永久素材

以上传图片为例(其他的类型我本着能偷懒就偷懒的原则,当作作业留给大家了)。上传图片需要 access_toke , type=image 和读取文件三个参数。

比较规范的写法为:<sup>1</sup>

def get_media_ID(path):
img_url='https://api.weixin.qq.com/cgi-bin/material/add_material'
payload_img={
'access_token':get_token(),
'type':'image'
}
data ={'media':open(path,'rb')}
r=requests.post(url=img_url,params=payload_img,files=data)
dict =r.json()
return dict['media_id']

其中 get_token()方法需要提供 appid 和 secret:<sup>1</sup>

def get_token():
payload_access_token={
'grant_type':'client_credential',
'appid':'xxxxxxxxxxxxx',
'secret':'xxxxxxxxxxxxx'
}
token_url='https://api.weixin.qq.com/cgi-bin/token'
r=requests.get(token_url,params=payload_access_token)
dict_result= (r.json())
return dict_result['access_token']

当然你也可以写的比较随意,因为上传素材这件事不一定要放到服务器上去,甚至自己在本地也可以做。

其中 access_token 可以直接在 微信公众平台接口调试工具 中获得。

Clipboard Image.png

Clipboard Image.png

可以看到 access_token 有效期为两小时。(每天请求次数上限 2000 次)

Clipboard Image.png

测试上传图片,可以看到返回了 media_id 和 url。

获取素材列表

import requests
import json
url="https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=yourtoken"

datas={
"type":"image",
"offset":0,
"count":20
}
data = json.dumps(datas)
a=requests.post(url=url,data =data)
print(a.text)

Clipboard Image.png

将返回的 json 数据包格式化以后:

Clipboard Image.png

可以看到列出了我们刚才上传的图片,给出了 media_id 和 url。

现在我们的素材库里已经有了图片素材,接下来考虑如何回复图片消息。

我们都应该知道,公众平台和用户的对话是用 XML 来交换信息的。也就是说,我们从用户那里接收到一个 XML 字串,解析后取得 FromUserName (用户ID,在回复消息时编程ToUserName)、MsgType 和相应的信息。同样的,我们在回复消息时也要把消息内容封装成XML 返回。

考虑我们之前已有的 templates/reply_text.xml :

$def with (toUser,fromUser,createTime,content)
<xml>
<ToUserName><![CDATA[$toUser]]></ToUserName>
<FromUserName><![CDATA[$fromUser]]></FromUserName>
<CreateTime>$createTime</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[$content]]></Content>
</xml>

现在需要回复图片信息,在目录下新建 reply_image.xml ,并参考文本消息的结构,写出相应的内容。唯一的不同之处就在于,文本消息的 content 变成了图片素材的 media_id 。

$def with (toUser,fromUser,createTime,media_id)
<xml>
<ToUserName><![CDATA[$toUser]]></ToUserName>
<FromUserName><![CDATA[$fromUser]]></FromUserName>
<CreateTime>$createTime</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[$media_id]]></MediaId>
</Image>
</xml>

现在我们有了图片素材,也有了回复图片消息的模版,直接修改 weixinInterface.py 添加相应的逻辑判断,回复图片消息即可。这里具体的代码就不贴了,因为牵扯了原来的一些逻辑,改的比较乱,所以最好还是自己新建一个后台,不要共用后台…其实也就相比回复文本也只用修改一行 return :

//return self.render.reply_text(fromUser,toUser,int(time.time()), msg)
return self.render.reply_image(fromUser,toUser,int(time.time()), media_id)

贴两张测试结果的图片。

Clipboard Image.png

Clipboard Image.png

由于用的同一后台,也顺手测试了原来的个人号,无法提供服务)

2

上面的测试号测试通过了,但是依然没有解决我们的核心需求,就是让自己未认证的没有素材管理权限的服务号被动回复图片信息。起初我是想着能不能在公众平台后台的素材管理里找到media_id 的字段的,结果找来找去找不到,偏偏发送消息的 XML 模版里是把 media_id 作为素材的唯一标识的,也只能另寻他法。(如果有办法能获取未认证订阅号素材管理中素材列表的media_id,请务必告知)

解决办法其实是相对简单的,因为发送信息是双向的,你向用户发送信息需要提供 media_id,用户给公众号发的消息里自然也带上了 media_id ,这一点在接收普通消息的官方文档里是可以看到的,只不过我们在之前就算对图片和语音消息做了一定的处理,也没有用到这个字段而已。现在我们知道,只要拿到了 media_id ,就等于在微信的素材服务器里拿到了资源,自然也就可以用它来作为消息返回了。

当然,这么做也有明显的缺陷,因为理论上讲…用户发送的消息是作为临时图片存储的,所以有效期只有三天…那又有什么办法呢…

这里我们做一个简单的测试就好,还是像我们最初做文本消息时的鹦鹉学舌一样,用户发送图片公众号原样回复图片,用户发送语音,公众号原样回复语音。

首先添加 templates/reply_voice.xml

$def with (toUser,fromUser,createTime,media_id)
<xml>
<ToUserName><![CDATA[$toUser]]></ToUserName>
<FromUserName><![CDATA[$fromUser]]></FromUserName>
<CreateTime>$createTime</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<Voice>
<MediaId><![CDATA[$media_id]]></MediaId>
</Voice>
</xml>

然后修改 weixinInterface.py ,当然写的比较简单,具体的代码可以自己参照前两篇完善。

if msgType == 'image':
media_id = xml.find('MediaId').text
return self.render.reply_image(fromUser,toUser,int(time.time()), media_id)
elif msgType == 'voice':
media_id = xml.find('MediaId').text
return self.render.reply_voice(fromUser,toUser,int(time.time()), media_id)

测试图片:

Clipboard Image.png

注:

①不同公众号间素材并不能串用,就好像我们刚才将图片上传到测试号以后拿到的 media_id 并不能用来在主号中回复;

②不同公众号的用户发送的图片也不能串用,这个测试一下就知道了,记录下来用户发送图片的 media_id ,然后用另一个号请求这张图片。如图。

Clipboard Image.png


Clipboard Image.png


③微信发送图片其实是支持 gif 动图的,但是显示的时候全部都是静态的,个人测试安卓上如果点全图下载到本地以后,用本地的图片查看器是会动的。但是要注意微信的自定义表情并不属于图片,而是一种不支持的格式。

④后续当然不要做这种原样返回的事情,可以自己多尝试一些点子,比如交换图片,交换语音什么的。今天一次性写太多太累了,懒得动脑子了。

思考题(我也不会):

语音是不能转发只能收藏的。但是我们刚才却抓到了用户语音的 media_id 并回复回去,理论上讲是可以转发给任何一个人的。由此有没有可能找到转发语音消息的办法?

3

至此大体上实现了我们要研究的内容和要实现的需求。只是还要探究一下拿这个东西做什么。

抛砖引玉吧,简单提两点,现在也写的有点乏了。

①斗图

前几天我一个朋友说他发现微软小冰的微信号能斗图了,发送表情的话能回复表情。当时我还挺惊讶的,因为微信公众号是不支持自定义表情的(QQ 表情和 emoji 可以作为文本处理,这个可以自行探究,也可以使用回复文本消息的办法回复 QQ 表情和 emoji ),如果接收到自定义表情的话微信后台显示的是 【收到不支持的消息类型,暂无法显示】。既然连接收都不支持,更何况回复呢。再说都识别不了对方究竟在发什么,又何来斗图一说呢。

实地探测后发现,微软小冰的回复全部都是图片格式,而且回复内容和发送的内容毫无关系。基本上认定在后台的识别中如果遇到【收到不支持的消息类型,暂无法显示】就任意回复一条静态的表情包图片,也可能会连续发送某一系列表情。如图。

Clipboard Image.png

这个自然也不难实现,只要自己积累一部分表情包的素材,然后添加判断【收到不支持的消息类型,暂无法显示】的逻辑后从 media_id 的列表中任意回复就好。这个感兴趣可以自己尝试。

②换图

因为前面提到,未认证的个人订阅号要通过获取用户发送素材的 media_id 来回复图片消息,而用户图片的有效期只有三天,这样一来很难自己长期维护一个良好可用的素材库。既然如此不如干脆就让用户们互相伤害。想法来自于前段时间看到和菜头做的图友 App ,用户交换图片。你发一张蓝天白云,我发一张花花草草,十分和谐。借鉴这个想法的话,我们直接作为平台,交换用户发来的图片即可。当然这种想法远非原创,很多人曾经做过,比如我在找参考资料时就在知乎看到了这个回答 微信公众号认证后高级接口如何玩耍? - 大强的回答 里面图片漂流的做法也就是我想表达的这个意思。

其他的就不再赘述了,好困。还是一口气熬夜写完,随后再完善吧。

参考资料(并未验证是否是第一手原创,仅供参考,无利益相关):

利用python 实现微信公众号群发图片与文本消息功能

C#开发微信门户及应用(26)-公众号微信素材管理

微信公众平台开发多的常见问题解惑

此外,Github 上有一些开发相对完善的项目,可以根据个人情况进行学习或使用:

Search · python weixin · GitHub

Search · python wechat · GitHub

写在最后:

这应该是这系列最后一篇了,因为未认证的个人订阅号做到这里基本也就到头了,作为一个学习者,了解到这个程度以后再在测试号上体验一下其余的高级接口也就差不多了。更多的内容要到有机会亲自参与认证订阅号或者服务号以后才能详细学习吧。而且这之后的门槛估计也不在某门语言或者微信官方的接口上面,而是更完善的 Web 开发能力和知识体系吧。

当然,应用级编程的门槛很大程度上是学习热情和学习能力。假如只谈入门、复制粘贴、做个测试(也就是我这种水平的),实在是没什么门槛的事情。我一直当作笔记来写,没想到还有许多人看,可能是我比较啰嗦,写出来的东西比较符合大家一贯的寻找答案的方式吧。当然,还望各位能借他山之石,做出更有意思的东西出来。


系列文章:

从零开始 Python 微信公众号开发

Python 微信公众号开发(2)——听得懂语音消息的聊天机器人

推荐 2
本文由 段小草 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

2 个评论

好给力的文章啊
这个很赞

要回复文章请先登录注册