Karakeep(Hoarder)RSS 集成完全指南:将列表发布为订阅源,并自动抓取外部 Feed 生成书签
2026/9/10 13:21:52 网站建设 项目流程

Karakeep(Hoarder)RSS 集成完全指南:将列表发布为订阅源,并自动抓取外部 Feed 生成书签

【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder

本篇技术指南聚焦开源项目 Karakeep(原 Hoarder)的 RSS 集成能力,涵盖两大方向:一是把任意列表(List)发布为带访问令牌保护的 RSS 订阅源,供 RSS 阅读器或他人订阅;二是监控外部 RSS/Atom 源,每小时自动抓取新条目并生成书签、可选导入分类标签。读完本文,你将掌握完整的界面操作步骤、URL 与令牌机制、字段映射规则,以及背后的 Worker 调度、去重、配额检查等源码级实现原理。

RSS 集成总览:发布与消费的双向能力

Karakeep 的 RSS 集成是双向的,对应两条独立的技术链路:

  • 发布(Publishing):将你创建的任意列表发布为 RSS Feed,其他人或 RSS 阅读器可以通过一个带 token 的 URL 订阅你的书签集合;
  • 消费(Consuming):在设置中添加外部 RSS/Atom 源,后台 Worker 会定时抓取,把新条目自动保存为书签,适合跟踪博客、新闻站点等内容源。

这两条链路在代码中分属不同的模块:发布侧位于 API 层的 rss.ts 与 utils/rss.ts,消费侧则由独立的 Feed Worker 驱动(feedWorker.ts)。

将列表发布为 RSS 订阅源

启用 RSS 的界面操作

按官方文档的步骤,进入任意列表后:

  1. 导航到你的某个列表;
  2. 点击列表设置(三点菜单);
  3. 打开RSS Feed开关;
  4. 复制生成的 RSS Feed URL。

从源码看,开关背后对应列表在数据库中的rssToken字段。在 lists.ts 中,token 的生成与读取由List.getOrCreateRSSKey/getRSSKey实现,每次重新生成会写入新的 token 值,从而让旧 URL 立即失效。

Feed URL 的结构与参数

生成的 Feed URL 形如:

https://<你的实例地址>/v1/rss/lists/<listId>?token=<访问令牌>

发布端点定义在 rss.ts,路由为GET /lists/:listId,支持两个查询参数:

参数类型说明
tokenstring(可选)访问令牌。列表未公开时必须提供,且必须与数据库中的rssToken完全一致
limitnumber(可选)返回条数,范围1MAX_NUM_BOOKMARKS_PER_PAGE,默认20,按创建时间倒序(order: "desc")返回

响应头为Content-Type: application/rss+xml,返回标准的 RSS 2.0 XML。Feed 的标题会自动带上列表图标与名称,例如Bookmarks from 📚 我的收藏feedUrl指向 API 端点,siteUrl指向 Web 端的列表页面(/dashboard/lists/<listId>)。

RSS 中发布的内容与字段映射

文档明确了 Feed 中包含的内容类型,其具体映射逻辑在 utils/rss.ts 中实现:

书签内容是否包含RSS 字段映射
链接(Link)url取书签 URL,author取页面作者,description取书签描述,title取书签标题
资产(Asset)url指向资产的查看地址,由publicUrl + getAssetUrl(assetId)拼接,用于查看 PDF、图片等上传文件
标签(Tags)作为 RSS 的<category>分类元素导出
日期书签创建时间createdAt作为<pubDate>发布日期
纯文本笔记(Note)不包含——笔记没有关联 URL,无法作为链接条目导出

每个条目的<guid>使用书签 ID,确保条目在阅读器中的唯一性与稳定更新。同时注意一个安全细节:在生成条目之前,代码会过滤掉javascript:data:等不安全协议的历史链接(通过isAllowedBookmarkUrl校验),防止阅读器跟踪恶意 URL。

安全模型:令牌、重新生成与即时失效

  • 唯一令牌访问:每个 Feed 需要唯一的 token 才能访问,对应数据库中的rssToken字段;
  • 随时重新生成:重新生成 token 会覆盖旧值,旧 URL 立即失效;
  • 关闭即撤销:禁用列表的 RSS 开关后,rssToken不再有效,访问即被拒绝。

在 lists.ts 的getPublicList查询中可以印证访问控制逻辑:列表可被访问的条件是「列表本身标记为公开(public = true)」「传入的 token 与rssToken相等」。也就是说,只要列表启用了公开访问或持有有效 token,就能读取内容。

订阅并自动抓取外部 RSS 源

添加 Feed 的界面步骤与字段

文档中的操作路径为设置(Settings)→ RSS Feeds → Add Feed,需要填写的字段如下:

  • Name:Feed 的友好名称;
  • URL:RSS/Atom 源的地址;
  • Enabled:是否启用该 Feed;
  • Import Tags:是否把 RSS 分类(categories)作为标签附加到生成的书签上。

这些字段与 feeds.ts 中的 zod 校验约束一一对应,底层约束值得注意:

字段类型校验约束
namestring必填,长度1–100
urlstring必填,必须是合法 URL,最长2000字符
enabledboolean是否启用
importTagsboolean默认false,仅新建时可设置

每小时自动检查的调度机制

文档说明 Karakeep每隔一小时检查启用的 Feed。源码中的实现比「整点扫描」更精细:在 feedWorker.ts 中,一个 cron 表达式为0 * * * *的调度器在每小时的第 0 分钟运行,它先查询所有enabled = true的 Feed,然后通过一个基于 Feed ID 哈希的getFeedMinuteOffset函数,把每个 Feed 映射到当前小时的某个分钟偏移(0–59),再以对应延迟将抓取任务入队。

这样做的好处是把大量 Feed 的抓取均匀分散在一小时内的各个时间点,避免整点并发风暴。每个任务还带有一个idempotencyKey(格式为<feedId>-<小时窗口>),保证同一小时内同一 Feed 不会被重复调度。

新条目去重与书签创建流程

在 feedWorker.ts 的run函数中,抓取流程包含完整的状态机:

  1. 配额预检:通过QuotaService.canCreateBookmark检查用户是否还有书签配额,配额不足时直接跳过本轮抓取(返回success,不算失败);
  2. 网络抓取:使用fetchWithProxy请求 Feed URL,携带 5 秒超时、User-Agent: Karakeep-RSS/1.0与 XML 相关的 Accept 头;
  3. 响应校验:HTTP 状态必须为 200,且Content-Type必须包含xml,否则记录failure并等待下一轮;
  4. 解析条目:通过parseFeedItems(基于rss-parser,feedParser.ts)把 XML 解析为条目列表,每个条目的guidguid ?? id ?? link的优先级推导;
  5. 去重:查询rssFeedImportsTable中该 Feed 已导入的entryId,只保留既无guid重复、又具备linkguid的新条目;
  6. 批量创建:通过模拟用户身份的 tRPC 客户端(buildImpersonatingTRPCClient)调用bookmarks.createBookmark,类型固定为 LINK,source标记为"rss",便于溯源;
  7. 标签导入:若启用了importTags,且条目带有 categories,则通过bookmarks.updateTags把每个分类作为标签附加到对应书签;
  8. 记录导入映射:把entryId ↔ bookmarkId的映射写入rssFeedImportsTableonConflictDoNothing幂等),作为后续去重的依据。

抓取完成后,Feed 记录会更新lastSuccessfulFetchAt时间戳,任务结果(success/failure)会写入lastFetchedStatuslastFetchedAt——这正是设置页面中展示 Feed 健康状态的字段来源。

手动立即抓取

除了每小时自动调度,还可以手动触发某个 Feed 立即抓取。REST 端点POST /feeds/:feedId/fetch(feeds.ts)以及 tRPC 的feeds.fetchNow(routers/feeds.ts)都会把该 Feed 的抓取任务直接投入队列,无需等待下一个小时窗口。

面向开发者的 API 与数据模型一览

REST 端点(Hono 实现)

发布侧与消费侧的管理接口均定义在 packages/api/routes 下:

方法路径说明
GET/v1/rss/lists/:listId获取列表的 RSS Feed(公开端点,无需登录,靠 token 鉴权)
GET/feeds列出当前用户的全部 Feed
POST/feeds新建 Feed
GET/feeds/:feedId获取单个 Feed 详情
PATCH/feeds/:feedId更新 Feed(名称、URL、启用状态等)
DELETE/feeds/:feedId删除 Feed
POST/feeds/:feedId/fetch手动触发抓取

tRPC 路由器与所有权隔离

管理类操作同样以 tRPC 形式暴露:feedsAppRouter(routers/feeds.ts)提供create / update / get / list / delete / fetchNow六个过程,统一走createScopedAuthedProcedure("feeds")鉴权。其中update / get / delete / fetchNow通过ensureFeedOwnership中间件先加载 Feed 并校验归属,越权访问会抛出User is not allowed to access resource错误。

数据模型

  • rssFeedsTable:存储用户订阅的外部 Feed,包含id、userId、name、url、enabled、importTags、lastFetchedStatus、lastFetchedAt、lastSuccessfulFetchAt等字段(见 schema.ts 中rssFeedsTable定义);
  • rssFeedImportsTable:记录每个 Feed 已导入的条目(entryId)与生成书签(bookmarkId)的映射,是去重机制的核心。

测试覆盖与边界限制

feeds.test.ts 提供了完整的路由级测试,可以佐证以下行为:

  • 创建/更新/列表/删除的常规 CRUD 均通过测试用例覆盖;
  • Feed 数量上限为 1000 个:创建第 1001 个 Feed 会抛出Maximum number of RSS feeds (1000) reached
  • 跨用户隔离:用户 A 无法删除或更新用户 B 的 Feed,列表接口也只返回自己的 Feed;
  • 删除不存在的 Feed 或所有权检查后行消失,会返回Feed not found/NOT_FOUND

适用场景与注意事项

推荐场景:把公开列表转成订阅源分享给团队或读者;把博客、技术媒体、更新公告等 RSS/Atom 源接入 Karakeep,实现自动收藏归档。

注意事项

  • RSS 发布依赖rssToken,分享 URL 时务必完整携带?token=...参数,token 泄露后应立即在列表设置中重新生成;
  • 纯文本笔记不会出现在发布的 RSS 中,如需对外分享笔记,请使用列表的公开分享链接;
  • 抓取频率以小时为单位,且分散在一小时内的不同分钟,对时效性要求极高的场景需手动触发fetch
  • 抓取器要求响应Content-Typexml,某些非标准实现的源可能因响应头问题被判定为失败;
  • Feed 生成的书签会占用用户书签配额,配额不足时抓取会被跳过。

掌握以上双向链路后,你既可以把 Karakeep 当作一个「个人书签的 RSS 输出站」,也可以把它变成「外部内容的自动采集器」,两种能力都可在 [设置 → RSS Feeds] 与列表设置中零代码完成。

【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询