RSS Feed是内容分发领域沿用至今的经典方案,读者通过订阅工具可以及时获取博客、新闻站的更新内容。很多Python开发者第一次接触RSS生成时,会尝试手写XML拼接字符串,结果发现频道元数据、条目时间格式、CDATA转义这些细节非常多,稍有疏忽产出的XML就无法被阅读器正确解析。其实Python有一个专门的库可以彻底解决这个问题,它就是feedgen。这个库同时支持RSS 2.0和Atom两种格式,用面向对象的方式管理feed结构,输出的XML严格遵守规范,非常适合博客系统、内容平台的订阅源输出场景。

一、feedgen的安装与核心概念
feedgen是一个纯Python实现的第三方库,安装非常简单,通过pip即可完成:
pip install feedgen
安装完成后,先来理解它的两个核心概念。第一个是FeedGenerator对象,它代表整个订阅源本身,负责管理频道级别的信息,比如feed的标题、链接、描述、语言、作者等。第二个是add_item方法返回的FeedEntry对象,每一个FeedEntry对应订阅源中的一条内容,比如一篇文章、一个视频或者一条公告。这种结构和RSS 2.0规范中的channel与item是一一对应的。
需要特别说明的是,feedgen在同一个generator实例上添加的条目,既可以输出为RSS格式,也可以输出为Atom格式,不需要维护两套数据。调用rss_str()得到RSS 2.0的XML字符串,调用atom_str()则得到Atom格式,两种输出共享同一份配置,这为需要同时支持多种订阅格式的站点提供了极大便利。
二、用feedgen创建一个基本的RSS订阅源
下面通过一个最小可运行的例子演示feedgen的基本用法,包括初始化generator、设置频道信息、添加一条条目并输出XML文件:
from feedgen.feed import FeedGenerator
# 创建feed生成器
fg = FeedGenerator()
fg.id('https://www.ipipp.com/feed')
fg.title('我的技术博客')
fg.link(href='https://www.ipipp.com', rel='alternate')
fg.link(href='https://www.ipipp.com/feed', rel='self')
fg.subtitle('分享Python后端开发经验与踩坑记录')
fg.language('zh-CN')
# 添加一条内容
fe = fg.add_entry()
fe.id('https://www.ipipp.com/post/100')
fe.title('feedgen使用入门')
fe.link(href='https://www.ipipp.com/post/100')
fe.description('一篇介绍如何用feedgen生成RSS订阅源的文章')
fe.published('2024-06-01T10:00:00+08:00')
# 输出为RSS格式的XML文件
fg.rss_file('feed.xml', pretty=True)这段代码有几个值得注意的细节。id()方法用于设置feed和条目的唯一标识,对RSS来说会渲染到link或guid字段,对Atom来说是必填的id元素。link()方法可以调用多次,通过rel参数区分站点首页链接和feed自身的地址,其中rel为self的链接在多数阅读器的校验中被强烈建议提供。
另外,rss_file()的pretty参数可以让输出的XML带缩进换行,便于开发调试。如果是生产环境的高流量接口,可以去掉pretty以减小响应体积。除了写文件,还可以用fg.rss_str(pretty=True)直接拿到XML字符串,方便配合Flask或FastAPI返回HTTP响应。
三、条目的正文内容、分类与时间处理
真实的订阅源条目往往不止一段摘要,还需要携带完整的HTML正文。feedgen提供了content()方法来设置正文,支持指定type和是否转义:
fe = fg.add_entry()
fe.id('https://www.ipipp.com/post/101')
fe.title('Flask动态输出RSS订阅源实战')
fe.link(href='https://www.ipipp.com/post/101')
# 设置完整HTML正文
fe.content(
'<p>这篇文章演示了如何在<strong>Flask</strong>中动态输出RSS。</p>',
type='html'
)
# 添加分类标签
fe.category([
{'term': 'Python'},
{'term': 'Flask'},
{'term': 'RSS'}
])
# 添加作者信息
fe.author({'name': '张三', 'email': 'zhangsan@ipipp.com'})
# 更新时间,字段名与标准对应
fe.updated('2024-06-10T09:30:00+08:00')时间处理是RSS生成中最容易踩坑的环节。RSS 2.0规范中pubDate要求使用RFC 822格式,而Atom要求ISO 8601格式。feedgen内部会根据输出格式自动转换,你只需要传入ISO 8601格式的字符串或者Python的datetime对象即可。如果传入datetime对象,建议带上时区信息,否则部分严格的阅读器会解析失败。可以这样做:
from datetime import datetime, timezone, timedelta tz = timezone(timedelta(hours=8)) now = datetime.now(tz) fe.published(now) fe.updated(now)
分类功能方面,category()接受一个字典列表,每个字典的term字段是分类名。对于中文站点,如果分类需要显示中文,直接传中文字符串即可,feedgen输出XML时会自动处理字符编码,最终文件以UTF-8编码保存,中文阅读器可以正常显示。
四、实战:从数据库读取文章动态生成订阅接口
最后给出一个贴近生产环境的例子。假设博客文章存在SQLite数据库中,需求是提供一个访问即刷新的RSS接口,始终输出最新的二十篇文章。这里用Flask配合feedgen实现:
import sqlite3
from flask import Flask, Response
from feedgen.feed import FeedGenerator
app = Flask(__name__)
def get_latest_posts(limit=20):
conn = sqlite3.connect('blog.db')
cursor = conn.cursor()
cursor.execute(
'SELECT id, title, summary, created_at FROM posts '
'ORDER BY created_at DESC LIMIT ?', (limit,)
)
rows = cursor.fetchall()
conn.close()
return rows
@app.route('/feed')
def feed():
fg = FeedGenerator()
fg.id('https://www.ipipp.com/feed')
fg.title('我的技术博客')
fg.link(href='https://www.ipipp.com', rel='alternate')
fg.link(href='https://www.ipipp.com/feed', rel='self')
fg.subtitle('最新文章订阅')
fg.language('zh-CN')
for post_id, title, summary, created_at in get_latest_posts():
url = f'https://www.ipipp.com/post/{post_id}'
fe = fg.add_entry()
fe.id(url)
fe.title(title)
fe.link(href=url)
fe.description(summary)
fe.published(created_at)
xml = fg.rss_str(pretty=True)
return Response(xml, mimetype='application/rss+xml')
if __name__ == '__main__':
app.run(debug=True)这个实现的关键点在于每次请求都实时查询数据库并重建feed,保证订阅内容的实时性。响应头中的mimetype设置为application/rss+xml,浏览器和阅读器都能正确识别。如果站点访问量大,可以考虑把生成的XML缓存起来,设置一个较短的过期时间(比如五分钟),在实时性和数据库压力之间取得平衡。
此外还有一种常见需求是为feed添加自定义命名空间的扩展字段,比如播放时长、封面图等。feedgen提供了fg.register_extension()方法注册扩展,通过继承BaseExtension可以自定义任意字段,灵活性完全够用。对于绝大多数博客和内容站来说,掌握本文介绍的频道配置、条目添加、时间处理和动态输出这四个环节,就已经能够搭建出一个规范且体验良好的RSS订阅源了。