Django开发微信公众号消息自动回复
通过python的中文分词jieba包和xml处理包xmltodict结合Django实现微信公众号的消息自动回复,用户通过微信公众号的对话框向后端发消息时,微信服务器将POST消息的XML数据包推送开发者填写的URL上。具体流程如下:
本文以接收和回复文本消息为主,微信公众号支持回复文本、图片、图文、语音、视频、音乐等类型的消息。根据微信官方的安全要求,需要用秘钥对收到的密文消息体进行解密,回复消息体也用此秘钥加密。
微信公众平台为开发者提供了5种语言的示例代码(包括C++、php、Java、Python和C#版本)下载地址如下:
https://wximg.gtimg.com/shake_tv/mpwiki/cryptoDemo.zip
首先,以粉丝给公众号发送文本消息:“欢迎进入公众号交流”,在开发者后台收到公众平台发送的xml如下:
<xml>
<ToUserName><![CDATA[公众号]]></ToUserName>
<FromUserName><![CDATA[粉丝号]]></FromUserName>
<CreateTime>1460537339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[欢迎进入公众号交流]]></Content>
<MsgId>6272960105994287618</MsgId>
</xml>
说明:
createTime 是微信公众平台记录粉丝发送该消息的具体时间
text: 用于标记该xml 是文本消息,一般用于区别判断
欢迎进入公众号交流: 说明该粉丝发给公众号的具体内容是欢迎进入公众号交流
MsgId: 是公众平台为记录识别该消息的一个标记数值, 微信后台系统自动产生
所以回复给用户的消息格式基本也如上,如想回复给粉丝一条文本消息,内容为“test”, 那么开发者发送给公众平台后台的xml 内容如下:
<xml>
<ToUserName><![CDATA[粉丝号]]></ToUserName>
<FromUserName><![CDATA[公众号]]></FromUserName>
<CreateTime>1460541339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[test]]></Content>
</xml>
说明:
1)ToUserName(接受者)、FromUserName(发送者)。
2)createtime用于标记开发者回复消息的时间。
3)text : 此次行为是发送文本消息 (当然可以是image/voice等类型)。
4)文本换行 ‘\n’。
关于微信公众号开发后端配置的可以去查看我的过往文章,有不清楚的可以留言交流。
回到本次的重点,Django来实现微信公众号自动回复消息
安装中文分词包jieba和xmltodict,jieba用于对接收的消息进行分词,然后判断关键词并进行回复,xmltodict主要是xml消息
pip install jieba xmltodict
直接上开发代码
# -*- coding: utf-8 -*-
from django.shortcuts import render
import hashlib
# Create your views here.
from django.http.response import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import xmltodict
import jieba
import time
@csrf_exempt
def wechat(request):
if request.method == 'GET':
signature = request.GET.get('signature')
timestamp = request.GET.get('timestamp', '')
nonce = request.GET.get('nonce', '')
echo_str = request.GET.get('echostr', '')
token='你的'
hashlist = [token, timestamp, nonce]
hashlist.sort()
list2 = ''.join(hashlist)
sha1 = hashlib.sha1()
sha1.update(list2.encode('utf-8'))
hashcode = sha1.hexdigest()
if hashcode == signature:
return HttpResponse(echo_str, content_type="text/plain")
else:
return HttpResponse('error', content_type="text/plain")
elif request.method == 'POST':
dict_xml=request.body.decode('utf-8')
xmlData = xmltodict.parse(dict_xml)
msg_type = xmlData['xml']['MsgType']
if msg_type == 'text':
contents = jieba.cut(xmlData['xml']['Content'])
for i in contents:
if i=="微信公众号开发" or i=="小程序开发" :
content = "请关注Django与python学习"
resp_data = {
"xml": {"ToUserName": xmlData['xml']['FromUserName'],
"FromUserName": xmlData['xml']['ToUserName'],
"CreateTime": int(time.time()),
"MsgType": 'text',
"Content": content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
else:
content = xmlData['xml']['Content']
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'news',
"ArticleCount": 1,
"Articles": {
"item": {
"Title": "搜索"+content+"的结果",
"Description": "和"+content+"相关的内容",
"PicUrl": "https://www.baidu.com/img/flexible/logo/pc/result.png",
"Url": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd="+content
}
},
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
elif msg_type == 'image':
content = "欢迎您"
resp_data = {
"xml": {"ToUserName": xmlData['xml']['FromUserName'],
"FromUserName": xmlData['xml']['ToUserName'],
"CreateTime": int(time.time()),
"MsgType": 'image',
"Content": content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
elif msg_type == 'voice':
content="欢迎您"
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'voice',
"Content":content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
elif msg_type == 'video':
content = "欢迎您"
resp_data = {
"xml": {"ToUserName": xmlData['xml']['FromUserName'],
"FromUserName": xmlData['xml']['ToUserName'],
"CreateTime": int(time.time()),
"MsgType": 'video',
"Content": content,
}
}
xmltodict.unparse(resp_data)
print(xmltodict.unparse(resp_data))
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
elif msg_type == 'music':
content = "欢迎您"
resp_data = {
"xml": {"ToUserName": xmlData['xml']['FromUserName'],
"FromUserName": xmlData['xml']['ToUserName'],
"CreateTime": int(time.time()),
"MsgType": 'music',
"Content": content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
elif msg_type == 'news':
resp_data = {
"xml": {"ToUserName": xmlData['xml']['FromUserName'],
"FromUserName": xmlData['xml']['ToUserName'],
"CreateTime": int(time.time()),
"MsgType": 'news',
"ArticleCount":1,
"Articles": {
"item":{
"Title":"Django之微信公众号开发接入",
"Description":"Django之微信公众号开发接入",
"PicUrl":"",
"Url":"https://mp.weixin.qq.com/s?__biz=MzAwNjE5MTE3Mw==&mid=2449814757&idx=1&sn=2f8a89d5bee110f4b598528303174480&chksm=8ce0b5cfbb973cd9c90b29d5a214e7489f88f0ea3bd1304c6fe503b727b7277c3c6809a1d39d&token=2126286068&lang=zh_CN#rd"
}
},
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
elif msg_type == 'event':
if xmlData['xml']['Event']=='subscribe':
content="谢谢您的关注"
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'text',
"Content":content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
if xmlData['xml']['Event']=='unsubscribe':
content="您取关后将接受不到我们的推送服务"
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'text',
"Content":content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
if xmlData['xml']['Event']=='LOCATION':
content="我们已收到你发送的定位"
openid = xmlData['xml']['FromUserName'] # 发送者的openid
Latitude=xmlData['xml']['Latitude']#地理位置纬度
Longitude=xmlData['xml']['Longitude']#地理位置经度
Precision=xmlData['xml']['Precision']#地理位置精度
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'text',
"Content":content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
if xmlData['xml']['Event']=='CLICK':
content="我们已收到你发送的定位"
openid=xmlData['xml']['FromUserName']#发送者的openid
EventKey=xmlData['xml']['EventKey']#事件 KEY 值,与自定义菜单接口中 KEY 值对应
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'text',
"Content":content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
if xmlData['xml']['Event']=='VIEW':
content="我们已收到你发送的定位"
openid=xmlData['xml']['FromUserName']#发送者的openid
EventKey=xmlData['xml']['EventKey']#事件 KEY 值,设置的跳转URL
resp_data={
"xml":{"ToUserName":xmlData['xml']['FromUserName'],
"FromUserName":xmlData['xml']['ToUserName'],
"CreateTime":int(time.time()),
"MsgType":'text',
"Content":content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
else:
content = "这是其他消息,不便回复"
resp_data = {
"xml": {"ToUserName": xmlData['xml']['FromUserName'],
"FromUserName": xmlData['xml']['ToUserName'],
"CreateTime": int(time.time()),
"MsgType": 'text',
"Content": content,
}
}
xmltodict.unparse(resp_data)
response = HttpResponse(xmltodict.unparse(resp_data), content_type="text/plain")
return response
完成上述代码,配置url既可实现自动回复。
消息回复的内容可以建立一个库或者加入ChatGPT也可实现微信公众号上对用户消息的判断回复。