一、为什么选择 RSS + 爬虫的混合架构
在信息碎片化的今天,搭建一个属于自己的新闻聚合器,是解决信息过载、高效获取行业动态的经典方案。而在采集层的技术选型上,单一方案往往存在明显短板:
- RSS 订阅:标准化、低开销、合规性好,是正规媒体的首选采集方式,但大量新媒体、垂直站点并不提供 RSS 源,且 RSS 输出内容常被截断,无法获取全文。
- 纯爬虫采集:覆盖范围广,可抓取任意公开网页,但开发维护成本高,容易触发反爬,且站点结构变动就会导致解析失效,长期稳定性差。
混合采集方案正是结合两者优势:优先使用 RSS 完成主流信源的稳定采集,用爬虫补充无 RSS 站点的内容抓取,再通过统一的清洗、去重、存储层打通数据链路,兼顾了稳定性、覆盖率和维护成本。本文将从零开始,完整讲解这套方案的设计与落地。
二、整体架构设计
整套聚合器采用分层架构,从下到上分为采集层、处理层、存储层、服务层与展示层,核心数据流如下:
- 采集层:RSS 采集器 + 通用爬虫 + 站点专属爬虫,并行执行采集任务
- 调度层:统一任务调度,控制采集频率,处理失败重试,避免重复采集
- 处理层:HTML 正文提取、内容清洗、结构化标准化、相似度去重、标签分类
- 存储层:元数据存关系型数据库,正文存文档数据库,URL 去重存缓存数据库
- 服务层:RESTful API 提供内容查询、订阅管理、分类筛选能力
- 展示层:Web 前端 / RSS 输出 / 移动端适配,实现内容阅读与管理
核心技术栈选型
- 开发语言:Python(生态完善,RSS 解析与爬虫库成熟)
- RSS 解析:feedparser
- 爬虫框架:Requests + BeautifulSoup(轻量场景)、Scrapy(大规模站点)、Playwright(动态渲染页面)
- 任务调度:APScheduler(单机)、Celery + Redis(分布式)
- 数据存储:SQLite/PostgreSQL(元数据)、MongoDB(正文内容)、Redis(URL 去重与缓存)
- 正文提取:trafilatura(通用全文提取)
- 前端:Vue3 + Vite(轻量自建)或直接使用 TinyTinyRSS / Miniflux 等开源前端
三、RSS 采集模块:稳定采集的基石
RSS 是整个系统的首选采集通道,开发成本极低,且几乎不会触发反爬策略,适合所有提供标准订阅源的站点。
3.1 RSS 源的获取与管理
首先需要维护一份订阅源清单,推荐用数据库表存储,核心字段包括:
- 源 ID、站点名称、RSS 链接、站点主页
- 采集频率(默认 30 分钟,可按站点调整)
- 上次采集时间、上次采集状态、失败次数
- 内容优先级、分类标签、是否启用全文抓取
3.2 基础 RSS 解析实现
使用feedparser可以一行代码完成 RSS/Atom 格式的兼容解析,无需手动处理 XML 结构差异:
python
运行
import feedparser from datetime import datetime def fetch_rss_feed(feed_url: str) -> list: """解析单个 RSS 源,返回标准化的文章列表""" feed = feedparser.parse(feed_url) articles = [] for entry in feed.entries: article = { "title": entry.get("title", "").strip(), "url": entry.get("link", ""), "summary": entry.get("summary", ""), "author": entry.get("author", ""), "published": parse_publish_time(entry), "source": feed.feed.get("title", ""), "fetch_type": "rss" } articles.append(article) return articles def parse_publish_time(entry) -> datetime: """兼容多种 RSS 时间格式,统一转为 datetime""" for time_field in ["published_parsed", "updated_parsed", "created_parsed"]: if hasattr(entry, time_field): time_tuple = getattr(entry, time_field) return datetime(*time_tuple[:6]) return datetime.now()3.3 RSS 采集的常见坑与处理
- 编码异常:部分老旧站点 RSS 编码不规范,需先手动指定编码再解析
- 订阅源失效:设置失败次数阈值,连续失败 5 次自动降级采集频率并告警
- 内容截断:RSS 只输出摘要时,标记为 "需全文抓取",交给后续爬虫模块补全正文
- 增量采集:记录上次采集的最新文章发布时间,下次只拉取更新内容,减少冗余解析
四、爬虫采集模块:覆盖无 RSS 站点
对于不提供 RSS 的站点,爬虫是必要的补充。我们将爬虫分为两类:通用列表页爬虫适配大多数资讯站,专属站点爬虫针对结构特殊的高优先级站点定制。
4.1 通用列表页爬虫设计
绝大多数资讯站点都遵循 "列表页 + 详情页" 的结构,通用爬虫只需要配置列表页 URL、文章链接选择器、翻页规则,即可完成基础采集:
python
运行
import requests from bs4 import BeautifulSoup from urllib.parse import urljoin def fetch_list_page(list_url: str, link_selector: str, base_url: str) -> list: """从列表页提取所有文章详情页链接""" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"} resp = requests.get(list_url, headers=headers, timeout=10) resp.encoding = resp.apparent_encoding soup = BeautifulSoup(resp.text, "html.parser") links = [] for a_tag in soup.select(link_selector): href = a_tag.get("href", "") full_url = urljoin(base_url, href) links.append(full_url) return list(set(links))4.2 正文提取:通用方案优先,定制规则兜底
详情页的正文解析是爬虫的核心难点,推荐优先使用通用正文提取库trafilatura,它能自动识别页面正文区域,过滤广告、导航、评论等噪声,准确率远超手写选择器:
python
运行
import trafilatura def extract_article_content(html: str) -> dict: """通用正文提取,返回正文文本与元数据""" result = trafilatura.bare_extraction(html, include_links=False, include_images=False) return { "content": result.get("text", ""), "title": result.get("title", ""), "author": result.get("author", ""), "date": result.get("date", "") }对于通用提取效果差的站点,再单独编写 CSS 选择器规则,配置在站点规则表中,形成 "通用 + 定制" 的双轨解析机制。
4.3 反爬应对策略
- 请求频率控制:单站点请求间隔不低于 3 秒,随机抖动 0.5-1 秒,避免触发频率限制
- User-Agent 池:维护 10+ 浏览器 UA,每次请求随机切换
- 代理池接入:针对强反爬站点,接入代理 IP 池,降低封禁风险
- 动态页面处理:JavaScript 渲染的页面使用 Playwright 无头浏览器加载,再提取渲染后的 HTML
- 重试机制:网络异常、5xx 错误自动重试 3 次,指数退避;403、429 直接降级暂停采集
五、混合调度与去重:避免重复与混乱
两套采集体系并行,最核心的问题是重复内容采集和任务冲突,需要统一的调度层来管理。
5.1 统一任务调度
使用 APScheduler 管理所有采集任务,按站点维度配置独立的采集周期:
- 主流新闻站点:15-30 分钟采集一次
- 垂直博客、小众站点:2-6 小时采集一次
- 更新频率极低的站点:每日采集一次
调度器只负责任务触发,实际采集逻辑提交到线程池 / 进程池执行,避免单任务阻塞整个调度队列。
5.2 URL 级去重方案
去重是聚合器的核心能力,采用 "缓存快速判断 + 数据库最终校验" 的两级方案:
第一级:Redis 布隆过滤器对文章 URL 做哈希后存入布隆过滤器,采集前先查询,存在则直接跳过,内存占用极低,查询速度毫秒级,适合拦截绝大多数重复请求。
第二级:数据库唯一索引对文章 URL 建立唯一索引,即使布隆过滤器出现误判,最终写入数据库时也会触发唯一约束冲突,保证数据不重复。
5.3 内容级去重补充
同一篇新闻常被多个站点转载,仅靠 URL 去重无法识别。可以对正文标题 + 前 200 字计算 SimHash 指纹,存入数据库,新文章入库前比对指纹,汉明距离小于 3 则判定为重复内容,合并到同一主题下。
六、数据清洗与标准化
两套采集链路输出的数据格式差异很大,必须经过统一的标准化处理,才能保证后续存储和展示的一致性。
6.1 字段标准化
所有采集到的内容,最终都统一为以下标准结构:
表格
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | string | 文章唯一 ID(URL 哈希生成) |
| title | string | 文章标题,去除首尾空白与特殊字符 |
| url | string | 原文链接,标准化为完整绝对路径 |
| source | string | 来源站点名称 |
| author | string | 作者,无则留空 |
| summary | string | 摘要,RSS 摘要或正文前 150 字 |
| content | string | 纯文本正文,去除 HTML 标签与广告 |
| content_html | string | 带格式的 HTML 正文(可选) |
| published_at | datetime | 原文发布时间 |
| fetched_at | datetime | 采集时间 |
| tags | array | 分类标签 |
| fetch_type | string | 采集类型:rss /crawler |
6.2 内容清洗规则
- 去除正文内的广告代码、推广文案、相关推荐、版权声明
- 统一 HTML 标签,过滤 script、style、iframe 等危险标签
- 修复相对路径的图片链接,转为绝对路径
- 去除多余空白字符、换行符,统一段落格式
- 敏感词过滤与内容安全校验(面向公网服务必须添加)
七、存储方案选型
根据数据特性分层存储,兼顾查询性能与存储成本。
7.1 元数据存储:PostgreSQL
文章标题、URL、发布时间、来源、标签等结构化元数据存入关系型数据库,便于做多条件筛选、排序、统计。核心建立三个索引:
- 发布时间倒序索引(列表页查询核心)
- 来源站点索引(按站点筛选)
- URL 唯一索引(去重兜底)
7.2 正文内容存储:MongoDB
正文属于长文本非结构化数据,且不同站点字段差异大,存入 MongoDB 更灵活,通过文章 ID 与 PostgreSQL 元数据关联。全文检索需求可以直接使用 MongoDB 的文本索引,中小规模场景足够使用。
7.3 缓存与去重:Redis
- 布隆过滤器存 URL 去重指纹
- 缓存热点文章内容,减轻数据库压力
- 存储采集任务状态、失败计数等运行时数据
八、服务层与前端展示
采集处理完成后,需要一套接口与界面实现内容的消费。
8.1 API 服务
用 FastAPI 快速搭建 RESTful 接口,核心接口包括:
GET /articles:分页获取文章列表,支持按来源、标签、时间范围筛选GET /articles/{id}:获取文章详情GET /feeds:获取订阅源列表POST /feeds:新增订阅源GET /categories:获取分类标签
同时可以输出标准 RSS 格式,支持用其他 RSS 阅读器直接订阅你的聚合器。
8.2 前端实现
如果不想从零开发前端,可以直接对接开源 RSS 阅读器的后端,或者用极简方案:
- 纯静态页面:使用 Vue3 + Element Plus,实现列表 + 详情的双栏布局
- 阅读体验优化:支持夜间模式、字体大小调整、全文搜索
- 订阅管理:可视化新增、编辑、删除订阅源,查看采集状态
九、部署与运维
9.1 单机部署方案
个人使用场景下,单机部署完全足够,推荐使用 Docker Compose 一键编排:
- 应用容器:Python 采集服务 + FastAPI 服务
- 数据库容器:PostgreSQL + MongoDB + Redis
- 反向代理:Nginx 处理静态资源与接口转发
9.2 运维监控要点
- 采集成功率监控:统计每个站点的采集成功率,低于 80% 自动告警
- 数据量统计:每日新增文章数、重复内容占比、各来源贡献占比
- 异常告警:站点连续失败、服务宕机、磁盘占用过高时推送通知
- 定期备份:每日备份数据库,避免订阅源配置与数据丢失
十、进阶优化方向
基础版本跑通后,可以根据需求逐步扩展能力:
- 智能分类与推荐:接入大语言模型对文章自动打标签、做摘要,基于阅读习惯做个性化推荐
- 全文检索升级:引入 Elasticsearch 实现毫秒级全文搜索,支持复杂关键词组合查询
- 分布式扩展:采集节点横向扩容,用消息队列解耦采集、处理、存储流程,支撑上万订阅源
- 多端同步:接入 Readwise、Instapaper 等稍后读服务,支持移动端同步阅读进度
- 订阅源自动发现:输入站点主页自动识别 RSS 地址,降低手动配置成本
十一、总结
RSS + 爬虫的混合采集方案,本质是用 20% 的成本解决 80% 的采集需求:RSS 负责主力信源的稳定高效采集,爬虫负责补全长尾站点的覆盖,再通过统一的调度、去重、清洗层打通数据闭环。
对于个人使用者,这套方案从零搭建只需要一周左右的开发量,就能获得一个完全可控、无广告、定制化的信息聚合入口。对于企业级场景,也可以在此架构基础上扩展分布式能力与智能处理模块,支撑更大规模的资讯采集与分析需求。