對於一個比較完備的微信自動回覆功能 有幾個需要注意的點需要提前說明
項目github:https://github.com/sph116/flask_WX
1.本篇實現的自動回覆功能,將會分離自動回覆的問題回答的配置信息。如下圖,可交給業務崗人員進行編輯。此模板文件以上轉至github
其中關鍵詞用來匹配用戶輸入的關鍵詞,以進入不同的回覆模塊。
不同模塊下,具有1級問題及2級問題,輸入一級問題序號返回一級問題下的二級問題列表,輸入二級問題序號,返回問題所匹配的答案。
2.每次用戶發起的請求,微信都會對後端重複發起三次請求。若第一次請求未來得及返回數據,又再次接到第二次請求,將會導致重複的返回信息。所以我們需要對於接收的請求進行去重,重複的請求我們將只返回一條數據.。
微信的請求,會帶上用戶的id及時間戳,我的實現方式是使用redis進行去重判斷。將用戶id+時間戳作爲key,每次接收請求若redis不存在此key則正常返回,若redis存在此key則返回空值。
3.我們根據序號來返回相關問題,但顯然不同關鍵詞模塊下都有相同的序號。所以我們需要判斷用戶上一次輸入,是在哪個關鍵詞模塊。這一部分的實現方式依然是使用redis,將用戶名作爲key值,用戶所在的關鍵詞模塊爲value,並設置3分鐘左右的失效時間。每次接收到請求,根據用戶id查找redis是否存在值嗎,若不存在。則返回數據讓用戶重新選擇輸入關鍵詞,若存在值。則進入相關的關鍵詞模塊。
相關源碼:
flask app.py 使用gevent進行協程封裝 提升併發量
from gevent import monkey; monkey.patch_all() # 猴子補丁打在最前面
import difflib
from gevent import pywsgi
from flask import Flask
from flask import request, jsonify
from wechatpy.utils import check_signature
from wechatpy.exceptions import InvalidSignatureException
from wechatpy.replies import TextReply, ArticlesReply
from wechatpy import parse_message
from Check_duplication import Redis
import pandas as pd
import re
app = Flask(__name__)
def Reply_text(msg, reply_text):
"""
用於回覆文本數據
:param msg:
:param reply_text:
:return:
"""
reply_data = TextReply(content=reply_text, message=msg)
reply_xml = reply_data.render()
return reply_xml
def Reply_Article(msg, reply_dic):
reply = ArticlesReply(message=msg)
reply.add_article(reply_dic)
return reply.render()
def extract_que(que_path):
"""
提取所有問題回覆數據
返回問題分級字典 和答案字典
:param que_path:
:return:
"""
que_df = pd.read_excel(que_path)
que_dict = {} # 序號對應問題字典
ans_dict = {} # 問題對應答案字典
for column in que_df:
que_dict[column] = {}
for que in list(que_df[column]):
if type(que) == str:
que = que.replace("\n", "")
if " " in que:
ques = que.split("答案:")[0]
ques = ques.replace(re.search("(\d+(\.\d+)?)", ques).group(), '').strip()
ans = que.split("答案:")[1]
ans_dict[ques] = ans.strip()
else:
que_index = que.split(".")[0]
if len(que_index) == 2:
que = que[3:]
else:
que = que[2:]
que_dict[column][que_index] = {}
# que_dict[column][que_index][que] = {j[2: 5]: j.split('\n')[0][5:] for j in list(que_df[column]) if " " in j and que_index+"." in j}
que_dict[column][que_index][que] = {}
ls = list(que_df[column])
# if que_index == "3":
# pass
for j in ls:
if type(j) == str:
if " " in j and que_index + "." in j:
j = j.strip()
if re.search("(\d+(\.\d+)?)", j).group().split('.')[0] != que_index:
pass
else:
if len(que_index) == 2:
que_dict[column][que_index][que][j[0: 4]] = j.split('\n')[0][4:]
else:
que_dict[column][que_index][que][j[0: 3]] = j.split('\n')[0][3:]
return que_dict, ans_dict
@app.route('/check_token', methods=['GET'])
def Check_token():
"""
用來驗證微信公衆號後臺鏈接
:return:
"""
rq_dict = request.args
if len(rq_dict) == 0:
return ""
signature = request.args.get('signature') # 提取請求參數
timestamp = request.args.get('timestamp')
nonce = request.args.get('nonce')
echostr = request.args.get('echostr')
try:
check_signature(token='jxgj8888', signature=signature, timestamp=timestamp, nonce=nonce) # 使用wechat庫驗證
except InvalidSignatureException as e:
return ''
else:
return echostr # 返回數據
@app.route('/check_token', methods=['POST'])
def Reply_user():
"""
用於自動回覆客服消息
:return:
"""
que_dict, ans_dict = extract_que('問題數據路徑') # 加載問題及回覆信息
req_key_word = que_dict.keys() # 所有可回覆的關鍵詞
wechat_send_data = request.data # 接收消息提醒 爲xml格式
msg = parse_message(wechat_send_data) # wechat模塊解析數據
FromUserName = msg.source # 消息的發送用戶
CreateTime = msg.create_time # 消息的創建時間
ToUserName = msg.target # 消息的目標用戶
duplication_flag = Redis.check_duplication("{}{}".format(FromUserName, CreateTime)) # 消息查重
if duplication_flag == 1:
pass
else:
print("推送重複")
return '' # 若重複 返回1
if msg.type == "event": # 爲事件消息
if msg.event == "subscribe": # 關注事件回覆
return Reply_text(msg, '====自動回覆=====\n 歡迎關注, 回覆 {} 即可獲取相關信息。').format("、".join(req_key_word))
elif msg.event == "unsubscribe": # 取關事件回覆
return Reply_text(msg, "====自動回覆=====\n 下次再見。")
elif msg.event == "click" and msg.key == "zhinengkefu":
return Reply_text(msg, '====自動回覆=====\n 回覆 {} 即可獲取相關信息。').format("、".join(req_key_word))
else:
return ''
elif msg.type == "text": # 爲文本消息
text_type = Redis.save_uesr_rec(FromUserName) # 查詢用戶上次五分鐘之內的瀏覽記錄
send_text = msg.content # 用戶發送的文本消息
if text_type != False: # 用戶具有上次瀏覽記錄
text_type = text_type.decode("utf-8")
if send_text in req_key_word: # 如果輸入爲關鍵詞
Redis.save_uesr_rec(user_id=FromUserName, type='save', rec=send_text) # 存儲本次瀏覽記錄
ques = que_dict[send_text]
reply_text = '====自動回覆=====\n{}\n 請回復問題前序號, 例"1"'.format(
''.join(["{}.{}\n".format(i, list(ques[i].keys())[0]) for i in ques]))
return Reply_text(msg, reply_text)
elif text_type in req_key_word: # 具有上次瀏覽記錄
if re.search("(\d+(\.\d+)?)", send_text): # 如果可以提取出數字
Redis.save_uesr_rec(user_id=FromUserName, type='save', rec=text_type) # 存儲本次瀏覽記錄
ques_index = re.search("(\d+(\.\d+)?)", send_text).group()
try:
if "." not in ques_index: # 詢問1級標題
ques = list(que_dict[text_type][str(send_text)].values())[0]
reply_text = '====自動回覆=====\n{}\n 請回復問題前序號, 例"1.1"'.format(
''.join(["{}{}\n".format(i, ques[i]) for i in ques]))
else: # 詢問二級標題
ques = list(que_dict[text_type][str(send_text.split('.')[0])].values())[0]
que_ans = ans_dict[ques[str(send_text)]]
return Reply_Article(msg, reply_dic)
reply_text = '====自動回覆=====\n{}\n'.format(que_ans)
except Exception as e:
print("失敗 輸入信息爲 {}-{} {}".format(send_text, text_type, e))
reply_text = '====自動回覆=====\n 我不懂您的意思, 請回復 {} 即可獲取相關信息。'.format("、".join(req_key_word))
else: # 無法提取出數字 首先進行模糊匹配 匹配失敗 返回 如下
pro_que = difflib.get_close_matches(send_text, ans_dict.keys(), 1, cutoff=0.6)
if pro_que == []:
reply_text = '====自動回覆=====\n 我不懂您的意思, 請回復 {} 即可獲取相關信息。'.format("、".join(req_key_word))
else:
que = pro_que[0]
que_ans = ans_dict[que]
reply_text = '====自動回覆=====\n 請問您要詢問的問題是否是?\n {} \n回覆:{}'.format(que, que_ans)
return Reply_text(msg, reply_text)
else: # 用戶不具有上次瀏覽記錄
if send_text in req_key_word: # 根據用戶回覆關鍵字 返回相關問題
Redis.save_uesr_rec(user_id=FromUserName, type='save', rec=send_text) # 存儲本次瀏覽記錄
ques = que_dict[send_text]
reply_text = '====自動回覆=====\n{}\n 請回復問題前序號, 例"1"'.format(
''.join(["{}.{}\n".format(i, list(ques[i].keys())[0]) for i in ques]))
else: # 若無關鍵字 首先進行模糊匹配 匹配失敗 返回建議信息
pro_que = difflib.get_close_matches(send_text, ans_dict.keys(), 1, cutoff=0.6)
if pro_que == []:
reply_text = '====自動回覆=====\n 我不懂您的意思, 請回復 {} 即可獲取相關信息。'.format("、".join(req_key_word))
else:
que = pro_que[0]
que_ans = ans_dict[que]
reply_text = '====自動回覆=====\n 請問您要詢問的問題是否是?\n {} \n回覆:{}'.format(que, que_ans)
return Reply_text(msg, reply_text)
if __name__ == '__main__':
app.debug = True # 1.0以後版本不通過本方法啓動調試模式
server = pywsgi.WSGIServer(('0.0.0.0', 80), app)
server.serve_forever()
# app.run(debug=True, processes=True)
redis操作模塊 Check_duplication.py
import redis
class Operation_Redis():
"""
redis 數據庫連接池 及增刪改查
"""
def __init__(self):
self.host = ""
self.psw = ""
self.port = 6379
self.db = 1
self.pool = redis.ConnectionPool(host=self.host, password=self.psw, port=self.port, db=self.db, max_connections=50)
def check_duplication(self, username_time):
"""
檢測鍵值是否存在於redis 若存在 若存在返回空字符串 不存在則插入
:param username_time:
:return:
"""
r = redis.Redis(connection_pool=self.pool, decode_responses=True)
flag = r.exists(username_time, '') # 判斷鍵值是否存在 hexists
if flag == 0:
r.set(username_time, '', ex=86400) # 存儲
return 1
elif flag == 1:
return ''
def save_uesr_rec(self, user_id, type='', rec=''):
"""
判斷用戶上一次停留位置
用戶查看問題後 將位置存儲mysql 超過五分鐘刪除
下次查看 若大於5分鐘 則返回重新瀏覽
小於 則進入上次停留位置
:param user_id:
:param rec:
:return:
"""
r = redis.Redis(connection_pool=self.pool, decode_responses=True)
flag = r.exists(user_id) # 判斷鍵是否存在 # hexists
if flag == 1:
rec = r.get(user_id)
r.delete(user_id)
if type != "":
r.set(user_id, rec, ex=300)
return rec
else:
if type != "":
r.set(user_id, rec, ex=300)
return False
Redis = Operation_Redis()
相關演示