自动化 · 2026

n8n 日报流水线

到点抓 12 家新闻源,AI 只打分、版面交给规则,排成报纸版式,再校验、提交、上线。生成跑在本机,GitHub 只做接收端。

v1.0 自用 Windows Python 3.14

这是什么

「每天一份日报」这件事整条链交出去,分两段:前半段把散在 12 家源里的信息压成一份报纸 HTML,后半段把这份 HTML 校验、提交、推到网上。人只在它出错的时候才需要看一眼。

出口固定在 reports/latest.html,首页右边那张「今日日报」卡一直指着它——所以日报换新的时候,首页一个字都不用改。

只保留最新一期:覆盖同名文件、不归档。换来的是地址恒定,书签和分享出去的链接永远不会失效。

一 · 生成链路

重点在这一段:19 个节点、两次 AI 调用,其余全是本地计算。难点不是抓取,而是取舍——哪条上头版、哪条当大稿、同一件事被五家报道时怎么只留一条。

text
开机触发(Webhook,由开机脚本调一次)
  ↓
抓取源(12 家 RSS 并发)
  ↓
解析合并(解析 / 去重 / 算兜底分)
  ↓
条数校验(不足 10 条直接报错,不出残报)
  ↓
查天气(wttr.in → ipwho.is + open-meteo 兜底)
  ↓
组装排序请求 → AI 打分(0~100)
  ↓
解析打分(合分 + 板块配额 + 单源上限)
  ↓
组装候选池 → AI 整理(译题 / 摘要 / 概念分组)
  ↓
解析 AI 输出
  ↓
渲染 HTML(38000 字符纯 JS,一次产出整份报纸)
  ↓
转附件(邮件发送默认关闭)

节点数看着多,其实只有三类活:抓、洗、排。抓和洗是确定性代码,只有「哪条重要」和「怎么把它写成一句人话」交给 AI。

阶段做什么出问题会怎样
抓取源12 家 RSS 并发,单源失败不拖垮整轮那个源当天缺席,其余照跑
解析合并解析字段、按标题去重、给每条算一个兜底分兜底分接住 AI 缺位
条数校验少于 10 条直接报错停下不出残报,线上仍是上一期
查天气wttr.in 为主,失败走 ipwho.is + open-meteo三源全挂,日期区留空占位
AI 打分每条 0~100,判断今天值不值得上版面退回规则分,rankDegraded 为真
解析打分合分、板块配额、单源上限全在本地算,不依赖外部
AI 整理译题、写摘要、把同一事件归成概念组保留原题与原始候选,degraded 为真
渲染 HTML38000 字符纯 JS,一次产出整份报纸缺标题、缺摘要、板块抓空都有兜底
转附件HTML 转成邮件附件默认关闭,不影响出报

取舍权给 AI,版面权给规则:AI 只回答「这条多重要」,不决定谁当大稿。哪条占通栏由 assign_weights() 按固定规则算——板块首条必为大稿,条数 ≥ 8 时中点再给一条。所以每天的骨架完全一致,只有内容在变;AI 就算传了重点,也会被规则覆盖。

同一件事被多家报道时,条目上带一个 cluster 标记,同值自动并成一组、组内第一篇当主稿。版面上表现为「一个大稿 + 若干来源」,而不是五条内容重复的短讯。

每一环都有降级,宁可出残报也不出空:天气三源全挂就留空占位,AI 两步全挂就退回规则分与原始候选,概念组缺失就退化成单条通排。代价是失败不报错——判断质量只能看 rankDegraded / degraded 这类业务字段,不能看节点执行状态是不是绿色。

二 · 发布链路

text
开机  触发(Webhook;守卫判今天没跑过才继续)
  ↓
取数整理(n8n 节点:抓取 + 摘要)
  ↓
排版(拼成自包含 HTML,样式内联、零外链)
  ↓
写盘(\out\daily.html)
  ↓
校验(存在 / 大小 / 是不是 HTML)
  ↓
覆盖(reports/latest.html)
  ↓
提交推送(git add → commit → push)
  ↓
上线(GitHub Pages 构建完成即可访问)

发布这一段是一条直链,没有分支,做的事只有「把这个文件送上线」。中间那层校验是关键:生成失败时上游经常仍然写出一个 200 字节的错误页,直接覆盖就会把首页那张卡指向一页报错信息。

写盘和推送现在接在同一条工作流的尾部:生成节点跑完,接着写盘、调 publish.py、写回当日日期,一条链走到底。

节点关键设置
开机触发(Webhook)POST /webhook/daily-brief-run,由开机脚本在 n8n 就绪后调一次;responseMode 用 onReceived,立即返回不等结果
查上次运行 / 今天跑过吗读 .last-run 里的日期,等于今天就整链结束——零点几秒,不抓网也不调 AI。一天开几次机都只出一期
写日报文件Read/Write Files from Disk,固定写 \out\daily.html;路径必须在写盘白名单里,否则报「Access to the file is not allowed」
推送上线Execute Command 调 publish.py;路径写死,不用 {{ $json.xxx }} 拼。n8n 2.x 默认封禁这个节点,要 NODES_EXCLUDE=[] 它才存在
记下今天推送成功后才写日期。失败的那次不算「已跑」,下次开机自动重试

几个取舍

  • 渲染器是纯 JS,内联在节点里:不 require、不读盘、不起子进程、不联网,只用 Date / Math / Set / Map / JSON 和 Buffer。早期版本是 Execute Command 调本机 Python 脚本,换一台机器就断在那里;现在整条链的外部依赖只剩「能上外网」和两个凭据。
  • 降级写在链里,不是写在文档里:天气、AI 打分、概念组三处都定义了失败时的替代值,所以 AI 两步全挂也能把报纸出出来。代价是不报错,判断质量得看业务字段。
  • 不用 GitHub Actions 定时:Actions 的 cron 按 UTC 算,换算容易错;境外 runner 抓国内源经常被限流;仓库 60 天没提交还会自动停用。而且它没法反过来触发本机的 n8n。
  • 生成放在本机:数据源、登录态、代理都在这台机器上,抓取成功率高;出问题能直接翻日志,不用等云端构建。
  • GitHub 只做接收端:一个仓库、一个分支,Pages 自己发布,仓库里不需要任何 workflow 文件。
  • 只留最新一期:覆盖同名文件意味着链接恒定,代价是 Pages 有缓存,偶尔会看到上一期——刷新或等几分钟就好。

发布脚本

scripts/publish.py 一次做完四件事:校验 → 覆盖 → 提交 → 推送。stdout 只输出一行 JSON,n8n 可以直接解析结果。

这段不在上面那个仓库里:仓库只收生成链(19 个节点的工作流 JSON、渲染器模板、构建脚本),publish.py 是站点这边的本地脚本,跟着站点走。

cmd
python "<站点目录>\scripts\publish.py" "<站点目录>\_incoming\daily.html"
status退出码含义
published0已发布,附带 commit 短 SHA
no_change0内容与仓库一致,跳过提交(不算失败)
bad_args2没传源文件路径
missing_src3源文件不存在
too_small4不足 512 字节,疑似生成失败
not_html5开头没有 <html> 或 <body>
no_repo6站点目录还不是 git 仓库
no_remote7仓库没配远端
git_add_failed8暂存失败
commit_failed9提交失败
push_failed10推送失败,多半是凭据或网络

路径里有中文时,n8n 的 Execute Command 很容易把引号吃掉,报「系统找不到指定的路径」。先确认引号还在;仍不行就把整个站点挪到纯英文路径下——Pages 不关心本地路径长什么样。

失败会怎样

  • 抓到的条数不够:少于 10 条在「条数校验」就停下,宁可不发也不出残报,线上仍是上一期。
  • AI 那两步降级:报纸照出,只是摘要没润色、没有跨源概念组。rankDegraded / degraded 会为真——节点是绿的,也可能走了降级。
  • 天气全挂:日期区留空占位,不影响其余版面。
  • 生成失败:内容不足 512 字节或者根本不是 HTML,脚本停在 too_small / not_html,线上仍然是上一期。
  • 仓库没绑:no_repo 直接退出,一个文件都不改,不会留下半成品提交。
  • 推送失败:push_failed;本地已经提交好了,网络恢复后手动 push 一次即可。
  • 内容没变:no_change,当成成功处理——每天的内容本来就有可能几乎一样。

常见问题

怎么确认 AI 那两步真的干活了?

看「解析打分」输出的 rankDegraded 和「解析 AI 输出」的 degraded。两个节点都设了 onError: continueRegularOutput,报错被静默吞掉,执行状态照样是绿的——只看绿色会误以为 AI 生效了,实际跑的是降级分支。

改版式要动几个文件?

两个真源:渲染器模板 render_brief.js.tpl 管版式与 CSS,工作流 JSON 管 19 个节点的代码与参数。构建脚本有一道硬闸——模板主体必须与节点里那段代码逐字节相同,只改一边会直接中止并打印差异位置,所以不会出现「模板和线上跑的代码悄悄分叉」。

日报换新了,首页那张卡会跟着变吗?

卡上不写具体日期,只写「每日一期」。卡片永远指向同一个地址,所以不会过期;变的是点进去的那一页。

为什么不把每一期都归档?

地址恒定优先。要归档就得让文件名带日期,卡片也就得跟着改,等于每天动一次首页;现在这样是拿「翻不到旧刊」换「链接永不失效」。

n8n 里要存 GitHub token 吗?

不用。走本机 git push,账号由 Windows 凭据管理器记着,n8n 只负责发一条命令,不碰任何密钥。前提是手动 push 成功过一次,凭据已记住。

站点还不是 git 仓库的时候会发出去吗?

不会。publish.py 检查到没有 .git 或没有远端,就以 no_repo / no_remote 停下,源文件连复制都不会发生。

更新记录

  • 生成链 v0.4.5 — AI 打分排序接进链路,节点定稿为 19 个;取舍权归 AI,版面权归规则
  • v1.0 — 校验、覆盖、提交、推送合成一个脚本,退出码分流给 n8n 判断
  • v0.6 — 从 Execute Command 里直接 copy + git,改成调用独立脚本
  • v0.3 — 首页日报卡与 reports/latest.html 拆开,地址固定不变