用 n8n 打造 AI 新闻自动聚合工作流

前言

AI 领域的信息密度已经到了"靠刷时间线会漏掉关键变化"的程度:官方发布、融资并购、开源动态、论文更新、监管动向,任何一块缺口都会让判断失真。信息源越多,重复越多,标题党越多,筛选成本直线上升。

这个项目做的事情很朴素:把每天的 AI 新闻收敛成一份可读、可回溯、可二次加工的 Top 15。不追求大而全,追求稳定、低成本、少踩坑。

最终效果:每天早上 9 点,飞书多维表格里自动新增 15 条 AI 新闻的中文摘要记录,包含重拟标题、原文链接、发布日期、结构化摘要和分类标签。


环境准备

  • n8n 2.9.4(自部署,服务器位于亚太地区)
  • Jina AI — 免费额度 100 万 tokens/月,负责网页正文抓取(HTML 转 Markdown)
  • Google Gemini API — 两处调用:Gemini Flash-Lite 做标题评分,Gemini 2.5 Flash 做摘要生成
  • 飞书开放平台 — 写入多维表格(Bitable),需要配置应用凭证

Jina AI Reader 在这里承担"网页转 Markdown"的角色:RSS 只给标题、摘要和链接,真正有价值的信息在正文里。直接抓 HTML 再清洗又脏又累,Reader 把主内容抽出来输出 Markdown,交给 LLM 做摘要会稳定很多。


工作流全局架构

工作流名为 get_news_final,节点链路如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Schedule Trigger
→ set rss AI source
→ RSS Read
→ Build scoring prompt
→ LLM Score (Gemini Flash-Lite)
→ Parse & Select Top 15
→ Loop articles
→ Jina AI Reader
→ If Content Check
→ Truncate Content
→ Basic LLM Chain summary (Gemini 2.5 Flash)
→ edit result
→ Escape for Feishu
→ write sheet
→(回到 Loop articles 处理下一条)

按阶段拆成 4 段:

  1. 采集 — 定时触发 + RSS 源列表 + 批量读取
  2. 评分筛选 — 日期过滤 + LLM 打分 + 去重 + 取 Top 15
  3. 内容处理 — 正文抓取 + 空值跳过 + 截断保护 + 中文摘要
  4. 输出 — 字段整理 + JSON 转义 + 写入飞书多维表格

第一阶段:RSS 数据源配置

9 个 RSS 源

# 来源 RSS 地址 定位
1 Google AI Blog https://blog.google/technology/ai/rss/ Google 产品发布(Gemini 等)
2 TechCrunch AI https://techcrunch.com/category/artificial-intelligence/feed/ 综合科技媒体 AI 板块
3 The Decoder https://the-decoder.com/feed/ AI 垂直媒体,更新频率高
4 MIT Tech Review https://www.technologyreview.com/topic/artificial-intelligence/feed/ 深度分析,偏政策和影响
5 arXiv cs.CL https://export.arxiv.org/rss/cs.CL NLP/LLM 前沿论文
6 OpenAI Blog https://openai.com/blog/rss.xml GPT/Sora 等第一手发布
7 Hugging Face Blog https://huggingface.co/blog/feed.xml 开源 AI 生态核心
8 Google DeepMind https://deepmind.google/blog/rss.xml Gemini 背后的研究团队
9 NVIDIA Blog https://blogs.nvidia.com/feed/ AI 算力/硬件动态

选源逻辑:官方博客 + 科技媒体 + 学术论文 + 开源社区四类组合,覆盖面够用,不被单一叙事带偏。

两个调整点值得记录:

  • VentureBeat 被移除 — 实测发现其 RSS 已连续 40 天没有更新,留着只增加空跑和噪音
  • arXiv 从 cs.AI 切到 cs.CLcs.AI 量太大且泛化题材多,cs.CL 更贴近 NLP/LLM 方向,噪音显著下降

set rss AI source 节点

用一个 Code 节点输出结构化的 RSS 源列表,下游 RSS Read 节点循环读取:

1
2
3
4
5
6
7
8
9
10
11
return [
{ "json": { "feed": "https://blog.google/technology/ai/rss/" } },
{ "json": { "feed": "https://techcrunch.com/category/artificial-intelligence/feed/" } },
{ "json": { "feed": "https://the-decoder.com/feed/" } },
{ "json": { "feed": "https://www.technologyreview.com/topic/artificial-intelligence/feed/" } },
{ "json": { "feed": "https://export.arxiv.org/rss/cs.CL" } },
{ "json": { "feed": "https://openai.com/blog/rss.xml" } },
{ "json": { "feed": "https://huggingface.co/blog/feed.xml" } },
{ "json": { "feed": "https://deepmind.google/blog/rss.xml" } },
{ "json": { "feed": "https://blogs.nvidia.com/feed/" } }
]

第二阶段:LLM 智能评分与去重

这一段的目标:把所有 RSS 条目收敛成当天最值得看的 15 条。速度和稳定性比"评分精准"更重要,所以选 Gemini Flash-Lite 来打分。

日期过滤

Build scoring prompt 节点先做一轮时间过滤,只保留最近 24 小时内发布的文章,避免旧闻混入。过滤窗口不能设太短(会漏跨时区发布),也不能太长(会把前天旧闻混进来)。

构建评分提示词

评分阶段只喂标题,不喂正文。把所有标题编号后发给 LLM,要求按 1-10 分评分并标记重复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const prompt = `你是AI新闻编辑。以下是今日RSS聚合的${items.length}条新闻标题。
请为每条评分(1-10)并识别重复新闻。

评分标准:
- 重大产品发布/技术突破/开源发布: 8-10
- 重要行业动态/融资/合作/政策: 5-7
- 一般技术讨论/教程/评测: 3-4
- 旧闻/水文/广告/招聘: 1-2

对于内容相同的重复新闻(不同来源报道同一事件),
只在最佳标题上给分,其余标记dup为true。

严格以JSON数组返回,不要包含其他文字:
[{"index":1,"score":8,"dup":false},{"index":2,"score":3,"dup":true},...]

标题列表:
${titles}`;

关键词 fallback 机制

LLM 评分的失败形态很现实:偶发超时、输出非标准 JSON、服务抖动。为了不让链路因为一次评分失败直接断掉,做了一个 fallback——用关键词命中做粗糙评分:

1
2
3
4
5
6
7
8
9
// LLM 解析失败时降级为关键词评分
const keywords = {
'launch': 3, 'release': 3, 'announce': 3, 'unveil': 3,
'breakthrough': 3, 'open-source': 3, 'opensource': 3,
'gpt': 2, 'claude': 2, 'gemini': 2, 'llama': 2, 'mistral': 2,
'openai': 2, 'anthropic': 2, 'deepseek': 2, 'nvidia': 2,
'agent': 2, 'multimodal': 2, 'reasoning': 2, 'agi': 2,
// ...更多关键词
};

这个 fallback 不追求精准,只保证"LLM 挂了工作流不断"。实际跑下来,大部分时候 LLM 评分正常工作,fallback 只在极少数情况下被触发。


第三阶段:内容抓取与摘要

Top 15 选出来后,每条新闻进入循环处理。

Jina AI Reader 抓取正文

把原始 URL 交给 Jina AI Reader,返回干净的 Markdown 内容。正文质量直接影响摘要质量:正文干净、结构清晰,摘要就稳;正文夹杂导航和推荐区块,LLM 输出就会飘。

If Content Check

跳过抓取失败的文章。失败原因通常是:站点反爬、需要 JS 渲染、403、Reader 返回空内容。处理策略简单粗暴——判断 content 是否非空,不满足就跳回循环处理下一条,避免把空内容丢给 LLM 让它凭空编造。

Truncate Content

LLM 最容易炸的情况之一是正文过长导致 token 超限。这里做硬截断,阈值 30000 字符:

1
2
3
4
5
6
7
8
9
10
11
12
const MAX_LENGTH = 30000;
for (const item of $input.all()) {
let content = item.json.content || "";
if (content.length > MAX_LENGTH) {
item.json.content = content.substring(0, MAX_LENGTH)
+ "\n\n[...Content truncated due to length limits...]";
item.json.is_truncated = true;
} else {
item.json.is_truncated = false;
}
}
return $input.all();

30000 字符是经验值,足够覆盖大部分文章主内容,又不会撑爆模型输入。

摘要提示词设计

摘要用 Gemini 2.5 Flash,重点不是"写得好",而是输出格式稳定。提示词采用固定四段式模板(【标题】【摘要】【要点】【标签】),强制 LLM 做"填模板"而非"自由发挥":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
你是一位专业的AI行业新闻编辑。请严格按照下方固定模板输出新闻摘要,
不要添加任何模板之外的内容。

## 输出模板(必须严格遵循,不得增减章节)

【标题】(重新拟定15字以内的中文标题,突出核心事件和关键数据)

【摘要】(100-150字的完整摘要。必须涵盖:什么事件、谁发起、
核心数据/指标、为什么重要。用陈述句,不用疑问句。)

【要点】
• 要点一(一句话,30字以内,聚焦技术细节或产品特性)
• 要点二(一句话,30字以内,聚焦市场影响或竞争格局)
• 要点三(一句话,30字以内,聚焦未来展望或落地时间)

【标签】#AI #领域标签 #公司或机构标签

## 规则
- 使用中文输出,专有名词保留英文(如 GPT-5、Claude、LLaMA)
- 数据指标保留原文数值,不做换算
- 三个要点必须覆盖不同维度,不得重复同一信息
- 如果原文内容为空或无法解析,仅输出:
【标题】原标题翻译
【摘要】暂无详细内容。

第四阶段:写入飞书

输出侧选飞书多维表格(Bitable)有几个现实优势:既能当数据库,又能当运营表;同事可以直接筛选、评论、补充;后续做周报月报直接在表里透视。

字段映射

飞书字段 数据来源 说明
新闻主题 LLM 重拟的中文标题 15 字以内
链接 原文 URL 可直接点击跳转
新闻日期 RSS pubDate ISO 格式
新闻内容 LLM 生成的中文摘要 包含摘要+要点+标签
新闻分类 RSS categories 原始分类标签

踩坑记录与优化

坑 1:飞书写入特殊字符导致 API 报错

问题

新闻内容里经常出现引号、反斜杠、换行符等特殊字符,直接拼进飞书 API 的 JSON body,轻则字段丢字符,重则 JSON 解析失败返回 400。

错误做法

最初的实现用正则粗暴删除特殊字符:

1
2
3
4
// 错误示例:直接删除,信息损失严重
$json.text.toString()
.replace(/[@!#$%^&*:/\"]/g, '')
.replace(/[\\n\\\\n]/g, '')

这既删不干净,又误删了内容中的合法字符——技术文章里的路径、代码片段、引用文本全被破坏。

正确做法

新增 Escape for Feishu Code 节点,用 JSON.stringify 做标准转义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function escapeForFeishu(str) {
if (!str) return '';
// JSON.stringify 自动处理:双引号、反斜杠、换行、制表符、控制字符
let escaped = JSON.stringify(str);
escaped = escaped.slice(1, -1); // 去掉 stringify 添加的外层引号
// 补充:转义单引号(防止下游二次拼接出问题)
escaped = escaped.replace(/'/g, "\\'");
// 补充:过滤残留的 ASCII 控制字符 (U+0000-U+001F)
escaped = escaped.replace(/[\x00-\x1f]/g, '');
return escaped;
}

for (const item of $input.all()) {
item.json.escapedText = escapeForFeishu(item.json.text);
item.json.escapedTitle = escapeForFeishu(item.json.title);
}
return $input.all();

原理一句话:JSON.stringify 负责"正确转义",比正则删字符靠谱得多。


坑 2:摘要提示词过于复杂,输出格式不一致

问题

原始 system prompt 堆了大量层级和装饰符号:

1
2
3
4
🔥 **爆点标题**(<15字,含「关键数据」)
💎 **核心价值**(≤50字:技术突破+受益方)
🚀 **行业影响**(≤30字:规则改变/趋势)
...

结果 LLM 输出格式经常不一致:有时有 emoji 有时没有,层级结构不固定,字数要求被忽略。

原因

LLM 更擅长"填空",不擅长"遵守一堆规则"。规则越多,模型越容易挑自己理解的一部分执行。提示词里混杂风格装饰(各种 emoji 标记),稳定性进一步下滑。

解决方案

换成固定模板 + 严格四段式输出(【标题】【摘要】【要点】【标签】),让模型做"填模板"而不是"自由发挥"。详见上文摘要提示词设计章节。

关键原则:给 LLM 一个明确的输出模板,比给一堆规则更有效。


坑 3:定时触发时间不合理

问题

原始 Schedule Trigger 设置在凌晨 3:20 触发(服务器时区 UTC+8)。实际效果是经常漏掉大量美国当天的发布内容。

原因

9 个源中大部分位于美国(OpenAI、Google、NVIDIA、TechCrunch 等),发布高峰在美西 9:00-18:00 PT。换算到 UTC+8 对应次日 1:00-10:00。凌晨 3:20(UTC+8)触发时,美西才上午 11:20 左右,大半天的内容还没发出来。

解决方案

触发时间改为上午 9:00(UTC+8)。此时美西约 17:00,当天内容基本发布完毕,RSS 列表更完整,评分筛选更像"当日总结"而非"半天快照"。

补充:arXiv 周末不更新(RSS 里有 skipDays: Saturday, Sunday),但其他 8 个源周末仍有内容,保持每天触发即可,不需要为单一源做特殊处理。


坑 4:中文 RSS 源生态崩塌

问题

一开始尝试把中文 AI 媒体也纳入 RSS 采集(机器之心、量子位、新智元等),想把中文视角的内容一起聚合。

实测结果

中文媒体 RSS 状态
机器之心 RSS 地址重定向到飞书表单,已废弃
量子位 无 RSS,仅微信公众号
新智元 无 RSS,仅微信公众号

这不是技术问题,是生态问题。中文 AI 媒体几乎全部迁移到微信公众号封闭分发,RSS 入口被主动放弃。

替代方案

唯一靠谱的方向是自建 RSSHub 实例,把公众号等来源转成 RSS 再接入 n8n。代价是维护成本变高、稳定性要自己兜底。对"每天自动 Top 15"的目标来说,先把英文源跑顺更划算,中文源是后续增强项。


总结

get_news_final 这条工作流的价值不在"用到了多少 AI",而在几个关键设计决策:

  • 源选择 — 官方 + 媒体 + 论文 + 开源,覆盖面够,噪音可控
  • 评分策略 — 标题级评分 + 去重 + Top 15,跑得快、成本低,LLM 失败有 fallback 兜底
  • 内容抓取 — Jina AI Reader 把正文变成干净 Markdown,摘要稳定性提升明显
  • 输出落地 — 飞书多维表格作为可检索的数据底座,后续做周报月报有抓手
  • 工程兜底 — 特殊字符转义解决写入稳定性,固定模板解决输出格式一致性

成本也很友好:Jina AI 免费额度足够覆盖正文抓取,Gemini 按每天 Top 15 的调用量算费用基本可以忽略。真正的投入是一次性把流程打磨稳定。

可扩展方向:

  • 增加输出渠道 — 企业微信、Slack、邮件推送
  • 接入中文源 — 通过 RSSHub 转换微信公众号
  • 增强后处理 — 主题聚类、相似新闻合并、自动生成周报