11 KiB
Source Schema 设计草案
1. 文档目的
本文档用于定义阅读流系统中的来源与内容对象模型,目标是把“来源分类”的讨论收敛成一套可执行的数据结构,供后续的抓取、摘要、过滤、入库和推送流程统一使用。
这份文档关注的是数据抽象,而不是具体实现语言、数据库模型或 API 细节。
2. 设计目标
这套 schema 需要解决以下问题:
- 不同渠道的内容如何进入同一条流水线
- RSS、微信文章、普通网页、GitHub 更新如何被统一表示
- 摘要器应该接收什么输入对象
- 过滤规则应该依赖哪些标准字段
- 入库与推送如何复用统一元信息
核心目标是建立稳定的中间层,避免后续各模块直接依赖渠道差异。
3. 设计原则
3.1 分层而不是混合
建议将系统中的对象分成三层:
sourceitemdocument
每一层职责不同:
source:描述一个来源渠道本身item:描述一次抓取到的候选内容document:描述经过正文抽取和摘要后的标准内容对象
3.2 先标准化,再智能化
在进入摘要、过滤、入库前,应该先把不同渠道的内容统一映射成相同的对象结构,而不是在每个处理阶段临时兼容各种来源格式。
3.3 渠道类型与内容形态分离
建议明确区分:
- 来源渠道是什么
- 内容本身是什么形态
例如:
- 微信文章和 RSS 博文可能都属于
article - GitHub Release 和 changelog 可能来自不同渠道,但内容形态更接近事件流
4. 对象分层
4.1 source
source 是来源配置对象,用来描述某个渠道本身是什么、如何接入、是否启用。
特点:
- 偏静态
- 由用户或系统配置维护
- 不代表具体某一篇内容
4.2 item
item 是采集层产物,用来表示“这次抓回来的一个候选条目”。
特点:
- 来自 RSS、API、HTML 抓取或转换 feed
- 可能只有标题、链接和摘要
- 不保证已经拿到完整正文
4.3 document
document 是标准内容对象,用来表示“已经过正文抽取和摘要处理,可以进入过滤、入库和推送阶段”。
特点:
- 已经完成正文抽取
- 已经有结构化摘要
- 是后续规则引擎的主输入
5. 枚举设计
5.1 source_type
表示来源类型。
可选值:
feedconverted_feedevent_feedpage_watchcustom_source
说明:
feed:原生 RSS / Atomconverted_feed:由 RSSHub、wewe-rss、RSS-Bridge 等转换而来event_feed:更像事件流,例如 GitHub Releases、Commits、Discussionspage_watch:轮询普通网页的更新情况custom_source:为特殊站点定制的抓取源
5.2 ingest_mode
表示接入方式。
可选值:
feed_directfeed_convertedapi_backedhtml_scrapedhybrid
说明:
feed_direct:直接消费 RSS / Atomfeed_converted:先转换成 feed,再统一消费api_backed:通过 API 获取数据html_scraped:直接抓 HTML 页面并解析hybrid:先消费 feed 元数据,再按需抓正文
5.3 content_kind
表示内容形态。
可选值:
articlethreadreleasechangelogvideomixed
说明:
article:标准文章thread:串联式内容,例如帖子串、社交媒体长线程release:版本发布说明changelog:更新日志video:视频内容mixed:无法明确归入单一形态
5.4 fetch_state
表示候选内容的正文抓取状态。
可选值:
pendingfetchedfailedskipped
5.5 pipeline_state
表示内容在整条流水线中的处理状态。
可选值:
ingestedsummarizedfilteredstoredpusheddropped
6. source 对象设计
6.1 角色定义
source 用于描述一个来源本身,而不是某条具体内容。
它应该回答这些问题:
- 这个来源是什么
- 它属于哪一类来源
- 用什么方式接入
- 是否启用
- 默认语言和默认内容形态是什么
6.2 建议字段
{
"source_id": "wechat-aiweekly",
"name": "AI Weekly 微信源",
"source_type": "converted_feed",
"ingest_mode": "feed_converted",
"content_kind": "article",
"base_url": "https://example.com",
"feed_url": "https://example.com/feed.xml",
"language": "zh",
"enabled": true,
"tags": ["ai", "newsletter"],
"priority": 80
}
6.3 字段说明
-
source_id- 系统内部唯一标识
-
name- 人类可读名称
-
source_type- 来源类型
-
ingest_mode- 接入方式
-
content_kind- 默认内容形态
-
base_url- 来源站点主域名
-
feed_url- feed 地址;没有则可为空
-
language- 默认语言
-
enabled- 是否启用该来源
-
tags- 来源级标签,供过滤和统计使用
-
priority- 用于抓取调度、结果排序或推送优先级控制
6.4 最小可用版本
{
"source_id": "my-blog",
"source_type": "feed",
"ingest_mode": "feed_direct",
"feed_url": "https://example.com/rss.xml",
"enabled": true
}
7. item 对象设计
7.1 角色定义
item 是采集层的标准对象,用来统一表示从不同渠道抓回来的候选内容。
它应该回答这些问题:
- 这条候选内容来自哪个 source
- 它的标题、链接和发布时间是什么
- RSS 是否已经提供摘要或正文
- 当前是否已抓到完整正文
7.2 建议字段
{
"item_id": "sha256:xxx",
"source_id": "wechat-aiweekly",
"external_id": "feed-entry-id-or-url",
"title": "文章标题",
"url": "https://example.com/post/123",
"author": "作者",
"published_at": "2026-03-23T08:00:00Z",
"discovered_at": "2026-03-23T10:00:00Z",
"content_kind": "article",
"language": "zh",
"raw_summary": "RSS 提供的摘要",
"raw_content": "RSS 提供的正文或片段",
"metadata": {
"feed_title": "源标题",
"categories": ["AI", "Tools"]
},
"fetch_state": "pending"
}
7.3 字段说明
-
item_id- 内部唯一标识
- 建议由
source_id + canonical_url + published_at做 hash 生成
-
source_id- 指向来源配置对象
-
external_id- 来源平台本身的 id,例如 RSS entry id、原始 URL 或平台内容 id
-
title- 候选内容标题
-
url- 原始内容链接
-
author- 作者信息;如果无法获取可为空
-
published_at- 原始发布时间
-
discovered_at- 被系统发现的时间
-
content_kind- 当前条目的内容形态
-
language- 当前条目的语言
-
raw_summary- 来源提供的摘要内容
-
raw_content- 来源直接提供的正文、正文片段或富文本转纯文本结果
-
metadata- 保留来源特有元信息,例如 feed 分类、栏目名、标签
-
fetch_state- 正文抓取状态
7.4 最小可用版本
{
"item_id": "sha256:xxx",
"source_id": "my-blog",
"title": "文章标题",
"url": "https://example.com/post/1",
"published_at": "2026-03-23T08:00:00Z",
"raw_summary": "摘要",
"raw_content": "",
"fetch_state": "pending"
}
8. document 对象设计
8.1 角色定义
document 是经过正文抽取和摘要处理后的标准内容对象。
它应该回答这些问题:
- 最终正文是什么
- 摘要和高亮点是什么
- 这个内容值不值得进入下一步过滤或入库
- 当前它处于流水线哪个阶段
8.2 建议字段
{
"document_id": "sha256:yyy",
"item_id": "sha256:xxx",
"source_id": "wechat-aiweekly",
"url": "https://example.com/post/123",
"title": "文章标题",
"author": "作者",
"published_at": "2026-03-23T08:00:00Z",
"language": "zh",
"content_kind": "article",
"plain_text": "抽取后的正文",
"summary": "3-5句摘要",
"highlights": ["要点1", "要点2", "要点3"],
"keywords": ["rss", "mcp"],
"topics": ["workflow", "knowledge-base"],
"scores": {
"relevance": 0.91,
"novelty": 0.72,
"readability": 0.84
},
"quality_flags": {
"is_paywalled": false,
"is_truncated": false,
"is_low_content": false
},
"pipeline_state": "summarized"
}
8.3 字段说明
-
document_iddocument的唯一标识
-
item_id- 回溯到原始候选条目
-
source_id- 回溯到来源配置
-
url- 原始链接
-
title- 标题
-
author- 作者信息
-
published_at- 发布时间
-
language- 语言
-
content_kind- 内容形态
-
plain_text- 抽取后的正文纯文本
-
summary- 结构化摘要中的主摘要
-
highlights- 关键要点
-
keywords- 关键词
-
topics- 主题标签
-
scores- 打分结果,例如相关性、新颖度、可读性
-
quality_flags- 内容质量标志,例如是否付费墙、是否截断、是否低信息密度
-
pipeline_state- 当前流水线状态
8.4 最小可用版本
{
"document_id": "sha256:yyy",
"item_id": "sha256:xxx",
"title": "文章标题",
"url": "https://example.com/post/1",
"plain_text": "正文",
"summary": "3-5句摘要",
"highlights": ["点1", "点2"],
"scores": {
"relevance": 0.9
},
"pipeline_state": "summarized"
}
9. 三层对象之间的关系
建议关系如下:
- 一个
source可以产生多个item - 一个
item在多数情况下对应一个document document是item经过正文抽取和摘要处理后的结果
简化理解:
source解决“从哪里来”item解决“抓到了什么”document解决“它值不值得留下”
10. 为什么必须分层
如果不区分 source、item 和 document,后面会出现这些问题:
- 来源配置和运行时数据混在一起
- RSS 字段、网页抓取字段和摘要字段混在一起
- 过滤规则既要判断来源,又要判断内容质量,还要判断摘要分数
- 不同来源的差异会不断渗透到下游模块
分层后的收益是:
- 采集层可以独立演进
- 摘要器输入更稳定
- 过滤器只依赖统一对象,不依赖来源细节
- 入库和推送更容易复用
11. 当前阶段的最小实现建议
为了降低复杂度,第一阶段建议:
- 只支持
feed和converted_feed - 只实现
source、item、document这三层基础对象 item只要求能容纳 RSS 拉回来的标题、链接、摘要、正文片段document只要求能容纳正文、摘要、高亮点和基础打分
这样做的好处是:
- 结构足够稳定
- 成本足够低
- 可以直接支撑下一步
summary-core设计
12. 后续可继续讨论的问题
- 是否需要给
source增加抓取周期、超时、重试策略字段 item_id和document_id的生成策略是否要统一metadata是否需要按来源类型拆出专用字段document的scores和quality_flags是否要定义更严格的 schema- 这套 schema 最终是落成 JSON 文件、数据库表,还是 Pydantic 模型
13. 当前阶段的一句话结论
当前最合适的做法是先固定 source -> item -> document 这三层对象模型,再围绕它设计 summary-core、过滤器和入库流程。