在Google App Engine上生成RSS订阅源
我想在 Google App Engine 上用 Python 提供一个 RSS 订阅源。
我试着用普通的请求处理器来生成 XML 响应。当我直接访问这个订阅源的链接时,可以看到内容是正确的。但是,当我尝试在 Google Reader 中订阅这个源时,它却显示:
“请求的订阅源无法找到。”
我在想,这种做法是不是正确。我考虑过用一个静态的 XML 文件,并通过定时任务来更新它。但是因为 Google App Engine 不支持文件输入输出,这种方法似乎行不通。
该怎么解决这个问题呢?谢谢!
3 个回答
生成XML和生成HTML其实没什么特别的区别,只要你设置好内容类型就行了。你可以把你的数据提交到这个验证工具 http://validator.w3.org/feed/,它会告诉你哪里出错了。
如果这个工具没法解决你的问题,那你就需要把你的源代码给我们看了。如果你不把代码给我们,我们就没法帮你找出问题所在。
我有一个为我的博客生成Atom订阅源的工具,它运行在AppEngine/Python上。我使用Django 1.2的模板引擎来构建这个订阅源。我的模板大概是这样的:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xml:lang="en"
xml:base="http://www.example.org">
<id>urn:uuid:4FC292A4-C69C-4126-A9E5-4C65B6566E05</id>
<title>Adam Crossland's Blog</title>
<subtitle>opinions and rants on software and...things</subtitle>
<updated>{{ updated }}</updated>
<author>
<name>Adam Crossland</name>
<email>adam@adamcrossland.net</email>
</author>
<link href="http://blog.adamcrossland.net/" />
<link rel="self" href="http://blog.adamcrossland.net/home/feed" />
{% for each_post in posts %}{{ each_post.to_atom|safe }}
{% endfor %}
</feed>
注意:如果你使用这些代码,你需要自己创建一个uuid,放到id节点里。
更新节点应该包含订阅源最后更新的时间和日期,格式要符合rfc 3339标准。幸运的是,Python有一个库可以帮你处理这个问题。下面是生成订阅源的控制器的一部分:
from rfc3339 import rfc3339
posts = Post.get_all_posts()
self.context['posts'] = posts
# Initially, we'll assume that there are no posts in the blog and provide
# an empty date.
self.context['updated'] = ""
if posts is not None and len(posts) > 0:
# But there are posts, so we will pick the most recent one to get a good
# value for updated.
self.context['updated'] = rfc3339(posts[0].updated(), utc=True)
response.content_type = "application/atom+xml"
别担心self.context['updated']
的内容。这只是我的框架提供的一个快捷方式,用来设置模板变量。重要的是,我用rfc3339
函数来编码我想要使用的日期。此外,我还把响应对象的content_type
属性设置为application/atom+xml
。
唯一缺少的部分是模板使用了一个叫to_atom
的方法,把Post
对象转换成Atom格式的数据:
def to_atom(self):
"Create an ATOM entry block to represent this Post."
from rfc3339 import rfc3339
url_for = self.url_for()
atom_out = "<entry>\n\t<title>%s</title>\n\t<link href=\"http://blog.adamcrossland.net/%s\" />\n\t<id>%s</id>\n\t<summary>%s</summary>\n\t<updated>%s</updated>\n </entry>" % (self.title, url_for, self.slug_text, self.summary_for(), rfc3339(self.updated(), utc=True))
return atom_out
据我所知,这就是所需的全部代码,这段代码确实能为我的博客生成一个完美且有效的订阅源。现在,如果你真的想做RSS而不是Atom,你需要更改订阅源模板、Post模板和content_type,但我认为这就是从AppEngine/Python应用生成订阅源的核心内容。
我建议有两个解决方案:
GAE-REST,你可以直接把它加到你的项目里,配置一下,它就能帮你生成RSS。不过这个项目比较老了,现在已经不再维护了。
你可以像我一样,使用一个模板来写一个列表,这样我就成功生成了可以通过谷歌阅读器读取的RSS(GeoRSS)。模板是:
<title>{{host}}</title> <link href="http://{{host}}" rel="self"/> <id>http://{{host}}/</id> <updated>2011-09-17T08:14:49.875423Z</updated> <generator uri="http://{{host}}/">{{host}}</generator> {% for entity in entities %} <entry> <title><![CDATA[{{entity.title}}]]></title> <link href="http://{{host}}/vi/{{entity.key.id}}"/> <id>http://{{host}}/vi/{{entity.key.id}}</id> <updated>{{entity.modified.isoformat}}Z</updated> <author><name>{{entity.title|escape}}</name></author> <georss:point>{{entity.geopt.lon|floatformat:2}},{{entity.geopt.lat|floatformat:2}}</georss:point> <published>{{entity.added}}</published> <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">{{entity.text|escape}}</div> </summary> </entry> {% endfor %} </feed>
我的处理程序是(你也可以用python 2.7来做,只需在处理程序外写一个函数,这样会更简单):
class GeoRSS(webapp2.RequestHandler):
def get(self):
start = datetime.datetime.now() - timedelta(days=60)
count = (int(self.request.get('count'
)) if not self.request.get('count') == '' else 1000)
try:
entities = memcache.get('entities')
except KeyError:
entity = Entity.all().filter('modified >',
start).filter('published =',
True).order('-modified').fetch(count)
memcache.set('entities', entities)
template_values = {'entities': entities, 'request': self.request,
'host': os.environ.get('HTTP_HOST',
os.environ['SERVER_NAME'])}
dispatch = 'templates/georss.html'
path = os.path.join(os.path.dirname(__file__), dispatch)
output = template.render(path, template_values)
self.response.headers['Cache-Control'] = 'public,max-age=%s' \
% 86400
self.response.headers['Content-Type'] = 'application/rss+xml'
self.response.out.write(output)
希望这些对你有帮助,这两种方法对我都有效。