学习 / 实践
AI Radar 工程拆解:从 RSS 到网站,中间发生了什么?
AI 应用 · 工程案例
沿着本站真实代码,追踪采集窗口、每日配额、去重、自动检查和部署,并用证据定位“采集成功却没有更新”。
浏览统计暂不可用
本文目录 7
“任务是绿色的,但网站没有新资讯。”这句话其实混合了五个不同的问题:请求是否成功、是否有符合条件的候选、是否写入文件、是否进入主干、线上是否部署了那个提交。
这篇文章沿着本站代码把它们拆开。阅读后,你应该能拿着一次采集报告找到内容停在哪一步,也能理解为什么“每两小时运行”不等于“每两小时必须新增十条”。
证据范围
本文由 AI 辅助整理,依据博客仓库提交
558e0a9的实现。描述的是可检查的代码行为,不是一次新的线上事故复盘,也没有测量生产延迟或筛选准确率。文中的配额算例是说明性输入。可从 固定版本源码 对照阅读。
1. 一条资讯经过哪些边界?
RSS 来源
→ 请求与解析
→ 主题 / 技术机制筛选
→ 窗口、重复、总配额、来源配额
→ 可公开的数据 + 关联报道
→ 内容检查 + 三语言构建
→ Git 提交与推送
→ 部署系统
→ 读者访问的页面前三步决定“值不值得收”。后半段决定“能不能稳定送达”。两者需要不同的日志,不能用一个成功图标替代。
| 边界 | 本站入口 | 需要核对的证据 |
|---|---|---|
| 来源与规则 | config/news-sources.json |
来源启用状态、域名白名单、每日限额 |
| 原始响应转候选 | scripts/news/lib.mjs 的 parseFeed |
有效发布时间、标题、原文 URL、摘录 |
| 研究相关性 | scripts/news/relevance.mjs |
主题证据、技术机制、拒绝原因 |
| 本轮选择 | selectItems |
outside-window、duplicate、daily-limit 等原因 |
| 发布文件 | scripts/news/collect.mjs |
data/news/items.json 的实际差异 |
| 检查与推送 | .github/workflows/ai-news.yml |
构建结果、提交 SHA、推送结果 |
表中路径均属于上述固定提交。页面不会在读者打开 Radar 时重新抓取 RSS 或调用模型;它读取已生成的数据。这让来源故障不直接拖慢页面请求。
2. 三种时间,解决三个问题
代码同时保存 publishedAt 和 collectedAt,报告另有 runAt。前者是来源声称的发布时间,后者是本站收录时间;补采旧文章时,它们天然不同。
当前配置有两层时间限制:解析器最多接受过去 14 天的条目;一次任务默认只选择过去 48 小时的候选。手动任务可调整 window_hours,但不能超过解析范围。扩大窗口也不能让 RSS 返回它已经移除的旧记录。
每天十条的“天”按 UTC 收录日计算,而不是来源发布日期,也不是北京时间午夜。在中国标准时间中,UTC 换日对应上午 08:00。因此,07:50 和 08:10 两次运行可能落入不同配额日。
当前调度是 35 */2 * * *,即 UTC 偶数小时的第 35 分钟。它是计划触发时间,不能作为准点完成的保证。以上均可核对 工作流 与 选择函数。
3. 十条是上限,六条也不是保底
设今天已收录 7 条,其中 AIHOT 5 条。即使下一轮拿到了很多合格候选,全站也只剩 3 个名额,AIHOT 只剩 1 个名额。其他来源默认各自最多 2 条/日;配置可以覆盖来源上限。
候选按研究优先级、来源优先级、发布时间、ID 依次排序,再逐条判断。AIHOT 的来源优先级不能越过研究筛选,也不会绕过已用配额。高优先级来源没有新内容时,不会用不相关内容凑数。
下面是独立可运行的配额算例,只演示剩余容量,不替代真实选择算法:
const remaining = (limit, used) => Math.max(0, limit - used);
const siteSlots = remaining(10, 7);
const aihotSlots = remaining(6, 5);
const aihotCanAdd = Math.min(siteSlots, aihotSlots);
console.log({ siteSlots, aihotSlots, aihotCanAdd });
// { siteSlots: 3, aihotSlots: 1, aihotCanAdd: 1 }这里有一个值得注意的设计取舍:当前是先到先占的跨运行配额,不是当天结束后再挑最佳十条。早间入库的普通候选不会因为晚间出现更好的文章而自动被替换。每天严格控量与全天最优选择是两个不同目标;后者需要候选池和延迟发布策略,目前未实现。
4. 为什么全文源有帮助,但仍会误筛?
简短标题可能只写“新版本上线”,真正有价值的信息藏在正文中的“沙箱隔离、测试套件、上下文卸载”。AIHOT 全文源让这些技术证据进入临时字段 relevanceText,供规则判断。
公开写入前,publicNewsItem 移除该字段;页面保存的来源摘录最多 180 个字符。读全文用于判断,不意味着全文转载。 当前自动工作流显式关闭模型摘要,所以来源摘录也不能标成 AI 总结。
现有筛选包含主题与机制组合,例如上下文工程搭配预算/压缩,机器人搭配轨迹/数据对齐。商业宣传、纯排名等还会经过排除规则。它比单个关键词更具体,但本质仍是规则,不理解全部语义:一句技术文章中的商业背景也可能触发排除,一段营销文案也可能刻意堆满术语。
因此,规则通过只意味着“满足本站收录条件”,不意味着“报道属实”或“方法有效”。改进筛选应先收集误收、漏收样本,再调整 相关性规则,而不是盲目降低阈值。
5. 去重与聚类,不是一回事
采集选择阶段用规范化 URL 和去掉标点、大小写后的标题阻止重复入库。展示阶段的事件合并还支持相同 URL,以及 72 小时内的相同标题或已有内容哈希。详见 Radar 数据适配器。
这并不是向量语义聚类。中文报道与英文官方公告即便描述同一件事,只要链接、标题和哈希不同,就可能仍是两个事件。代码“支持内容哈希”也不代表当前 RSS 解析器为每条记录都生成了它。
同组报道会优先选择原始或官方来源,但来源排序不等于事实核验。verification 需要核验者、时间和证据,不能用“官方”或“多个站点转述”自动制造已核实状态。
6. “成功但没更新”的排查顺序
先打开该次 Actions 的 news-run-report 附件,再按下面顺序缩小范围:
| 观察 | 下一步 | 不能直接得出的结论 |
|---|---|---|
来源 failed |
看 HTTP、超时、解析失败,检查上一阶段安装是否成功 | 所有来源都失效 |
来源成功,inWindow = 0 |
对比最新发布时间与报告窗口 | 定时任务没有运行 |
selected = 0 |
查 rejected 与 notSelected 的原因 |
RSS 没有内容 |
selected > 0,构建失败 |
看内容检查/三语言构建日志 | 已经发布 |
| 构建通过,推送失败 | 核对远端提交与并发更新,重新集成后检查 | 数据已经在主干 |
| 推送成功,页面仍旧 | 比较部署绑定的提交 SHA、生产域名和构建数据 | 一定是浏览器缓存 |
流程只在至少一个来源成功时保持采集阶段成功;部分来源失败可以和整个任务绿色同时出现。发布步骤还可能因为“没有文件差异”正常退出。这两种情况都需要读报告,而不是只看任务颜色。
当前工作流的并发锁只约束同组采集任务,不会锁住人工推送。若检查期间主干前进,最后的推送可能被拒绝;这个版本没有自动重算并重试推送。定位时要保留最后成功的边界,不要通过强制推送跳过问题。
7. 从这个案例带走什么?
把任何内容流水线写成“输入 → 选择 → 存储 → 发布”,然后为每个箭头定义可检查的产物。这里分别是来源状态、候选决策、文件差异、提交 SHA 与生产部署。
对本站而言,下一步最有价值的工程工作是建立一小份人工标注的筛选样本集,持续检查误收与漏收;它属于评估工作,不能用今天有几条新闻来代替。想看目前实际产物,可以回到 AI Radar,也可以查看 站点项目。
写给未来的自己合上文章前,留下一点自己的理解。
你的私人学习便签,只存在当前浏览器,不会上传或公开。清理浏览器数据会丢失,请导出留存。三种语言共用这篇文章的便签。
继续探索
继续探索
接下来,走哪条路?
- Java 8 调用大语言模型:从第一次请求到多轮对话 →
用三个真实运行的 Java Demo,理解模型请求、JSON 解析、读取超时与对话历史。
依据文章关联与同主题已发布内容整理,不使用随机推荐。