在Google App Engine上生成RSS订阅源

3 投票
3 回答
2864 浏览
提问于 2025-04-17 08:22

我想在 Google App Engine 上用 Python 提供一个 RSS 订阅源。

我试着用普通的请求处理器来生成 XML 响应。当我直接访问这个订阅源的链接时,可以看到内容是正确的。但是,当我尝试在 Google Reader 中订阅这个源时,它却显示:

“请求的订阅源无法找到。”

我在想,这种做法是不是正确。我考虑过用一个静态的 XML 文件,并通过定时任务来更新它。但是因为 Google App Engine 不支持文件输入输出,这种方法似乎行不通。

该怎么解决这个问题呢?谢谢!

3 个回答

2

生成XML和生成HTML其实没什么特别的区别,只要你设置好内容类型就行了。你可以把你的数据提交到这个验证工具 http://validator.w3.org/feed/,它会告诉你哪里出错了。

如果这个工具没法解决你的问题,那你就需要把你的源代码给我们看了。如果你不把代码给我们,我们就没法帮你找出问题所在。

2

我有一个为我的博客生成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应用生成订阅源的核心内容。

6

我建议有两个解决方案:

  1. GAE-REST,你可以直接把它加到你的项目里,配置一下,它就能帮你生成RSS。不过这个项目比较老了,现在已经不再维护了。

  2. 你可以像我一样,使用一个模板来写一个列表,这样我就成功生成了可以通过谷歌阅读器读取的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)

希望这些对你有帮助,这两种方法对我都有效。

撰写回答