feedparser 是解析 RSS/Atom 的最稳方案,自动归一化时间、补全链接、处理空标题;去重用时间戳整数键,摘要截断用 textwrap.shorten,缓存用 SQLite,输出用 print 直接格式化。

用 feedparser 解析 RSS 源最稳,别自己写 XML 解析
Python 标准库不带 RSS 解析能力,硬啃 xml.etree.ElementTree 容易漏掉 atom:updated、dc:date、时区偏移、相对链接补全等细节。直接上 feedparser 是唯一合理选择——它专为现实世界混乱的 RSS/Atom 设计,自动归一化时间字段为 parsed 对象,统一处理 link 字段(包括 enclosure 和 alternate)。
安装后立刻可用:
pip install feedparser注意:别用
python-feedparser(旧包名),也别信某些教程推荐的 lxml + 手动 XPath,那是在给自己埋兼容性雷。
- 所有时间字段(无论原始格式是 RFC 2822、ISO 8601 还是模糊字符串)都可通过
entry.published_parsed或entry.updated_parsed取到time.struct_time -
entry.link已是绝对 URL,无需再拼 base;若需原文中的 link(比如<link rel="alternate">),查entry.links列表 - 遇到空
entry.title时,feedparser会 fallback 到entry.description[:60] + "...",这行为可接受,不必额外判空
按发布时间去重并截断摘要,靠 datetime 和 textwrap
RSS 源不保证条目顺序,同一源多次抓取可能重复推送,必须基于时间戳做去重。别用字符串比对标题——不同源标题可能撞车,同一源改标题也会漏判;也别依赖 entry.id,很多小博客根本不发 <guid></guid>。
正确做法:把所有条目的 updated_parsed 或 published_parsed 转成 datetime,再用 datetime.timestamp() 当唯一键。摘要截断用标准库 textwrap.shorten(),它会智能断词,不砍在单词中间。
立即学习“Python免费学习笔记(深入)”;
- 优先用
entry.updated_parsed,没则 fallback 到entry.published_parsed,两者都无才用当前抓取时间(说明源极不规范) - 去重用
set存时间戳整数(秒级足够),不是存整个datetime对象——避免浮点精度和时区对象比较陷阱 -
textwrap.shorten(entry.description, width=200, placeholder="…")比手写[:200] + "..."安全得多,能处理换行、多余空格、HTML 实体残留
本地缓存已读状态,用 sqlite3 而非 JSON 文件
小型聚合器不需要 Redis 或 PostgreSQL。但用 JSON 文件存已读 ID 或时间戳,会在并发读写时丢数据——Python 的 json.dump() 不带原子写入,也没文件锁。SQLite 是零配置、单文件、ACID 兼容的最优解。
建一张 feeds 表存源 URL 和最后检查时间,一张 entries 表存条目哈希(如 sha256(f"{entry.link}{entry.updated}"))和入库时间。每次抓取前查 feeds.last_checked,只拉取该时间之后的新条目。
- 表结构越简单越好:
CREATE TABLE entries (hash TEXT PRIMARY KEY, fetched_at TIMESTAMP),别加自增 ID - 插入用
INSERT OR IGNORE,避免重复执行异常;查询用WHERE fetched_at > ?配合参数化,防 SQL 注入 - SQLite 默认支持 WAL 模式,多进程写入也不卡死,比文件锁 +
os.rename()简洁可靠
输出纯文本摘要时,用 print() 直接刷屏比写 HTML 更实用
所谓“输出更新摘要”,多数场景就是命令行里看一眼。生成 HTML 再开浏览器,反而增加延迟和依赖。用 print() 拼接即可:源名加粗、标题换行、摘要缩进两格、发布时间右对齐——终端原生支持这些基础格式。
别引入 rich 或 colorama,除非你明确需要颜色。普通用户终端里,\033[1m 转义序列不一定生效,而加粗的 <strong></strong> 标签在纯文本里只是噪音。
- 时间显示用
datetime.strftime("%m-%d %H:%M"),别用%c(locale 依赖太强) - 标题过长时用
textwrap.fill(title, width=80)自动折行,比手动replace("\n", " ")更尊重原文段落 - 每条之间空一行,比加横线
"—"*50更省眼力,也方便管道给grep或less
真正难的不是解析或存储,而是判断某条“算不算新”——RSS 源可能倒序发、时间戳乱填、甚至故意不更新 updated 字段。所以首次运行后,务必人工核对几条,确认时间过滤逻辑没漏掉真更新。


















