☰
从零搭建NewsNow:RSS信息聚合与AI摘要推送全攻略
2026/9/26 2:09:59 网站建设 项目流程

NewsNow这个名字最近在折腾信息聚合的朋友圈子里被反复提起。说白了,它就是一个帮你把全网散落的消息自动抓回来、筛一遍、生成摘要、再推到你手机上的工具。尤其2026年这版,很多细节跟早前流传的教程已经不一样了,我把自己从零搭起来的过程完整写一遍,包含踩过的坑和调整过的参数,照着做基本能一次跑通。

这套方案适合谁呢?平时需要盯多个资讯源、又不想装一堆App的人,或者做内容运营、需要快速掌握行业动态的朋友。不管你是第一次接触自建消息流,还是之前试过别的聚合工具觉得不顺手,这篇都能给你一套可以直接抄作业的玩法。

1. NewsNow整体设计与架构思路

1.1 项目到底要解决什么问题

先聊聊我为什么放着现成的RSS阅读器不用,非要自己折腾一套。市面上的RSS阅读器确实能订阅,但有两个核心痛点解决不了:一是纯RSS拿到的往往只有文章摘要,想看清全文还得跳转;二是信息过载严重,几百条更新里真正值得看的可能就两三条。NewsNow的思路是把“订阅、抓取、筛选、摘要、推送”串成一条流水线,只把结果送到你面前。

我在设计时定了几条硬指标:全自动运行、不需要租服务器(普通电脑或低配NAS就能跑)、摘要生成要调用大模型接口以便保证质量、推送要能直接到微信或者Telegram。整个项目从代码到部署,大概控制在三到四百行核心代码以内,不引入重型框架,方便后续维护和改造。

1.2 技术选型的逻辑和取舍

技术栈选择上,我围绕“轻量、稳定、易改”三个原则来做取舍。

后端语言选了Python,原因是生态里做爬虫和数据处理最顺手,而且不管后续想接哪个大模型SDK,基本上都有现成包。调度这一块用APScheduler,而不是系统自带的crontab,这样程序内部就能管理定时任务,Windows和Linux通吃,不用额外配置系统计划任务。抓取用httpx库,支持异步和HTTP/2,比老牌的requests在现代站点兼容性上更好。解析HTML我用BeautifulSoup加lxml引擎,如果源站意外返回了网页而不是RSS,也能兜底提取正文。

存储这块我选了SQLite,很多人觉得它“玩具”,其实配合WAL模式,单机场景下性能完全够用,还省掉了数据库服务的安装维护。推送部分做成插件式结构,微信方面用Pushplus接口,Telegram走Bot API,邮件作为兜底,三个通道互相独立。摘要生成统一走OpenAI兼容格式的接口,这样哪天想换模型,只改配置不改代码。

1.3 目录结构与数据流设计

项目目录我建议按功能拆成模块,而不是把所有逻辑写在一个文件里。我的目录结构长这样:

newsnow/ ├── config.yaml ├── requirements.txt ├── main.py ├── core/ │ ├── fetcher.py │ ├── parser.py │ ├── deduplicator.py │ ├── summarizer.py │ └── notifier.py ├── data/ │ ├── newsnow.db │ └── logs/ └── tests/

数据流是一条单向管道:定时触发器唤醒采集器,采集器同时抓取多个订阅源,解析器把RSS里乱七八糟的格式统一成标准结构,去重模块用哈希表过滤掉重复信息,摘要器把长文压缩成两百字以内的要点,最后通知器把成品推出去。全程无人工介入,任何一步挂掉都不影响其他环节,下次触发会自动恢复。

这种分层设计最大的好处是调试方便。推送出问题了,单独测试notifier模块就行,不用把整套流程跑一遍。

2. 核心细节解析与实操要点

2.1 采集器的并发策略和请求参数

采集器是整个流水线的入口,这里的细节直接决定你能稳定抓到多少内容。并发我推荐用3到5个线程,对大多数个人使用场景足够了。别贪多,源站不是你家开的,并发太高容易被封IP,5个线程抓一百个源,实测大概两分钟能完成一轮,这个速度足够及时。

请求头伪装要认真做。UA字符串别用默认的Python标识,我观察了很多源站的反爬策略,它们第一道关卡就是看UA。构造一个尽量像真实Chrome浏览器的UA,同时加上Accept和Accept-Language头。但有一点要提醒:不要用同一套UA抓所有网站,最好给每个源配置独立的请求头,或者至少准备几套轮换。

超时参数我设的是连接5秒、读取10秒。RSS源大多是小站点,服务器性能参差不齐,给太长超时会拖慢整体节奏,太短又容易误判。重试策略我用的是指数退避,第一次失败等2秒,第二次4秒,最多重试3次。这里有个经验是:如果某个源连续7天都抓取失败,就自动在数据库里给它标记降级,后续抓取频率从30分钟拉长到6小时,避免反复对失效源发请求。

2.2 解析器对不同RSS格式的统一处理

RSS这潭水深得很,光格式就有RSS 2.0、Atom、RDF三种,不同的站点实现细节千奇百怪。有的把全文放在description里,有的放在content:encoded,还有的用Media RSS扩展字段。解析器的核心职责就是把这些差异抹平。解析这块用现成库。feedparser在Python社区扛了十几年了,兼容性和稳定性都经过充分验证,没必要自己造轮子。

从解析结果里我重点取三类数据:标题、链接、时间戳。时间戳要万分注意——RSS规范里的时间格式是RFC 822,但很多源站直接给ISO 8601格式,两者混在一起,不处理直接存数据库,后面做“近24小时新闻”筛选时就会漏掉内容。我的做法是写一个时间归一化函数,先尝试解析RFC 822,失败就退回ISO 8601,再不行就取当前时间兜底。

链接清洗也容易被忽略。有些站点会在RSS链接里带上追踪参数,比如?utm_source=rss。这些参数不影响打开文章,但会导致同一个内容的去重哈希对不上,明明同一篇文章却存了两遍。我在解析阶段就把查询参数里的常见追踪字段白名单删除,保留核心参数。

2.3 去重模块的思路与哈希策略

新闻领域“同源文章”特别多,同一件大事,不同媒体发的稿子有七八成内容重合。去重不能只比对URL完全一致,那样只能去掉转发链上的重复,拦不住不同媒体间的洗稿式转载。

我用的是双通道去重:第一道是内容指纹比对。把正文取前300字,清洗掉所有空白和标点,用MD5生成指纹,指纹一致就判定重复。这道门槛比较严,但能绝对保证不误伤。第二道是标题近似匹配,用编辑距离计算,相似度超过0.85就打上“疑似重复”标签。对第二类我没直接删掉,而是单独存放,等摘要生成后比较摘要相似度,再决定保留哪个版本。

这里有个容易踩的坑:哈希比对要在去停用词之前完成。如果你先清洗文本再算哈希,原文是“我国成功发射卫星”和“我国成功发射卫星”当然没问题,但碰到“我国成功发射了卫星”和“我国成功发射卫星”就差了一个字,MD5完全不同。我的解决方案是双轨并行:保留一个只做字符过滤的原文哈希,另算一个分词后的语义哈希,两者同时过一遍。

2.4 摘要生成时的prompt调优和成本控制

摘要模块是所有环节里最值得下功夫的地方。我最初的是简单prompt,让模型总结新闻要点,出来的结果经常带有模型自己的评论,甚至夹带情绪化表达。后来改成结构化prompt,把输出限制成固定的三段式:核心事实、关键数据、影响范围。实测下来,这种格式的可读性和信息密度提升非常明显。

我用的prompt模板大致是这样:

你是一名资深新闻编辑。请将以下新闻压缩为不超过200字的摘要,必须包含: 1. 发生的事件主体和核心事实 2. 关键数字或时间节点 3. 可能产生的影响范围 要求:只陈述客观事实,不做评价,不含预测。新闻原文如下: {content}

参数方面temperature我设为0.2,模型越是低随机性越不容易跑题。max_tokens控制在300到400之间,太长了成本高,太短了有时候三段式没写完。成本控制有个经验值:一天抓三百条新闻,约七八十条需要生成摘要,按tokens估算一天大约消耗15万token,一个月下来大概一杯咖啡的钱。如果觉得成本高,可以把摘要只在第一次入库时生成,已经存过的旧闻不重复调用。

另外一个细节:大模型对超长文本的处理有限制,正文超过5000字的文章,直接丢进去不仅浪费token,还可能截断关键信息。我做了前置切片:取开头2000字加结尾1000字,中间部分如果有小标题就拼接几个小标题,让模型获得文章骨架,这个“头尾+提纲”的组合效果实测比全量塞入更稳定。

3. 实操实录:从零搭建到跑通全流程

3.1 环境准备与依赖安装

开始动手前,先准备好Python 3.10以上的环境。我自己用的3.11,新语法特性支持完整,一些标准库的API也更顺手。建议用venv建独立虚拟环境,别直接装到系统Python里,不然过一阵子依赖冲突会让你怀疑人生。

依赖列表不长,核心就这几项:

fastapi==0.115.0 uvicorn[standard]==0.30.0 httpx==0.27.0 feedparser==6.0.11 beautifulsoup4==4.12.3 lxml==5.2.0 apscheduler==3.10.4 sqlalchemy==2.0.32 pyyaml==6.0.2 openai==1.35.0

版本号是我实测过的,不建议贪新直接升级大版本,尤其是openai和sqlalchemy,大版本间API变动比较多,教程里的代码可能直接跑不起来。

安装命令很简单:

pip install -r requirements.txt

安装过程中如果遇到lxml编译报错,多数是因为系统缺libxml2的头文件。Windows用户建议直接装预编译的whl包,Linux用户先执行apt install libxml2-dev libxslt1-dev再重装。

3.2 配置文件的完整解读

所有可调参数我统一放在config.yaml里,不写死在代码中。配置分四大块:订阅源、调度频率、大模型参数、推送渠道。下面贴一份带注释的配置,基本是我的实际配置脱敏后的版本:

feeds: - name: "36kr" url: "https://36kr.com/feed" category: "科技" priority: 1 - name: "少数派" url: "https://sspai.com/feed" category: "效率" priority: 2 schedule: interval_minutes: 30 initial_delay_seconds: 10 llm: api_base: "https://api.example.com/v1" api_key: "sk-xxxx" model: "gpt-4o-mini" temperature: 0.2 max_tokens: 400 notify: pushplus_token: "xxxx" telegram_bot_token: "xxxx" telegram_chat_id: "xxxx" email_smtp: "smtp.example.com" email_user: "user@example.com" email_pass: "xxxx" email_to: "receiver@example.com" dedup: similarity_threshold: 0.85 hash_length: 300 database: path: "./data/newsnow.db"

调度频率这个参数,我做过几轮实测。每10分钟跑一轮,能最早抓到突发新闻,但绝大部分源站10分钟内根本没有更新,白白浪费资源。每30分钟一轮,突发新闻损失最多二十分钟的窗口期,对绝大多数人来说完全能接受。如果你关注的是科技圈的发布会爆料,建议单独把高优先级的几个源设成10分钟轮询,其他源保持30分钟。APScheduler里可以同时挂多套触发器,按优先级分配不同的抓取间隔,这个思路在配置里就能实现。

3.3 采集与解析模块的代码实现

采集器我用httpx的异步客户端来实现并发请求。这里有个细节:复用同一个Client实例,而不是每篇请求都新建连接,能显著降低握手开销。我用自定义的fetch_all函数,把每个源的抓取任务扔进异步队列,限流用信号量控制。

import asyncio import httpx async def fetch_feed(client, feed): headers = {"User-Agent": random_ua(), "Accept": "application/rss+xml, application/xml, text/xml"} response = await client.get(feed["url"], headers=headers, timeout=10.0, follow_redirects=True) response.raise_for_status() return feed, response.text async def fetch_all(feeds, concurrency=5): async with httpx.AsyncClient(http2=True) as client: sem = asyncio.Semaphore(concurrency) async def bounded(feed): async with sem: return await fetch_feed(client, feed) results = await asyncio.gather(*[bounded(f) for f in feeds], return_exceptions=True) return results

解析器的核心是feedparser,但要把它的输出映射成统一结构。我定义了一个Article的dataclass,后面所有模块都基于这个结构工作,不再关心原始RSS长什么样:

from dataclasses import dataclass from datetime import datetime @dataclass class Article: guid: str title: str url: str summary: str content: str published_at: datetime source: str category: str

3.4 去重和摘要的串联逻辑

去重模块运行在入库之前,否则数据库里堆一堆重复记录,后面摘要也要重复生成。主流程里是这样一个顺序:抓取 → 解析 → 去重 → 过滤 → 入库 → 摘要 → 推送。入库之后就不再对原始内容做任何修改,摘要只作为独立字段追加。

去重的实现不算复杂,维护一张哈希表,键是MD5,值是入库时间。新文章进来先算哈希,在表里查得到就直接跳过,查不到就插入。MD5碰撞的概率在个人数据量级下可以忽略不计,没必要上SHA256自找麻烦。相似度阈值我调过几版,0.8的时候容易误杀,两个不同发布会可能因为文案风格相近就被折叠,0.9又放走不少转载。0.85是个平衡点,实测两百篇文章里误判大概两三篇,可接受。

摘要模块我封装成一个独立的函数,内部又把“判断是否需要摘要”和“调用模型”分成两步,避免对已摘要的文章重复调用。

def summarize_article(article, config): if article.summary is None: return None text = truncate_content(article.content) messages = [ {"role": "system", "content": "你是一名严谨的新闻编辑,只陈述事实。"}, {"role": "user", "content": PROMPT_TEMPLATE.format(content=text)} ] response = client.chat.completions.create( model=config["llm"]["model"], messages=messages, temperature=config["llm"]["temperature"], max_tokens=config["llm"]["max_tokens"] ) return response.choices[0].message.content

3.5 推送模块的三种通道对比

推送通道我做了三种,使用方法各不相同,对比一下:

通道接入成本送达速度适合场景
Pushplus最低,扫码关注即可拿token秒级个人微信接收
Telegram Bot需科学注册,但API最开放秒级重度用户自建频道
SMTP邮件需要邮箱授权码分钟级备份归档

我日常主力用的是Pushplus,效率最高,直接在微信里看摘要卡片,点一下能跳转原文。Telegram适合做频道型的公共资讯站,如果你建了频道还可以开放订阅。邮件推送这版我权重最低,只在Telegram或Pushplus连续失败时作为fallback触发。

推送模块的接口我统一抽象成send(title, content, url),三种通道各自实现这个接口,调用方不用关心底层细节。内容格式上有个细节:微信的链接预览抓取站点信息比较慢,我干脆把摘要正文和链接放在同一段文字里,避免推送卡片出现“无标题”的情况。

3.6 定时任务与主程序入口

主程序入口做的事情不多:读配置、初始化数据库、注册任务、启动调度器。真正干活的逻辑都在各个模块里,main.py只做编排。

定时任务我用的APScheduler的BackgroundScheduler。为什么不用cron?因为cron只能做到分钟级定时,我想让不同优先级源拥有不同频率,这需要代码级别的定时逻辑。APScheduler自带持久化作业存储,重启后不会丢失任务状态。

from apscheduler.schedulers.background import BackgroundScheduler def main(): config = load_config("config.yaml") init_db(config) scheduler = BackgroundScheduler(timezone="Asia/Shanghai") scheduler.add_job( run_pipeline, trigger="interval", minutes=config["schedule"]["interval_minutes"], id="news_pipeline", misfire_grace_time=60 ) scheduler.start() print(f"NewsNow已启动,每{config['schedule']['interval_minutes']}分钟抓取一轮") try: asyncio.get_event_loop().run_forever() except KeyboardInterrupt: scheduler.shutdown()

misfire_grace_time这个参数别忽视。假设调度器在凌晨三点触发任务,但电脑当时处于休眠状态,等早上开机的时,任务到底补跑还是跳过就有讲究。我设的是60秒,超过这个窗口的任务直接丢,避免补跑带来的重复推送。推送频率我已基本控制在每天十几条,阈值都用配置里的关键词黑白名单控制。

4. 常见问题与排查技巧实录

4.1 抓取源经常超时或返回空数据

这个问题几乎所有自建聚合项目都会遇到。RSS源站服务器稳定性参差不齐,我一度频繁从日志里看到超时错误。排查下来的结论很简单:单线程串行请求背锅了。你按顺序一个个请求三十个源,任何一个慢源都会堵住后面的请求。改成异步并发之后,整体耗时立刻降到一个可接受的范围。

但还有一个隐蔽因素:Redis缓存导致内容不变。某些源站对同一IP抓取返回304 Not Modified,此时RSS内容其实没变,不能再生成一条“新”数据。我在解析器里加了校验,只有状态码为200且内容体与上次不一致时才入库。如果你发现“一天只抓到三条,但源站明明更新了”,优先检查是不是被缓存了,可以让源站每次响应带一个随机缓存破坏参数。

4.2 摘要内容与原文不符或出现幻觉

这是我最头疼的问题,用过一阵子的大模型都躲不开“幻觉”。明明新闻里没提某个数字,摘要里凭空多出一个。我的应对策略是双保险。第一道在prompt里加一句“所有数据必须来自原文,任何原文未提及的信息都不得添加”,这个约束对大多数模型有效。第二道在代码里做程序校验:用正则把摘要里的数字全部抠出来,去原文里检查这些数字是否出现。如果超过三个数字在原文里找不到对应,摘要直接打回重生成,直到通过校验。

重生成也不是无限重试,我限制最多两次。两次都失败就放弃摘要,只推原文链接。记住:宁可推送格式不完美,也不能推送编造内容。

4.3 不同时区时间戳混乱的问题

如果你同时订阅了北美、欧洲、东亚的源站,时间戳混乱几乎是必然的。RSS时间格式里带了时区偏移,比如GMT+0800或者-0700,抓回来如果不统一归一化,排序就会错乱,欧洲的新闻可能会被排到亚洲新闻前面。我第二次重建项目时就踩了这个坑,当时按时间排序总是觉得哪里不对,调了半天才发现。

解决方案是统一转成UTC时间戳存库,显示时再按本地时区转换。数据库里不存带时区的字符串,只存Unix时间戳整数。排序、筛选都基于这个整数操作,彻底杜绝时区问题。

4.4 推送消息丢失或重复推送

推送丢失最常见的原因是消息体里带了特殊字符。某些新闻标题里会有引号、emoji或其他Unicode字符,推送到Telegram时如果没做转义,API会直接报错。我做了一个sanitize函数在推送前过滤掉控制字符和非法引用。Pushplus那边丢消息则多是因为链接里的某些字符触发了平台的过滤,对策是统一对URL做百分号编码。

重复推送一般是程序崩溃重启导致任务重复执行。APScheduler默认设置了misfire逻辑已经处理了一部分,但如果Redis或数据库锁没配置,原生调度器并无法完全防重复。我加的方案是:在推送表里给(article_guid, channel)建唯一索引,同一条新闻对同一通道只允许推一次,数据库层面兜底。

4.5 程序长时间运行后内存占用持续上升

运行三五天后内存越吃越高,最后干脆卡死。这个问题排查起来不难,用tracemalloc很快锁定位置:feedparser解析大体积RSS时,在内部缓存了完整DOM树,调用方拿到结果后不会主动释放。标准的解决方法是解析函数内部用完后立即del d,并调用gc.collect()强制回收。但更治本的是限制单次解析的源数量,分批处理,每批结束打一个日志标记释放状态。

另外httpx的AsyncClient如果反复创建而不关闭,文件描述符也会泄漏,几万次请求之后跑满限制导致报错。用async with严格管理生命周期,不要图省事把Client挂成全局变量。

5. 进阶玩法与优化建议

5.1 关键词过滤和个性化打分

跑通基础流程后,我加了简单的关键词过滤机制。用正则匹配标题和摘要,命中关键词的新闻直接标记为“重点”,推送到微信时在标题前加[重点]前缀。这个功能特别适合内容运营盯行业动态,比如你是做AI方向的内容,预设“大模型”“AIGC”“智能体”这些词,系统每天自动把相关新闻挑出来,节省大量人工筛选时间。

打分逻辑也可以继续细化。我目前以一个三要素加权公式:时效性权重0.4、来源站点权重0.3、关键词命中数权重0.3。每一项归一化到0到1之间,综合得分超过0.6的才推送。公式不复杂,但你可以根据自己的领域调整权重参数。注意推送别太勤,微信轰炸式推送一定会被你自己关掉。

5.2 双模型摘要策略

大模型摘要的质量在不同场景下差异不小。头条级别的短讯,用轻量廉价模型就能搞定;深度长文分析,摘要难度大,就得上推理能力强的新模型。我后来加了一层路由逻辑:文章字数少于2000的走轻量模型,超过2000或者标题含“深度”“解读”“报告”字样的,走更贵的模型。这样在质量和成本之间找到了一个相对最优的平衡点。粗算下来一天的总成本比全量大模型方案低了将近六成,摘要在关键评测场景上的可用性反而更高了。

5.3 数据报表与趋势统计

运行一段时间后,数据库里攒下来的历史新闻其实是个不错的分析素材。我把SQLite数据用Pandas定期汇总,输出一份每日报告:今天推送了几条、命中哪些关键词、哪些信源贡献最多、整体更新量相比前一周是涨是跌。这份报告每周发一封邮件给自己。数量不大但不定期看几眼,能帮你判断订阅源需不需要清理。如果一个信源连续两周贡献量为零,基本可以降级或剔除。

另外可以做的还有简单的时序分析,比如关键词在你的订阅源里出现频率的趋势。这个比任何行业资讯平台的“热点榜”都更贴合你自己的信息需求,因为它是你自定义信源池的统计结果,不受平台算法引导。

5.4 懒人部署方案:Docker化

我不建议一上来就上Docker,调试阶段频繁改代码重建镜像太消耗耐心。但跑通之后,Docker能帮你彻底解决环境问题,尤其是换设备部署时,一条命令拉起整个服务,不用再装Python依赖。我写了对应的Dockerfile,构建命令很简单。

Docker部署时要注意数据卷挂载。SQLite数据库文件如果在容器里存储,容器重建数据就全丢了,必须在宿主机挂载data目录。我用的挂载命令大致如下:

docker build -t newsnow . docker run -d --name newsnow \ -v ./data:/app/data \ -v ./config.yaml:/app/config.yaml \ --restart unless-stopped \ newsnow

restart unless-stopped保证服务器重启后服务自动拉起来,不用手动干预。日志默认打到stdout,用docker logs newsnow随时查看排障。

6. 写在最后的几点实在话

新闻聚合这个方向,工具很多,但“符合自己口味”的很少。我前后试过各类现成的阅读器和资讯App,要么信息噪声太大,要么算法推荐看不懂,总觉得不受控制。自己搭NewsNow之后,最大的变化倒不是推送的几条新闻,而是每天打开手机知道哪些事情值得看,哪些不值得看,信息焦虑缓解了不少。

实操中有两件事我想再强调一下。第一,任何抓取行为都要有分寸感,礼貌一点设置合理的间隔和超时,尊重源站资源,你的IP也才活得久。第二,大模型摘要永远只能做辅助,发布任何对外内容前,关键信息务必回到原文确认。你要是把新闻聚合结果发到有读者的工作群里,摘要里一个数字编错了,丢的可是你自己的信用。

如果后续想做更多扩展,可以考虑给NewsNow加一个简单的Web界面,把当天推送的新闻列表展示出来,方便工作上查阅历史记录。也可以接入语音合成,让摘要变成播报音频,通勤时候听。这个项目的天花板不在技术,在于你能从信息流里发掘出多少实际的用处。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询