您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Python微信公眾號(hào)開(kāi)發(fā)平臺(tái)的示例分析的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
上大學(xué)的時(shí)候,對(duì)微信公眾號(hào)開(kāi)發(fā)淺嘗輒止的玩了一下,感覺(jué)還是挺有意思的。
https://www.jb51.net/article/133677.htm后來(lái)服務(wù)器到期了,也就擱置了。由于發(fā)布web程序,使用PHP很順手,就使用了PHP作為開(kāi)發(fā)語(yǔ)言。但是其實(shí)微信公眾號(hào)的開(kāi)發(fā)和語(yǔ)言關(guān)聯(lián)并不大,流程,原理上都是一致的。
快要做畢設(shè)了,想著到時(shí)候應(yīng)該會(huì)部署一些代碼到服務(wù)器上,進(jìn)行長(zhǎng)期的系統(tǒng)構(gòu)建。所以趁著還是學(xué)生,就買(mǎi)了阿里云的學(xué)生機(jī)。買(mǎi)了之后,就想著玩點(diǎn)什么,于是微信公眾號(hào)的開(kāi)發(fā),就又提上了日程。但是這次,我不打算使用PHP了,感覺(jué)局限性相對(duì)于Python而言,稍微有點(diǎn)大。
使用Python的話,可以靈活的部署一些爬蟲(chóng)類(lèi)程序,和用戶(hù)交互起來(lái)也會(huì)比較方便。可拓展性感覺(jué)也比較的高,于是就選它了。
服務(wù)器配置這部分屬于是比較基礎(chǔ)的,不太明白的可以看看我之前的那個(gè)博客,還算是比較的詳細(xì)。今天就只是對(duì)核心代碼做下介紹好了。
項(xiàng)目目錄
root@aliyun:/var/www/html/wx/py# ls *.py api.py dispatcher.py robot.py root@aliyun:/var/www/html/wx/py#
api.py
這個(gè)文件相當(dāng)于是一個(gè)關(guān)卡,涉及token的驗(yàn)證,和服務(wù)的支持。
# -*- coding:utf-8 -*- #中文編碼 import sys reload(sys) # 不加這部分處理中文還是會(huì)出問(wèn)題 sys.setdefaultencoding('utf-8') import time from flask import Flask, request, make_response import hashlib import json import xml.etree.ElementTree as ET from dispatcher import * app = Flask(__name__) app.debug = True @app.route('/') # 默認(rèn)網(wǎng)址 def index(): return 'Index Page' @app.route('/wx', methods=['GET', 'POST']) def wechat_auth(): # 處理微信請(qǐng)求的處理函數(shù),get方法用于認(rèn)證,post方法取得微信轉(zhuǎn)發(fā)的數(shù)據(jù) if request.method == 'GET': token = '你自己設(shè)置好的token' data = request.args signature = data.get('signature', '') timestamp = data.get('timestamp', '') nonce = data.get('nonce', '') echostr = data.get('echostr', '') s = [timestamp, nonce, token] s.sort() s = ''.join(s) if (hashlib.sha1(s).hexdigest() == signature): return make_response(echostr) else: rec = request.stream.read() # 接收消息 dispatcher = MsgDispatcher(rec) data = dispatcher.dispatch() with open("./debug.log", "a") as file: file.write(data) file.close() response = make_response(data) response.content_type = 'application/xml' return response if __name__ == '__main__': app.run(host="0.0.0.0", port=80)
dispatcher.py
這個(gè)文件是整個(gè)服務(wù)的核心,用于識(shí)別用戶(hù)發(fā)來(lái)的消息類(lèi)型,然后交給不同的handler來(lái)處理,并將運(yùn)行的結(jié)果反饋給前臺(tái),發(fā)送給用戶(hù)。消息類(lèi)型這塊,在微信的開(kāi)發(fā)文檔上有詳細(xì)的介紹,因此這里就不再過(guò)多的贅述了。
#! /usr/bin python # coding: utf8 import sys reload(sys) sys.setdefaultencoding("utf8") import time import json import xml.etree.ElementTree as ET from robot import * class MsgParser(object): """ 用于解析從微信公眾平臺(tái)傳遞過(guò)來(lái)的參數(shù),并進(jìn)行解析 """ def __init__(self, data): self.data = data def parse(self): self.et = ET.fromstring(self.data) self.user = self.et.find("FromUserName").text self.master = self.et.find("ToUserName").text self.msgtype = self.et.find("MsgType").text # 純文字信息字段 self.content = self.et.find("Content").text if self.et.find("Content") is not None else "" # 語(yǔ)音信息字段 self.recognition = self.et.find("Recognition").text if self.et.find("Recognition") is not None else "" self.format = self.et.find("Format").text if self.et.find("Format") is not None else "" self.msgid = self.et.find("MsgId").text if self.et.find("MsgId") is not None else "" # 圖片 self.picurl = self.et.find("PicUrl").text if self.et.find("PicUrl") is not None else "" self.mediaid = self.et.find("MediaId").text if self.et.find("MediaId") is not None else "" # 事件 self.event = self.et.find("Event").text if self.et.find("Event") is not None else "" return self class MsgDispatcher(object): """ 根據(jù)消息的類(lèi)型,獲取不同的處理返回值 """ def __init__(self, data): parser = MsgParser(data).parse() self.msg = parser self.handler = MsgHandler(parser) def dispatch(self): self.result = "" # 統(tǒng)一的公眾號(hào)出口數(shù)據(jù) if self.msg.msgtype == "text": self.result = self.handler.textHandle() elif self.msg.msgtype == "voice": self.result = self.handler.voiceHandle() elif self.msg.msgtype == 'image': self.result = self.handler.imageHandle() elif self.msg.msgtype == 'video': self.result = self.handler.videoHandle() elif self.msg.msgtype == 'shortvideo': self.result = self.handler.shortVideoHandle() elif self.msg.msgtype == 'location': self.result = self.handler.locationHandle() elif self.msg.msgtype == 'link': self.result = self.handler.linkHandle() elif self.msg.msgtype == 'event': self.result = self.handler.eventHandle() return self.result class MsgHandler(object): """ 針對(duì)type不同,轉(zhuǎn)交給不同的處理函數(shù)。直接處理即可 """ def __init__(self, msg): self.msg = msg self.time = int(time.time()) def textHandle(self, user='', master='', time='', content=''): template = """ <xml> <ToUserName><![CDATA[{}]]></ToUserName> <FromUserName><![CDATA[{}]]></FromUserName> <CreateTime>{}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[{}]]></Content> </xml> """ # 對(duì)用戶(hù)發(fā)過(guò)來(lái)的數(shù)據(jù)進(jìn)行解析,并執(zhí)行不同的路徑 try: response = get_response_by_keyword(self.msg.content) if response['type'] == "image": result = self.imageHandle(self.msg.user, self.msg.master, self.time, response['content']) elif response['type'] == "music": data = response['content'] result = self.musicHandle(data['title'], data['description'], data['url'], data['hqurl']) elif response['type'] == "news": items = response['content'] result = self.newsHandle(items) # 這里還可以添加更多的拓展內(nèi)容 else: response = get_turing_response(self.msg.content) result = template.format(self.msg.user, self.msg.master, self.time, response) #with open("./debug.log", 'a') as f: # f.write(response['content'] + '~~' + result) # f.close() except Exception as e: with open("./debug.log", 'a') as f: f.write("text handler:"+str(e.message)) f.close() return result def musicHandle(self, title='', description='', url='', hqurl=''): template = """ <xml> <ToUserName><![CDATA[{}]]></ToUserName> <FromUserName><![CDATA[{}]]></FromUserName> <CreateTime>{}</CreateTime> <MsgType><![CDATA[music]]></MsgType> <Music> <Title><![CDATA[{}]]></Title> <Description><![CDATA[{}]]></Description> <MusicUrl><![CDATA[{}]]></MusicUrl> <HQMusicUrl><![CDATA[{}]]></HQMusicUrl> </Music> <FuncFlag>0</FuncFlag> </xml> """ response = template.format(self.msg.user, self.msg.master, self.time, title, description, url, hqurl) return response def voiceHandle(self): response = get_turing_response(self.msg.recognition) result = self.textHandle(self.msg.user, self.msg.master, self.time, response) return result def imageHandle(self, user='', master='', time='', mediaid=''): template = """ <xml> <ToUserName><![CDATA[{}]]></ToUserName> <FromUserName><![CDATA[{}]]></FromUserName> <CreateTime>{}</CreateTime> <MsgType><![CDATA[image]]></MsgType> <Image> <MediaId><![CDATA[{}]]></MediaId> </Image> </xml> """ if mediaid == '': response = self.msg.mediaid else: response = mediaid result = template.format(self.msg.user, self.msg.master, self.time, response) return result def videoHandle(self): return 'video' def shortVideoHandle(self): return 'shortvideo' def locationHandle(self): return 'location' def linkHandle(self): return 'link' def eventHandle(self): return 'event' def newsHandle(self, items): # 圖文消息這塊真的好多坑,尤其是<![CDATA[]]>中間不可以有空格,可怕極了 articlestr = """ <item> <Title><![CDATA[{}]]></Title> <Description><![CDATA[{}]]></Description> <PicUrl><![CDATA[{}]]></PicUrl> <Url><![CDATA[{}]]></Url> </item> """ itemstr = "" for item in items: itemstr += str(articlestr.format(item['title'], item['description'], item['picurl'], item['url'])) template = """ <xml> <ToUserName><![CDATA[{}]]></ToUserName> <FromUserName><![CDATA[{}]]></FromUserName> <CreateTime>{}</CreateTime> <MsgType><![CDATA[news]]></MsgType> <ArticleCount>{}</ArticleCount> <Articles>{}</Articles> </xml> """ result = template.format(self.msg.user, self.msg.master, self.time, len(items), itemstr) return result
robot.py
這個(gè)文件屬于那種畫(huà)龍點(diǎn)睛性質(zhì)的。
#!/usr/bin python #coding: utf8 import requests import json def get_turing_response(req=""): url = "http://www.tuling123.com/openapi/api" secretcode = "嘿嘿,這個(gè)就不說(shuō)啦" response = requests.post(url=url, json={"key": secretcode, "info": req, "userid": 12345678}) return json.loads(response.text)['text'] if response.status_code == 200 else "" def get_qingyunke_response(req=""): url = "http://api.qingyunke.com/api.php?key=free&appid=0&msg={}".format(req) response = requests.get(url=url) return json.loads(response.text)['content'] if response.status_code == 200 else "" # 簡(jiǎn)單做下。后面慢慢來(lái) def get_response_by_keyword(keyword): if '團(tuán)建' in keyword: result = {"type": "image", "content": "3s9Dh6rYdP9QruoJ_M6tIYDnxLLdsQNCMxkY0L2FMi6HhMlNPlkA1-50xaE_imL7"} elif 'music' in keyword or '音樂(lè)' in keyword: musicurl='http://204.11.1.34:9999/dl.stream.qqmusic.qq.com/C400001oO7TM2DE1OE.m4a?vkey=3DFC73D67AF14C36FD1128A7ABB7247D421A482EBEDA17DE43FF0F68420032B5A2D6818E364CB0BD4EAAD44E3E6DA00F5632859BEB687344&guid=5024663952&uin=1064319632&fromtag=66' result = {"type": "music", "content": {"title": "80000", "description":"有個(gè)男歌手姓巴,他的女朋友姓萬(wàn),于是這首歌叫80000", "url": musicurl, "hqurl": musicurl}} elif '關(guān)于' in keyword: items = [{"title": "關(guān)于我", "description":"喜歡瞎搞一些腳本", "picurl":"https://avatars1.githubusercontent.com/u/12973402?s=460&v=4", "url":"https://github.com/guoruibiao"}, {"title": "我的博客", "description":"收集到的,瞎寫(xiě)的一些博客", "picurl":"http://avatar.csdn.net/0/8/F/1_marksinoberg.jpg", "url":"http://blog.csdn.net/marksinoberg"}, {"title": "薛定諤的:dog:", "description": "副標(biāo)題有點(diǎn)奇怪,不知道要怎么設(shè)置比較好","picurl": "https://www.baidu.com/img/bd_logo1.png","url": "http://www.baidu.com"} ] result = {"type": "news", "content": items} else: result = {"type": "text", "content": "可以自由進(jìn)行拓展"} return result
其實(shí)這看起來(lái)是一個(gè)文件,其實(shí)可以拓展為很多的方面。
如果想通過(guò)公眾號(hào)來(lái)監(jiān)控服務(wù)器的運(yùn)行情況,就可以添加一個(gè)對(duì)服務(wù)器負(fù)載的監(jiān)控的腳本;
如果想做一些爬蟲(chóng),每天抓取一些高質(zhì)量的文章,然后通過(guò)公眾號(hào)進(jìn)行展示。
不方便使用電腦的情況下,讓公眾號(hào)調(diào)用一些命令也可以算是曲線救國(guó)的一種方式。
等等吧,其實(shí)有多少想法,就可以用Python進(jìn)行事先。然后通過(guò)公眾號(hào)這個(gè)平臺(tái)進(jìn)行展示。
易錯(cuò)點(diǎn)
在從PHP重構(gòu)為Python的過(guò)程中,我其實(shí)也是遇到了一些坑的。下面總結(jié)下,如果恰好能幫助到遇到同樣問(wèn)題的你,那我這篇文章也算是沒(méi)有白寫(xiě)了。
微信公眾號(hào)的開(kāi)發(fā),其實(shí)關(guān)鍵就在于理解這個(gè)工作的模式。大致有這么兩條路。
用戶(hù)把消息發(fā)送到微信公眾平臺(tái)上,平臺(tái)把信息拼接組裝成XML發(fā)到我們自己的服務(wù)器。(通過(guò)一系列的認(rèn)證,校驗(yàn),讓平臺(tái)知道,我們的服務(wù)是合法的),然后服務(wù)器將XML進(jìn)行解析,處理。
我們的服務(wù)器解析處理完成后,將數(shù)據(jù)再次拼接組裝成XML,發(fā)給微信公眾平臺(tái),平臺(tái)幫我們把數(shù)據(jù)反饋給對(duì)應(yīng)的用戶(hù)。
這樣,一個(gè)交互就算是完成了。在這個(gè)過(guò)程中,有下面幾個(gè)容易出錯(cuò)的地方。
token校驗(yàn): token的校驗(yàn)是一個(gè)get方式的請(qǐng)求。通過(guò)代碼我們也可以看到,就是對(duì)singature的校驗(yàn),具體看代碼就明白了。
XML數(shù)據(jù)的解析,對(duì)于不同的消息,記得使用不同的格式。其中很容易出錯(cuò)的就是格式不規(guī)范。 <!CDATA[[]]> 中括號(hào)之間最好不要有空格,不然定位起錯(cuò)誤還是很麻煩的。
服務(wù)的穩(wěn)定性。這里用的web框架是flask,小巧精良。但是對(duì)并發(fā)的支持性不是很好,對(duì)此可以使用uwsgi和Nginx來(lái)實(shí)現(xiàn)一個(gè)更穩(wěn)定的服務(wù)。如果就是打算自己玩一玩,通過(guò)命令行啟用(如python api.py)就不是很保險(xiǎn)了,因?yàn)楹苡锌赡軙?huì)因?yàn)橛脩?hù)的一個(gè)奇怪的輸入導(dǎo)致整個(gè)服務(wù)垮掉,建議使用nohup的方式,來(lái)在一定程度上保證服務(wù)的質(zhì)量。
結(jié)果演示
目前這個(gè)公眾號(hào)支持文字,語(yǔ)音,圖片,圖文等消息類(lèi)型。示例如下。
感謝各位的閱讀!關(guān)于“Python微信公眾號(hào)開(kāi)發(fā)平臺(tái)的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。