公众号发布管线迭代实录:从「基本能用」到「不必盯着」
上一篇文章写了公众号配图自动化的从零到一。一个月后再看,当初那个脚本除了"能跑"之外,哪哪都是窟窿。本文记录的是六轮迭代——不是重写,而是在同一个脚本上不断发现、修复、升级的过程。某种意义上,它比第一版的搭建过程更值得写。
为什么需要迭代
初版管线(publish-via-api.mjs)工作量大约一个下午:Pollinations.ai 生图 → 上传微信 CDN → 组装 HTML → 创建草稿。跑通的那一刻很有成就感,但很快发现这套管线只是在理想条件下能工作。
实际情况是:
- Pollinations.ai 免费额度极低,第二天生图就开始返回 HTTP 402
- 正文里莫名其妙出现了"标题候选"列表
- 摘要和正文里引用同一段话,读起来像复读机
- 排版就是纯文字堆砌,数据段落和普通段落混在一起
- 封面图质量看心情,有时候好看有时候抽象
这些问题单独看都是小问题,但叠加起来导致一个后果:每次发布前还是需要大量手动修复。"一行命令发布"变成了"一行命令 + 半小时人工修图 + 排版调整"。那自动化省下来的时间去哪了?
所以决定:不重写,不规划大版本,用最笨的方式——发现一个问题修一个问题,修完就测,测完上线。
第一轮:内容污染治理
最先暴露的问题不是图片——而是文章内容本身。
bug 1:标题候选进入正文
我的写作流程是先写「内容管线」再手动提取标题候选。markdown 文件末尾有一段:
---
## 标题候选
碳电算协同:三条线同时收紧意味着什么
当考核、碳价、算力用电同时收紧
...... buildArticleBody() 函数里有一个正则去掉 --- 分隔线。问题在于标题候选区块本身也是 ---\n## 标题候选\n...\n--- 的格式——去掉分隔线之后,## 标题候选 变成了一个正常的三级标题出现在正文中。
另一个更隐蔽的问题:这块内容同时流入了 TOC(目录生成)逻辑,导致目录里多了一个叫「标题候选」的章节。
修复:在去掉 --- 之前先用 ---\n## 标题候选[\s\S]*?\n--- 找到并整块剥离。
教训是:markdown 里的「注释区段」应该用 HTML 注释 <!-- --> 而不是依赖分隔线语法。不过改动 markdown 文件本身可能引发别的问题,我选择了让解析器更健壮。
bug 2:摘要重复
文章开头的 > 引用 是用于公众号摘要(digest)的。但这个引用同时出现在正文里——且 digest 字段也引用了同一段文字。读者打开文章看到的第一段和摘要完全一样,体验很差。
修复:在 buildArticleBody() 的产物中新增正则去掉正文首行的 > blockquote。
这是一类典型的管线问题:同一份数据被两个下游以不同方式消费,但上游不知道自己被消费了两次。
bug 3:TOC 编号对齐
旧版 TOC 用表格布局 + inline-block 的编号圆点,在不同手机上垂直对齐飘忽不定。修复成 flexbox 等宽圆点 + 自动换行文本,移动端横竖屏都对齐了。
这些代码改动都不大(每处 3-5 行),但它们是第一轮迭代的核心价值:扫清了「发布后才发现要改」的问题,让人可以放心地一键生成草稿。
第二轮:配图的九九八十一难
内容问题清完后,最痛的点浮出水面:配图。
第一回合:Pollinations 免费额度
Pollinations.ai 底层跑的是 Flux 模型,质量够用,也不需要 API Key——最开始生图成功率 100%。但第二天开始,第 3 张、第 4 张图开始返回 HTTP 402(Payment Required)。
尝试过的对策:
- 缩短提示词(长提示词触发付费判定)
- 去掉
nologo=true参数 - 换不同的描述结构
结果:前 2 张始终免费,后面的随机成功或 402。不是内容问题,是明确的免费额度限制。
第二回合:Picsum.photos 兜底
加了一个 fallback:如果 Pollinations 返回 402,就从 Picsum.photos 拉一张随机图片占位。Picsum 永远不收费,速度也快——但图是纯粹的随机风景照,跟文章内容毫无关系。
有一篇文章的配图是一张雪山照片,配文是「电力现货市场交易策略」——看起来很滑稽。
这个方案在技术上是可用的,但在内容层面不合格。
第三回合:SVG 主题插画
再往下想,如果要保证图片既免费又与内容相关,而且不需要外部 API,那唯一的路是程序化生成——用 SVG。
初版 SVG 很简单:
- 数据图表示意:Excel 风格的折线图
- 应用场景示意:⚡ emoji + 文字
- 趋势展望示意:圆圈 + 箭头
因为 macOS 的 sips 命令可以把 SVG 直接转成 PNG,最后转出来的图片可以在微信 CDN 上传。但说实话质量就是「看个意思」的水平。
后来设计了一套全新的 SVG 插画(我对这个迭代花的精力最多):
- 数据仪表盘:碳价折线图 + KPI 卡片 + 工具提示模拟,329KB
- 算力服务器透视:服务器机架 + 增长柱状图 + KPI 卡片,164KB
- 三线交汇预测:三条趋势线汇聚 + 未来预测气泡,273KB
这三张图全部通过 SVG 手动绘制坐标、路径、渐变色,不需要任何外部图像 API。它们被 sips 转成 PNG 后上传微信 CDN,失败时用 SVG data-URI 兜底。
这件事的核心启示:不依赖任何外部生图服务的情况下,仅用代码生成的矢量图像可以达到"插图级"质量——前提是你愿意花时间在 <path> 的贝塞尔曲线上。
第四回合:微信 AI 配图 + 混合策略
之后公众号后台推出了 AI 配图功能——开启后微信会在发布时自动为正文段落配图。测试发现效果尚可,封面仍用 Pollinations 生成(可以人工选图 prompt),正文留给微信处理。
当前管线采用了四张图一次性批量生成的策略:
- 封面 1 张:Pollinations 生图,上传后作为 thumb_media_id
- 正文 3 张:同样由 Pollinations 统一生成,上传微信 CDN 后嵌入正文段落
- 任何一张生成失败 → 尝试更短的通用提示词重试
- 都失败 → 跳过(让微信 AI 配图兜底)
这是 N 次试错后的最佳性价比方案:免费的先用,免费的用完就用微信自带的,都不强求。
第三轮:排版从「能看」到「好看」
内容和配图都有了,但文章体感很糙——大段文字堆在一起,数据缺乏视觉层次。
isDataLine 收窄
最开始做了一个功能:检测包含数字和单位的段落,自动用品牌色高亮数字。但条件设得太宽,任何带数字和单位的句子都触发。结果一篇文章里三分之一内容被标红——红色失去了注意力引导的意义。
修复策略是两级收紧:
- 基础条件:至少 2 个大数字 + 单位 + 段落长度小于 80 字符
- 强化条件:当检测到「同比」「环比」「增长」等数据关键词时,1 个数字也能触发
- 去掉之前「1 个数字 + 短段落」的宽松规则(误伤最多)
效果:数据卡从 20+ 降到了 9 个,仅真·数据段落触发红框标注。
视觉组件系统
在 mdToWeChatHtml 函数里逐步积累了一套排版组件:
- KPI 卡片:
> **标签**: 数值自动转为白底圆角卡片,大字突出数字 - 拉取引文:
> "..." —— 来源品牌色背景 + 弯引号装饰 - 容器块:
::: highlight/data/insight/warning标记特定区块 - 调色板系统:品牌红 #c00000 + 灰阶统一,全局 CSS 变量化
- CTA 结尾:红色圆角卡片 + 要点总结 + 转发语
这套组件通过 mdToWeChatHtml() 一个函数实现,输入是 markdown,输出是可发布的 HTML。组件设计上克制——不贪多,每种只做一种。
第四轮:质量门禁
发表了第一篇完整的管线文章后,第二天打开公众号后台发现图片裂了。原因是某个 CDN 上传成功了但返回的 URL 格式变了。
这种问题无法通过测试覆盖(因为涉及外部 API 响应格式),唯一能做的就是在创建草稿之前自动检查每个环节的输出。
article-quality-gate.mjs 做的事情:
- 文件存在性
- 标题长度(不能为空,不能太长)
- 摘要存在性
- 正文完整性(字数下限)
- 配图数量
- 图片可访问性(对每张图片 URL 发 HTTP HEAD 请求)
检查项不覆盖排版——排版问题靠预览 HTML 人工看。但所有可自动检查的项目在生成草稿前全部过一遍,发现问题可以原地修复而不是发布后修复。
这个门禁还有一个重要设计:以 --preview 模式运行时会自动触发门禁,验证通过后再去掉 --preview 正式创建草稿。不能跳过。
当前架构一览
整条管线经过六轮迭代后,最终的架构概览:
文章 markdown 文件
│
▼
┌──────────────────────────────────────────┐
│ publish.mjs │
│ │
│ 1. 解析 markdown 元信息(标题/摘要) │
│ 2. 配图管线(4 张批量生图) │
│ ├─ Pollinations.ai 生图 │
│ ├─ 短提示词回落(失败时) │
│ └─ 上传微信 CDN + 封面素材库 │
│ 3. 正文生成(buildArticleBody) │
│ ├─ 去掉标题候选区块 │
│ ├─ 去掉首行重复摘要 │
│ ├─ mdToWeChatHtml 渲染 │
│ │ ├─ 表格/列表/引用 │
│ │ ├─ isDataLine 数字高亮 │
│ │ ├─ KPI 卡片/引文/容器块 │
│ │ └─ CTA 结尾 │
│ └─ 嵌入正文配图 │
│ 4. 质量门禁(自动) │
│ 5. 创建草稿(cgi-bin/draft/add) │
│ └─ 返回 media_id │
│ │
│ 一条命令: │
│ node publish.mjs --md article.md │
└──────────────────────────────────────────┘
│
▼
公众号后台「草稿箱」→ 人工审核 → 发布 这个架构的特点是:局部迭代,整体稳定。每次改动只影响一个模块,管线入口永远是 node publish.mjs --md 文件名。
核心教训
迭代发生在线上,不是在规划里
这六轮迭代中,没有哪一轮是在开始之前就规划好的。每轮都是在发布后发现问题——有些读了预览 HTML 发现,有些在后台草稿箱里翻到,有些是读者告诉我的。
如果按传统的「先规划再开发」,代码只会停在第一版——因为第一版在理想条件下确实能跑。只有放到真实流水线上跑几轮,才会发现那些被理想条件掩盖的脆弱环节。
AI 协作改变了迭代成本
一般来说,一个人维护一个发布了 400+ 行 Node 脚本的迭代意愿是很低的——工作量看起来大,产出看起来小。但因为有 Sisyphus 做执行层,每次迭代的边际成本被压缩到几分钟对话。
这意味着:以前不值得修的问题,现在值得了。以前「捏着鼻子手动改一下吧」的问题,现在可以变成一行 prompt 解决。这种变化带来的不是效率提升,而是质量标准的迁移——你能容忍的「粗糙」边界在不断后退。
管线类项目不需要大版本
我们不叫它 v2 或者重写。它就是同一个文件,在持续地变好。没有大版本规划,没有架构重构计划,没有技术债务清理周。变化方式是:发现问题 → 修 → 测 → 下次发布自动用上新版本。
这听起来不像软件工程的正统做法,但对于一个人维护的生产管线来说,它可能是最优的——只要每次发布出来的文章比上一次更好,方向就是对的。
管线代码在 ~/Documents/wechat-publisher/publish.mjs,文章 markdown 在 ~/Documents/codex/suan_dian/drafts/。这个流程本身也是这条管线发布的——从 Sisyphus 起草到完成,中间没有人工干预。