SAE 使用lxml处理XML数据
数据的解析是不可避免的,我们继续推进我们的代码。
下一个目标,是在回复了消息的基础上更一步。不是固守地回复一个 hello
,而是用户发什么文本,就回复什么文本。
看到前面的 xml 数据,可能有人就直接暴力地操上正则表达式,去抽取各字段的内容了。这当然也行,毕竟这套 xml 格式是很简单的。当然,即使如此,你写的正则表达式也不一定总能正式工作了,比如我发的消息内容就是 ]]>
,那么微信服务器过来的数据就是:
<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>
有些纠结了吧。你可以拿 ]]>
这些字符串去试一下其它的公众账号的反应,上面的内容不是合格的 XML 。
正则不是不能写,但是解析 xml ,还是上专业的工具更合适,也更高效与方便。同时我们不光要解析 XML 数据,在回复时还需要构建 XML 数据,虽然在构建上使用 lxml 的 api 不一定比直接上模板直观。
lxml
是 Python 的第三方模块,是对 C 实现的 libxml
的封装。SAE 已经预装了此模块,谢天谢地。此项目的官网在 http://lxml.de/ 。你可以需要去上面看看文档和示例代码。
使用 lxml
解析比较小的 xml 时,xpath
是一个强大的工具,你可能需要额外地去了解一下 xpath
的基本使用。
要在 SAE 中使用 lxml 这个模块,需要在 config.yaml
中单独配置一下,先在下面的文档看看 SAE 支持的预装模块及对应的版本:
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"
前面说过了,除了解析 XML ,我们还使用 lxml 生成响应的 XML 数据。lxml 提供的 E-factory 这个 API 用来生成 XML 数据很好用。不过坏消息是它不支持节点的填充内容为 CDATA
,好消息是在 github 上的 lxml 项目的开发源码中,已经添加了这个特性。
这里我们就要自己动手搞点东西了,我们把 github 上的 builder.py
的最新源码拿下来单独用。为了组织代码方便,我们先在项目目录中创建一个 packages
目录,第三方模块就往这里放。
mkdir packages cd packages wget https://raw.githubusercontent.com/lxml/lxml/master/src/lxml/builder.py -Olxml_builder.py
这样就可以了。现在慢慢地我们的逻辑在增加,每做一次修改,都要把代码提交到 SAE 上,然后打开手机,发送消息来检查代码的正确于否,这样做太痛苦了。还记得之前做的 server.py
么,我们应该在本机完成更多的测试工作。
在不添加验证逻辑的情况下,和微信服务器的交互,仅仅是处理 POST 的数据而已,这点我们在本机很容易实现。
创建一个 sample
目录,把之前在 storage 中保存的那几个文本,图片,音频的请求数据存到文件中去。
现在我们项目的目录树是这样的了:
. ├── config.yaml ├── index.wsgi ├── packages │ ├── lxml_builder.py ├── sample │ ├── wx-img.xml │ ├── wx-txt.xml │ └── wx-voice.xml └── server.py
开始在 index.wsgi
中实现我们的逻辑,用户发什么,我们就回复什么:
# -*- 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]
这里我们不光回复了用户输入的内容,还把整个请求信息都得现一遍。
先在本机跑跑,启动 python server.py
,然后使用 curl
发送保存 sample
目录下的数据看看结果:
curl -d @sample/wx-txt.xml 'http://localhost:8888'
得到正确的 xml 响应就没问题了。把代码提交到 SAE,用手机上的微信试试了。
更多建议: