Python微信公衆號開發

演示1

大三上的時候,對微信公衆號開發淺嘗輒止的玩了一下,感受仍是挺有意思的。http://blog.csdn.net/marksinoberg/article/details/54235271 後來服務器到期了,也就擱置了。因爲發佈web程序,使用PHP很順手,就使用了PHP做爲開發語言。可是其實微信公衆號的開發和語言關聯並不大,流程,原理上都是一致的。php

快要作畢設了,想着到時候應該會部署一些代碼到服務器上,進行長期的系統構建。因此趁着仍是學生,就買了阿里雲的學生機。買了以後,就想着玩點什麼,因而微信公衆號的開發,就又提上了日程。可是此次,我不打算使用PHP了,感受侷限性相對於Python而言,稍微有點大。html

使用Python的話,能夠靈活的部署一些爬蟲類程序,和用戶交互起來也會比較方便。可拓展性感受也比較的高,因而就選它了。python

服務器配置這部分屬因而比較基礎的,不太明白的能夠看看我以前的那個博客,還算是比較的詳細。今天就只是對核心代碼作下介紹好了。git


項目目錄

root@aliyun:/var/www/html/wx/py# ls *.py
api.py  dispatcher.py  robot.py
root@aliyun:/var/www/html/wx/py# 
複製代碼

api.py

這個文件至關因而一個關卡,涉及token的驗證,和服務的支持。github

# -*- coding:utf-8 -*- #中文編碼
import sys

reload(sys)  # 不加這部分處理中文仍是會出問題
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('/')  # 默認網址
def index():
    return 'Index Page'


@app.route('/wx', methods=['GET', 'POST'])
def wechat_auth():  # 處理微信請求的處理函數,get方法用於認證,post方法取得微信轉發的數據
    if request.method == 'GET':
        token = '你本身設置好的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

這個文件是整個服務的核心,用於識別用戶發來的消息類型,而後交給不一樣的handler來處理,並將運行的結果反饋給前臺,發送給用戶。消息類型這塊,在微信的開發文檔上有詳細的介紹,所以這裏就再也不過多的贅述了。web

#! /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):
    """ 用於解析從微信公衆平臺傳遞過來的參數,並進行解析 """

    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 ""
        # 語音信息字段
        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):
    """ 根據消息的類型,獲取不一樣的處理返回值 """

    def __init__(self, data):
        parser = MsgParser(data).parse()
        self.msg = parser
        self.handler = MsgHandler(parser)

    def dispatch(self):
        self.result = ""  # 統一的公衆號出口數據
        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):
    """ 針對type不一樣,轉交給不一樣的處理函數。直接處理便可 """

    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> """
        # 對用戶發過來的數據進行解析,並執行不一樣的路徑
        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)
            # 這裏還能夠添加更多的拓展內容
            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

這個文件屬於那種畫龍點睛性質的。json

#!/usr/bin python
#coding: utf8
import requests
import json

def get_turing_response(req=""):
    url = "http://www.tuling123.com/openapi/api"
    secretcode = "嘿嘿,這個就不說啦"
    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 ""

# 簡單作下。後面慢慢來
def get_response_by_keyword(keyword):
    if '團建' in keyword:
        result = {"type": "image", "content": "3s9Dh5rYdP9QruoJ_M6tIYDnxLLdsQNCMxkY0L2FMi6HhMlNPlkA1-50xaE_imL7"}
    elif 'music' in keyword or '音樂' 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":"有個男歌手姓巴,他的女友姓萬,因而這首歌叫80000", "url": musicurl, "hqurl": musicurl}}
    elif '關於' in keyword:
        items = [{"title": "關於我", "description":"喜歡瞎搞一些腳本", "picurl":"https://avatars1.githubusercontent.com/u/12973402?s=460&v=4", "url":"https://github.com/guoruibiao"},
                 {"title": "個人博客", "description":"收集到的,瞎寫的一些博客", "picurl":"http://avatar.csdn.net/0/8/F/1_marksinoberg.jpg", "url":"http://blog.csdn.net/marksinoberg"},
                 {"title": "薛定諤的🐶", "description": "副標題有點奇怪,不知道要怎麼設置比較好","picurl": "https://www.baidu.com/img/bd_logo1.png","url": "http://www.baidu.com"}
                 ]
        result = {"type": "news", "content": items}
    else:
        result = {"type": "text", "content": "能夠自由進行拓展"}
    return result
複製代碼

其實這看起來是一個文件,其實能夠拓展爲不少的方面。flask

  • 若是想經過公衆號來監控服務器的運行狀況,就能夠添加一個對服務器負載的監控的腳本;api

  • 若是想作一些爬蟲,天天抓取一些高質量的文章,而後經過公衆號進行展現。bash

  • 不方便使用電腦的狀況下,讓公衆號調用一些命令也能夠算是曲線救國的一種方式。

等等吧,其實有多少想法,就能夠用Python進行事先。而後經過公衆號這個平臺進行展現。

易錯點

在從PHP重構爲Python的過程當中,我其實也是遇到了一些坑的。下面總結下,若是剛好能幫助到遇到一樣問題的你,那我這篇文章也算是沒有白寫了。

微信公衆號的開發,其實關鍵就在於理解這個工做的模式。大體有這麼兩條路。

  1. 用戶把消息發送到微信公衆平臺上,平臺把信息拼接組裝成XML發到咱們本身的服務器。(經過一系列的認證,校驗,讓平臺知道,咱們的服務是合法的),而後服務器將XML進行解析,處理。
  2. 咱們的服務器解析處理完成後,將數據再次拼接組裝成XML,發給微信公衆平臺,平臺幫咱們把數據反饋給對應的用戶。

這樣,一個交互就算是完成了。在這個過程當中,有下面幾個容易出錯的地方。

  • token校驗: token的校驗是一個get方式的請求。經過代碼咱們也能夠看到,就是對singature的校驗,具體看代碼就明白了。

  • XML數據的解析,對於不一樣的消息,記得使用不一樣的格式。其中很容易出錯的就是格式不規範。<!CDATA[[]]> 中括號之間最好不要有空格,否則定位起錯誤仍是很麻煩的。

  • 服務的穩定性。這裏用的web框架是flask,小巧精良。可是對併發的支持性不是很好,對此可使用uwsgi和Nginx來實現一個更穩定的服務。若是就是打算本身玩一玩,經過命令行啓用(如python api.py)就不是很保險了,由於頗有可能會由於用戶的一個奇怪的輸入致使整個服務垮掉,建議使用nohup的方式,來在必定程度上保證服務的質量。

結果演示

目前這個公衆號支持文字,語音,圖片,圖文等消息類型。示例以下。

演示1

演示2

總結

在將公衆號從PHP重構爲Python的過程當中,遇到了一些問題,而後經過不斷的摸索,慢慢的也把問題解決了。其實有時候就是這樣,只有不斷的發現問題,才能不斷的提高本身。

這裏其實並無深刻的去完善,重構後的微信公衆號其實能作的還有不少,畢竟就看敢不敢想嘛。好了,就先扯這麼多了,後面若是有好的思路和實現,再回來更新好了。

相關文章
相關標籤/搜索