Public
CNBC + CBS Top News merged and de-duplicated into one RSS feed
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

us-topnews-merge

Val Town: https://www.val.town/x/anmeimei/us-topnews-merge Base: https://anmeimei--1349d12aa7df11f1abc71607ee4eb77e.web.val.run

路由内容
/CNBC Top + CBS Top 合并去重(重复保留 CNBC)
/politicsCNBC 政治与政策,扣掉已在 Top News 出现过的
/economyCNBC 经济,同上
/techCNBC 科技,同上

源:

参数(任何路由通用,多个参数用 & 连接,只有第一个用 ?): ?sim=0.45 阈值 ?tag=0 去来源前缀 ?diag=0 去诊断条目 ?debug=1 诊断网页

feed 第一条是「合并诊断」,置顶。guid 见文末 2026-09-04 那节(已改成不带日期)。

实测 2026-09-03

/ CNBC30 + CBS30 → 保留 56,丢弃 4 /politics CNBC30 → 保留 22,丢弃 8 /economy CNBC30 → 保留 28,丢弃 2 /tech CNBC30 → 保留 21,丢弃 9(9 条全部是「同一篇」精确命中)

板块 vs Top News 基本都是同一篇文章(链接相同),走精确匹配,不靠相似度猜。 跨 CBS/CNBC 才需要近似匹配。sim=0.36 会误杀 CBS 理财软文标题,故默认 0.45。

2026-09-04 修复:标题完全相同但日期相隔很远的重复

CBS 的 latest feed 会把 60 Minutes 旧片段按原始播出日期重发 (实测出现过同一条 "When Social Security mistakenly overpays benefits" 的 2025-07-27 / 2024-06-23 / 2023-12-20 三份)。 原来的近似判重有 WINDOW_HOURS=36 的时间窗,相隔几年的两条永远不会被拿来比较, 所以三条全留下了。

修法:在近似比较之前加一道「标题归一化后完全相同 → 同一篇」,这一步不看日期。 归一化 = 转小写 + 非字母数字全替换成空格 + trim。零误判风险。 本地用那三条真实标题跑过,2 条被丢、1 条保留,走的是 same-title 分支。

注意 CBS 这类旧稿是它自己有意重推的内容,不是脏数据,所以只去重、不按年龄过滤。

2026-09-04 诊断条目修正(已上线)

两处改动,均在 diagEntry()

  1. 日期按美东算,不再用 UTC。 new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }) 之前用 toISOString().slice(0,10),美东晚 8 点后 UTC 已经跳到第二天, 所以标题会显示 9/4 而实际是 9/3。

  2. 诊断条目的 guid 去掉日期diag-${base.pathname}。 每个 feed 永远只有唯一一条「合并诊断」,内容随刷新就地更新, 不再每天新增、不会积攒。

验证(2026-09-03 ET):

路由guid诊断条数item 总数
/diag-/157
/politicsdiag-/politics124
/economydiag-/economy130
/techdiag-/tech121

副作用:改 guid 会在 NetNewsWire 里各留下一条旧的孤儿诊断(四个 feed 各一条), NNW 会自行清掉。这是最后一次——以后 guid 固定,不会再产生孤儿。

2026-09-04 查证:三个板块之间要不要互相去重 → 不需要

问题:Tech / Politics / Economy 之间是否也有重复,要不要做成级联去重 (Tech 只扣 Top;Politics 扣 Top+Tech;Economy 扣 Top+Tech+Politics)。

实测(当天四个源各 30 条,按文章 URL 比对):

比对重叠
Tech ∩ Politics1
Tech ∩ Economy0
Politics ∩ Economy0
Tech ∩ Top News10
Politics ∩ Top News7
Economy ∩ Top News1

那唯一 1 条(Musk 中期选举花钱)本身就在 Top News 里,两个板块已经各自扣掉了, 所以实际可见的跨板块重复是 0 条。 又把模糊阈值放宽到 0.40 并去掉 36 小时时间窗重跑,三个板块两两之间零个近似标题。

原因是结构性的:CNBC 每篇文章基本只归一个板块,Top News 是跨板块挑出来的一层覆盖, 所以「板块 ↔ Top」重叠大,「板块 ↔ 板块」几乎不重叠。

结论:不做级联。理由不只是没必要,还有代价—— 跨板块用 sim=0.45 会误杀(Economy 一篇关税报道 vs Politics 另一篇关税报道 是两篇不同文章,不是重复),且引入顺序依赖、/economy 每次多抓两个源。 以每天 0~1 条真重复换这些,不划算。

如果以后真的反复看到同一篇出现两次,再加只按 URL 精确匹配、不做模糊的级联, 那个零误杀风险。注意:这个结论基于一天的切片(90 条), Musk 那条证明「一篇挂两个板块」是可能的,只是罕见。

2026-09-06 方向反转:改成「Top News 扣板块」

起因

用户在 Top News 和 CNBC: Tech 里看到同一条新闻。查下来去重逻辑没坏 (当时 /tech 的诊断里就把它列为「同一篇」丢弃了),但暴露了两个成因:

成因一(竞态):CNBC 先把稿子发到板块,过一会儿才提拔进 Top News。 NetNewsWire 在这个空窗里抓了板块 feed,就存下一份;等它进了 Top News 又存一份。

成因二(结构性,更严重):CNBC Top News 只有 30 条、约 1.5 天翻一轮, 而板块 feed 能追到两三周前。用短窗口当参照系,绝大部分板块条目根本没被检查过。 2026-09-06 实测:

feed时间跨度比 Top News 窗口还老的条目
Top News(参照系)09-04 11:00 → 09-06 00:24
Tech09-02 12:43 → 09-04 14:4721 / 26(81%)
Politics09-01 18:53 → 09-04 19:4821 / 24(88%)
Economy08-19 21:29 → 09-04 17:1625 / 26(96%)

改法

反过来:Top News 扣掉三个板块,板块恢复全量。

参照系(三个板块合起来 08-19 → 09-05,2.5 周)完整覆盖被过滤对象 (Top News 09-04 → 09-05,1.5 天),成因二的窟窿直接消失,不需要持久化存储。

实测 CNBC Top 30 条里 14 条(47%)已经在某个板块里,反转后 Top News 从 57 条降到 43 条。

三个配套决定

一、Top News 扣板块只做精确链接 / 同标题匹配,不做相似度。 主 feed 误杀一条的代价远大于漏掉一条重复。CNBC 与 CBS 之间仍然用 sim=0.45。

二、板块条目压后 6 小时再输出(?delay=)。 等 CNBC 把该提拔的提拔完,★ 第一次渲染就准。 用户读完就标已读、不会回头看,所以「首次准确」比「事后补上」重要。 代价:板块新闻晚 6 小时出现,这段时间它在任何 feed 里都看不到。 (用户明确说不介意——他只用 NetNewsWire 每天 catch-up 一次,不做实时。)

三、板块里也上了 Top News 的条目标 ★(?star=0 关掉)。 反转之后,编辑部「这是今天头条」的信号会随着条目移出 Top News 而丢失, ★ 把这个信号保留在用户真正会读完的地方。

剩下没治的

CNBC 偶尔先发 Top News、后发板块的话,那一刻 Top News 会显示它, 等板块拿到才扣掉 —— NNW 两边各存一份。治它只能延迟 Top News,不划算。 从实测看 CNBC 是先板块后头条(最近 12 小时属于板块的 5 条全都已在板块里),这个方向应该很少。

实测 2026-09-05(美东)

路由条目带 ★
/43(丢弃 17)
/tech304
/politics306
/economy293

?delay=24 时 /tech 变成 27 条,压后功能正常。

2026-09-07 剔除 CBS 的电视片段(/video/

起因

用户反馈:CBS Top News 每天推送太密集,而且「很多什么 9/11 的好多条,这些我确实不 care」。 CNBC 的密度刚刚好,但它基本只覆盖钱相关的事,缺 Lindsay Clancy 这类社会新闻。

判断依据

CBS 的 feed 里有很大一部分条目链接是 /video/(电视节目片段),其余是 /news/(文字稿)。 两次抽样:主 feed 25 条里 11 条是 /video/;这次上线后 CBS 抓到 30 条里 16 条是 /video/——大约一半。

/video/ 条目和同一天的 /news/ 条目配对,没有配上的残余基本都不是硬新闻

video-only 残余(抽样)类型
2 friends hit the road for a good cause软性栏目
Exclusive discounts from CBS Mornings Deals带货广告
Meet the psychiatrist behind "therapy speak"访谈片段
Is quitting good for you?软性栏目
American farmers sell herds over high costs节目片段
9/6: CBS Weekend News / 9/6: Face The Nation整期节目片头
9/6/2026: Sharswood; Boom Chicago地方台整期节目
Connecticut school honors Sept. 11 attacks victim纪念类
Lindsay Clancy 庭审片段真新闻,唯一一条

硬新闻(飓风、空难、庭审)文字和视频两种格式都会有;video-only 的漏网基本是软内容。

唯一一条真新闻 Lindsay Clancy,在 cbsnews.com 上搜到大量 /news/ 文章 (/news/lindsay-clancy-mistrial-what-happens-next//news/trump-lindsay-clancy-case-mistrial-hung-jury-horrible-thing/,还有若干 /boston/news/…), 说明这类连续跟进的大案子不会因为剔掉视频而丢——今天漏了明天还在。

用户自己的判断(「CBS 的 text 和 video 基本都讲一件事儿,真正重要的新闻两个 format 都会有」) 结论对,理由只对一半:不是每条视频都有对应文字,而是没有对应文字的那些本来就不值得读

实现

const isVideo = (it: Item) => /\/video\//.test(it.link || "");

collect() 加了 keepVideo 参数,默认剔除,?video=1 保留。 counts 统计的是过滤后的数量(所以诊断里 CBS 显示 14 而不是 30), 剔掉多少条单独写在诊断第一段:「剔除 CBS 电视片段 N 条」。

板块路由的 videoOut 只算板块自己的(永远是 0,CNBC 没有 video 链接), 不把「Top News 参照系剔掉的」算进去——那会让 /tech 的诊断显示一个跟它无关的数字。

实测 2026-09-07(美东)

路由条目/video/ 链接
/32(31 条新闻 + 诊断)0
/tech300(4 条带 ★)
/politics290
/economy310

?video=1 恢复:Top News 41 条新闻,其中 11 条是 /video/。 四个路由都没有 NaN / undefined。

密度参考(当时测的其他候选源)

每天条数
CBS Top News(未过滤)~30
CNBC Top News~20
PBS NewsHour~19
NPR News~8

2026-09-11 Top News 加发布时间上限(?maxage=

起因

用户两三天没 catch-up,回来发现 US Top News 里混着 4 月 / 7 月 / 8 月的 CBS 文章, 而且清楚记得这几天反复标过已读。

查证(读了 Mac 上 NetNewsWire 的 iCloud 库,只读副本)

该 feed 171 条未读,按到达日期分组:

到达日条数
09-041
09-087
09-0963
09-1069
09-1131

那 11 条"很久远"的条目,发布日期从 2026-04-30 到 08-29,到达日期全部是 09-09

所以不是同步丢失,也不是标过又变回未读:CBS 在 09-09 往 feed 里吐了一批旧存档, NetNewsWire 按发布日期排序,它们一进来就沉到列表最底部,从上往下读永远扫不到。

佐证:09-11 当天 CBS 源里仍挂着一条发布日期 2024-08-01 的「The 60 Minutes Channel」, 只是恰好是 /video/,被视频过滤器顺手挡掉了。这个源确实会往外吐旧条目。

改法

Top News 的输出加发布时间上限,默认 10 天,?maxage= 可调,?maxage=0 不限。 只作用于 / 自己的条目——不作用于板块参照集,也不作用于板块路由。

为什么板块不加:Top News 是「现在在发生什么」,十天前的东西躺在列表底部对读者零价值。 板块不是。实测 09-11 当天 /economy 最老一条发布于 08-25(17 天前), 加 10 天上限会直接砍掉一大半;而且板块的时间深度还要拿去给 Top News 当去重参照系, 砍短了会让 2026-09-06 那次反转解决的窗口盲区重新出现。

实测 2026-09-11(美东)

路由条目最老发布日备注
/3409-10默认 maxage=10,当天没有超龄条目
/?maxage=13309-10 20:28剔除 1 条,诊断第一段有提示
/?maxage=23409-10 01:26剔除 0 条
/tech2409-09不受影响
/politics2509-08不受影响
/economy2808-25不受影响(这条就是不给板块加上限的理由)

四个路由都没有 NaN / undefined。

部署踩的坑(以后改这个 val 的人看这里)

Val Town 存盘时会跑 prettier,把长语句折成多行。用「整行匹配 + 在该行之后插入」的方式 改代码时,如果目标语句在线上已经被折成多行,插入点会落在语句中间、把它劈成两半—— 这次就把 const delayH = 和它下面的三元表达式劈开了,线上直接 500。 改之前先把目标区域按行打印出来确认实际排版,不要假设它和本地文件长得一样。

相关(同一套 RSS 系统的其他 val)

  • 财新滚动要闻 / 晨读荐闻:val anmeimei/caixin-briefing-rss
  • 财新 T早报(tzaobao.ts)也在同一个 val 里
  • MIT Tech Review 三个 AI feed 合并去重:val anmeimei/mit-tr-ai