數(shù)據(jù)的解析是不可避免的,我們繼續(xù)推進(jìn)我們的代碼。
下一個(gè)目標(biāo),是在回復(fù)了消息的基礎(chǔ)上更一步。不是固守地回復(fù)一個(gè) hello
,而是用戶(hù)發(fā)什么文本,就回復(fù)什么文本。
看到前面的 xml 數(shù)據(jù),可能有人就直接暴力地操上正則表達(dá)式,去抽取各字段的內(nèi)容了。這當(dāng)然也行,畢竟這套 xml 格式是很簡(jiǎn)單的。當(dāng)然,即使如此,你寫(xiě)的正則表達(dá)式也不一定總能正式工作了,比如我發(fā)的消息內(nèi)容就是 ]]>
,那么微信服務(wù)器過(guò)來(lái)的數(shù)據(jù)就是:
<xml><ToUserName><![CDATA[gh_b47caeadeeb7]]></ToUserName> <FromUserName><![CDATA[ov_QzuF0iskLIXqu0r71qOLmZV6B]]></FromUserName> <CreateTime>1407301203</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[]]>]]></Content> <MsgId>6044312642707056806</MsgId> </xml>
有些糾結(jié)了吧。你可以拿 ]]>
這些字符串去試一下其它的公眾賬號(hào)的反應(yīng),上面的內(nèi)容不是合格的 XML 。
正則不是不能寫(xiě),但是解析 xml ,還是上專(zhuān)業(yè)的工具更合適,也更高效與方便。同時(shí)我們不光要解析 XML 數(shù)據(jù),在回復(fù)時(shí)還需要構(gòu)建 XML 數(shù)據(jù),雖然在構(gòu)建上使用 lxml 的 api 不一定比直接上模板直觀(guān)。
lxml
是 Python 的第三方模塊,是對(duì) C 實(shí)現(xiàn)的 libxml
的封裝。SAE 已經(jīng)預(yù)裝了此模塊,謝天謝地。此項(xiàng)目的官網(wǎng)在 http://lxml.de/ 。你可以需要去上面看看文檔和示例代碼。
使用 lxml
解析比較小的 xml 時(shí),xpath
是一個(gè)強(qiáng)大的工具,你可能需要額外地去了解一下 xpath
的基本使用。
要在 SAE 中使用 lxml 這個(gè)模塊,需要在 config.yaml
中單獨(dú)配置一下,先在下面的文檔看看 SAE 支持的預(yù)裝模塊及對(duì)應(yīng)的版本:
http://sae.sina.com.cn/doc/python/runtime.html#pre-installed-package-list
修改 config.yaml
文件:
name: xxx version: 1 libraries: - name: lxml version: "2.3.4"
前面說(shuō)過(guò)了,除了解析 XML ,我們還使用 lxml 生成響應(yīng)的 XML 數(shù)據(jù)。lxml 提供的 E-factory 這個(gè) API 用來(lái)生成 XML 數(shù)據(jù)很好用。不過(guò)壞消息是它不支持節(jié)點(diǎn)的填充內(nèi)容為 CDATA
,好消息是在 github 上的 lxml 項(xiàng)目的開(kāi)發(fā)源碼中,已經(jīng)添加了這個(gè)特性。
這里我們就要自己動(dòng)手搞點(diǎn)東西了,我們把 github 上的 builder.py
的最新源碼拿下來(lái)單獨(dú)用。為了組織代碼方便,我們先在項(xiàng)目目錄中創(chuàng)建一個(gè) packages
目錄,第三方模塊就往這里放。
mkdir packages cd packages wget https://raw.githubusercontent.com/lxml/lxml/master/src/lxml/builder.py -Olxml_builder.py
這樣就可以了?,F(xiàn)在慢慢地我們的邏輯在增加,每做一次修改,都要把代碼提交到 SAE 上,然后打開(kāi)手機(jī),發(fā)送消息來(lái)檢查代碼的正確于否,這樣做太痛苦了。還記得之前做的 server.py
么,我們應(yīng)該在本機(jī)完成更多的測(cè)試工作。
在不添加驗(yàn)證邏輯的情況下,和微信服務(wù)器的交互,僅僅是處理 POST 的數(shù)據(jù)而已,這點(diǎn)我們?cè)诒緳C(jī)很容易實(shí)現(xiàn)。
創(chuàng)建一個(gè) sample
目錄,把之前在 storage 中保存的那幾個(gè)文本,圖片,音頻的請(qǐng)求數(shù)據(jù)存到文件中去。
現(xiàn)在我們項(xiàng)目的目錄樹(shù)是這樣的了:
. ├── config.yaml ├── index.wsgi ├── packages │ ├── lxml_builder.py ├── sample │ ├── wx-img.xml │ ├── wx-txt.xml │ └── wx-voice.xml └── server.py
開(kāi)始在 index.wsgi
中實(shí)現(xiàn)我們的邏輯,用戶(hù)發(fā)什么,我們就回復(fù)什么:
# -*- coding: utf-8 -*- import sys import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'packages')) import re from lxml import etree from lxml_builder import E from lxml.etree import CDATA def application(environ, start_response): if environ.get('REQUEST_METHOD', 'GET') == 'GET': q = environ.get('QUERY_STRING') m = re.findall('echostr=(.*)', q)[0] s = m.split('&', 1)[0] start_response('200 ok', [('content-type', 'text/plain')]) return [s] length = environ.get('CONTENT_LENGTH', 0) length = int(length) body = environ['wsgi.input'].read(length) try: root = etree.XML(body.decode('utf8')) except: start_response('200 ok', [('content-type', 'text/xml')]) return [''] me = root.xpath('/xml/ToUserName/text()')[0] user = root.xpath('/xml/FromUserName/text()')[0] create = root.xpath('/xml/CreateTime/text()')[0] type = root.xpath('/xml/MsgType/text()')[0] content = root.xpath('/xml/Content/text()')[0] id = root.xpath('/xml/MsgId/text()')[0] xml = ( E.xml( E.ToUserName(CDATA(user)), E.FromUserName(CDATA(me)), E.CreateTime(CDATA(create)), E.MsgType(CDATA('text')), E.Content(CDATA('\n'.join([me, user, create, type, content, id]))), ) ) s = etree.tostring(xml, encoding='utf-8') start_response('200 ok', [('content-type', 'text/xml')]) return [s]
這里我們不光回復(fù)了用戶(hù)輸入的內(nèi)容,還把整個(gè)請(qǐng)求信息都得現(xiàn)一遍。
先在本機(jī)跑跑,啟動(dòng) python server.py
,然后使用 curl
發(fā)送保存 sample
目錄下的數(shù)據(jù)看看結(jié)果:
curl -d @sample/wx-txt.xml 'http://localhost:8888'
得到正確的 xml 響應(yīng)就沒(méi)問(wèn)題了。把代碼提交到 SAE,用手機(jī)上的微信試試了。
更多建議: