手把手教你用 Python + RSSHub + Ollama 搭建一个 AI 摘要阅读器,告别信息过载。
项目背景
每天刷几十个公众号、看十几篇技术博客,时间全耗在"浏览"上了。
真正的阅读是理解,不是翻页。
我试过 Feedly、Inoreader,它们能聚合内容,但不会帮你提炼重点。后来我搭了一个 AI RSS 摘要器,每天早上推送 10 条精华摘要,30 秒搞定一天的信息摄入。
今天把这个方案分享出来。
技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| 语言 | Python 3.10+ | 生态丰富 |
| RSS 解析 | feedparser | 经典稳定 |
| AI 模型 | Ollama + Qwen2.5 | 本地运行,免费 |
| 定时任务 | cron | 系统自带 |
| 推送方式 | 钉钉 Webhook | 配置简单 |
实现步骤
第一步:安装依赖
pip install feedparser requests
确保 Ollama 已安装并拉取模型:
ollama pull qwen2.5:7b
第二步:编写核心脚本
创建 ai_rss_summarizer.py:
#!/usr/bin/env python3
"""AI RSS 智能摘要阅读器"""
import json
import os
import time
import hashlib
from datetime import datetime
from pathlib import Path
import feedparser
import requests
# ============ 配置区 ============
# RSS 源列表(替换为你关注的源)
RSS_FEEDS = [
{"name": "Hacker News", "url": "https://hnrss.org/frontpage"},
{"name": "InfoQ 中文站", "url": "https://www.infoq.cn/feed"},
{"name": "阮一峰周刊", "url": "https://www.ruanyifeng.com/blog/atom.xml"},
{"name": "V2EX", "url": "https://www.v2ex.com/index.xml"},
]
# 摘要数量(每个源取最新 N 条)
ARTICLES_PER_FEED = 3
# 摘要提示词模板
SUMMARY_PROMPT = """你是一个技术资讯编辑。请对以下文章进行摘要。
要求:
1. 用一句话概括核心内容
2. 列出 2-3 个关键要点
3. 标注难度等级(入门/进阶/高阶)
文章标题:{title}
作者:{author}
发布时间:{pub_date}
正文摘要:{content}
只输出 JSON 格式,不要其他文字:
{{"summary": "...", "key_points": ["...", "..."], "difficulty": "..."}}
"""
# 钉钉 Webhook(留空则只打印到终端)
DINGTALK_WEBHOOK = os.getenv("DINGTALK_WEBHOOK", "")
# 缓存文件路径
CACHE_FILE = Path.home() / ".ai_rss_cache.json"
def load_cache() -> dict:
"""加载已处理的文章 ID 缓存"""
if CACHE_FILE.exists():
return json.loads(CACHE_FILE.read_text())
return {}
def save_cache(cache: dict):
"""保存缓存"""
# 只保留最近 1000 条记录,防止无限增长
items = list(cache.items())[-1000:]
CACHE_FILE.write_text(json.dumps(dict(items)))
def fetch_articles(feed: dict) -> list:
"""从 RSS 源获取文章列表"""
entries = []
try:
d = feedparser.parse(feed["url"])
for entry in d.entries[:ARTICLES_PER_FEED]:
entries.append({
"feed_name": feed["name"],
"title": entry.get("title", "无标题"),
"link": entry.get("link", ""),
"author": entry.get("author", "未知"),
"pub_date": entry.get("published", "未知"),
"content": (entry.get("summary", "") or "")[:500],
"id": hashlib.md5(entry.get("link", "").encode()).hexdigest(),
})
except Exception as e:
print(f"[错误] 获取 {feed['name']} 失败: {e}")
return entries
def summarize_with_ollama(article: dict) -> dict:
"""调用 Ollama 生成摘要"""
prompt = SUMMARY_PROMPT.format(
title=article["title"],
author=article["author"],
pub_date=article["pub_date"],
content=article["content"],
)
try:
resp = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "qwen2.5:7b",
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.3,
"num_predict": 300,
},
},
timeout=30,
)
result = resp.json()
raw_output = result.get("response", "")
# 尝试从返回中提取 JSON
start = raw_output.find("{")
end = raw_output.rfind("}") + 1
if start >= 0 and end > start:
return json.loads(raw_output[start:end])
# 降级:直接返回文本摘要
return {
"summary": raw_output[:200],
"key_points": [],
"difficulty": "未识别",
}
except Exception as e:
print(f"[警告] Ollama 请求失败: {e}")
return {
"summary": article["title"],
"key_points": [article["content"][:100]],
"difficulty": "未知",
}
def send_dingtalk(notifications: list):
"""发送钉钉通知"""
if not DINGTALK_WEBHOOK:
return
text = "📰 AI RSS 每日精选\n\n"
for i, item in enumerate(notifications, 1):
text += f"{i}. **{item['feed_name']}**: {item['title']}\n"
text += f" {item['summary']}\n\n"
payload = {
"msgtype": "markdown",
"markdown": {
"title": "AI RSS 每日精选",
"text": text,
},
}
requests.post(DINGTALK_WEBHOOK, json=payload, timeout=10)
def main():
cache = load_cache()
notifications = []
new_count = 0
for feed in RSS_FEEDS:
articles = fetch_articles(feed)
for article in articles:
# 跳过已处理的文章
if article["id"] in cache:
continue
cache[article["id"]] = datetime.now().isoformat()
# 生成摘要
summary = summarize_with_ollama(article)
notification = {
"feed_name": article["feed_name"],
"title": article["title"],
"link": article["link"],
"summary": summary["summary"],
"key_points": summary.get("key_points", []),
"difficulty": summary.get("difficulty", "未知"),
}
notifications.append(notification)
new_count += 1
save_cache(cache)
if notifications:
print(f"\n✅ 共发现 {new_count} 条新内容\n")
for item in notifications:
print(f"[{item['feed_name']}] {item['title']}")
print(f" 摘要: {item['summary']}")
print(f" 难度: {item['difficulty']}")
print()
send_dingtalk(notifications)
print("📤 钉钉通知已发送")
else:
print("📭 没有新内容")
if __name__ == "__main__":
main()
第三步:设置定时执行
crontab -e
添加以下内容(每天早上 9 点执行):
0 9 * * * /usr/bin/python3 ~/ai_rss_summarizer.py >> ~/rss_summary.log 2>&1
第四步:测试运行
python3 ai_rss_summarizer.py
预期输出类似:
✅ 共发现 12 条新内容
[Hacker News] Show HN: I built a real-time collaborative code editor
摘要: 作者展示了一个基于 CRDT 算法的实时协作代码编辑器,支持多人同时编辑同一文件...
难度: 进阶
[阮一峰周刊] 2026年第28期:AI Agent 框架盘点
摘要: 本期周刊聚焦 2026 年主流的 AI Agent 开发框架,包括 LangChain、AutoGen 等...
难度: 入门
运行效果
跑起来之后,每天早上 9 点你会收到一份精简版资讯。
每条内容包含:一句话摘要、关键要点、难度标签。
不需要点开链接就能判断哪些值得深入阅读。
我实际跑了两周,每天大约处理 30-50 条新内容,真正需要点开看的不到 10 条。
优化方向
加个 Web 界面。 用 Streamlit 写个简单的页面,可以手动刷新、收藏、搜索历史摘要。
接入更多数据源。 RSSHub 能覆盖几乎所有网站,把 RSSHub 的地址加到 RSS_FEEDS 里就行。
模型切换。 如果 Qwen2.5 响应太慢,可以换成 llama3.2:1b 或者调用 API 版的 GPT-4o-mini。
去重增强。 当前用 URL MD5 做去重,如果遇到短链会被误判。可以加一个语义相似度检查,用 sentence-transformers 算余弦距离。
你平时都关注哪些 RSS 源?加到我的列表里,明天就能看到效果。
这篇文章对你有帮助吗?