从上一篇闭关纪要文章闭关纪要19.Google Datastore API的疑似BUG?之后,因为这个BUG,至今没有得到解决,因此,我暂时停止了需要大批量向Google Datastore传递大量数据的程序的研究,不过还是继续在进行GAE相关的研究,特别是,我在发现我在万网申请的虚拟主机在铁通居然上不了,心中很郁闷,因此更加倾向于将我的网站的更多功能转移到GAE上了。
我原先以为下载和解析XML是一个很广泛的使用,在GAE上肯定使用起来很容易的,可是我慢慢发现,没有这么容易,Google App Engine居然在这个过程上BUG不少,我花了好久终于找到了一个能够完成XML下载和运行的方案。
我根据网上大家的讨论,使用过ElementTree,和SimpleXMLTreeBuilder,最终都出现这样或那样的BUG,例如"illegal character in content",还有几个error,我没有具体的error内容是什么了,而且在处理包含中文的XML的时候,又会出现更复杂的问题,因此,我专门的介绍一下我成功实现的一个RSS文件读取功能,希望对也要实现同样功能的用户有所帮助。
我最后采用的XML解析类是minidom,从名字看起来,是一个很简单的XML解析,简单与否我倒是不在意,只要能解析就行,要不然我就必须要通过正则表达式自己去匹配内容,可就累得多了。
我的简单实现代码如下(我这个是一个随机取一个笑话返回的REST程序,运行结果如下:http://service.dituren.cn/services/joke_random?id=30&c=onJokeLoaded,在客户端请求的时候,随机的使用一个id,服务端会自动下载一个RSS之中的内容,并将相应的结果返回。
joke_random.py
1# -*- coding: utf-8 -*- #
2import wsgiref.handlers
3import cgi
4import types
5from datetime import *
6from xml.dom import minidom
7from google.appengine.ext import db
8from google.appengine.ext import webapp
9from google.appengine.api import urlfetch
10
11#定义Rss条目的数据类
12class RssData(db.Model):
13 category = db.CategoryProperty(required=True)
14 rssUrl = db.LinkProperty(required=True)
15 downloadTime = db.DateTimeProperty(auto_now_add= True )
16 title = db.StringProperty(required=True)
17 content = db.TextProperty()
18
19class JokeRandom(webapp.RequestHandler):
20 #根据指定的url,返回minidom的解析结果文档,如果解析失败,则不返回内容
21 def parse(self, url ) :
22 result = urlfetch.fetch(url)
23 if result.status_code == 200:
24 content=result.content
25 #这一句用来将返回内容里面的xml文件头的encoding="gb2312"替换成encoding="utf-8"
26 content=content.replace("gb2312","utf-8")
27 #下面这一句用来进行编码转换,本来应该也是gb2312的,可是我调用的这个XML不够规范,他返回的实际编码是gbk,可是文件头里面写的却是gb2312
28 #因此,要进行一个这样的转换
29 #很多中文网站都喜欢以这样变态的方式输出,因此特别要注意了
30 content=content.decode("gbk").encode('UTF_8')
31 #将utf-8编码的XML传递给minidom
32 return minidom.parseString(content)
33
34 def getJoke(self,id):
35 query=RssData.all()
36 query.filter('category = ','Joke')
37 #根据请求的条目编号,请求指定数目的条目
38 results=query.fetch(id+1)
39 number=len(results)
40 #如果已经有数据,并且没有过期,则直接返回指定数据
41 if number>0 and datetime.now()-results[0].downloadTime<timedelta(7):
42 #这里必须取模,因为数据未必有这么多条
43 return results[id%number]
44 rssUrl='http://www.xiaohuayoumo.com/plus/rss/5.xml'
45 #下载和解析rss内容
46 rss = self.parse(rssUrl)
47
48 if not(rss):
49 if number>0:
50 return results[id%number]
51 else:
52 return None
53
54 if number>0:
55 query=RssData.all()
56 query.filter('category = ','Joke')
57 results=query.fetch(1000)
58 db.delete(results)
59 number=0
60 #从RSS文件之中解析条目并添加到数据库之中
61 for item in rss.getElementsByTagName('item'):
62 title=item.getElementsByTagName('title')[0].firstChild.nodeValue
63 content=item.getElementsByTagName('description')[0].firstChild.nodeValue
64 joke=RssData(category="Joke",rssUrl=rssUrl,title=title)
65 joke.content=db.Text(content)
66 joke.put()
67 number=number+1
68
69 if number==0:
70 return None
71 return self.getJoke(id)
72
73 def get(self):
74 if self.request.headers.get('If-Modified-Since',''):
75 self.response.set_status(304)
76 return
77 #设置缓存
78 self.response.headers['Content-Type'] = 'text/html; charset=UTF-8'
79 self.response.headers.add_header("Expires", (datetime.now()+timedelta(7)).strftime("%a, %d %b %Y %H:%M:%S GMT"))
80 self.response.headers.add_header("Last-Modified", datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT"))
81 #输出REST的调用函数名称
82 self.response.out.write(self.request.get('c'))
83 id=self.request.get('id')
84 if not id:
85 id=0
86 joke=self.getJoke(int(id))
87 self.response.out.write('(')
88 if joke :
89 self.response.out.write('{title:\"'+joke.title+'\",content:\"'+joke.content+'\"}')
90 self.response.out.write(")")
91def main():
92 application = webapp.WSGIApplication(
93 [('/services/joke_random', JokeRandom)],
94 debug=True)
95 wsgiref.handlers.CGIHandler().run(application)
96if __name__ == "__main__":
97 main()
从以上的代码,就实现了一个这样的过程,客户端请求一个指定编号的笑话条目的时候,服务端请求一个rss文件,并且返回指定编号的条目,并将内容保存到数据库之中,以便下次查询的时候不再需要下载。
我的程序最终目的是能够在客户端实现显示一个随机的笑话,效果可以从http://www.dituren.cn/ 上面看到,每次地图加载完成之前,在地图显示区域先显示一个随机的笑话,因为是随机的,所以不会每次显示同一个笑话,而且因为有一定的缓存技术,加载速度比较快,可以用来在用户等待地图加载的时候看个笑话消遣用。