codecamp

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,用手机上的微信试试了。


SAE 第一个响应
SAE 获取access_token
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }