ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用n8n搭建公众号自动写作到发布的全流程流水线

用n8n搭建公众号自动写作到发布的全流程流水线

如果每天要被公众号选题、排版、发布这些事反复打断,用n8n搭一条自动生成流水线,是性价比最高的解法之一。我折腾过好几套方案,最后把“素材输入-大模型写稿-样式排版-发布到公众号草稿箱”整体接到n8n里,基本实现了早上起来只看一眼、确认后点个按钮就能发文的程度。

先说需求:不是随便生成一篇水文就完事,而是要稳定、能控制格式、能接公众号后台接口。我对“自动生成”有个底线——AI负责初稿,最终发布前我要审核。所以工作流不是纯无人值守,而是自动生成草稿加人工确认。

1. 方案选型:为什么是n8n

做公众号的人都知道,最难的不是突然想写一篇的时候写不出来,而是稳定产出,同时控制时间成本。我一开始想写Python定时任务,后来发现n8n更合适,因为它把流程里最琐碎的鉴权、重试、格式转换都变成了可视化节点,不用自己维护一堆脚本。

1.1 自己写脚本和n8n的边界

最朴素的做法是写一个Python脚本:定时跑、调大模型、再调公众号接口。好处是灵活,缺点是公众号access_token的获取和缓存、错误重试、内容解析这些逻辑都要自己维护,一旦接口升级,脚本可能没有任何提示就挂了。n8n的优势在于把每个环节变成节点:运行时你能看到每一步的输入输出,哪一步失败就是哪个节点报红,改起来不用从堆栈里找问题。

我做过对比,拿“定时任务+请求大模型+上传草稿”这个场景来看:

方案学习成本维护成本可视化与调试费用
Python自建高高弱服务器费用
n8n自建中低强服务器费用,软件免费
商业自动化平台低低强订阅费,可能按执行次数收费

n8n不是唯一选择,但它在“可视化”和“自定义能力”之间平衡得最好。它自己就是Node.js服务,你可以用Docker装到自己的机器上,数据不走别人的服务,这一点对做内容的人来说很重要。

1.2 n8n在自动生成场景里的优势

具体到公众号场景,n8n的核心优势有三个。

第一,HTTP Request节点足够强。大模型接口、公众号接口都是REST API,只要会配置JSON请求体和鉴权头,剩下的都能在编辑器里完成。第二,内置定时任务和Webhook。定时更新、手动触发都能实现。第三,节点之间天然传递JSON,生成的文章标题、正文、摘要可以作为一个个字段在流程里流转,再用Code节点做清洗和格式转换。

对我来说最重要的是错误处理。n8n每个节点可以配置失败后的重试次数,还可以用IF节点判断返回码走不同分支。比如公众号返回45009(调用过于频繁)时,我可以让它等一分钟再试,而不是整个流程直接失败。

1.3 整体架构拆解

这套自动生成流程的架构可以分成四段。

输入端负责触发,常见的有Schedule Trigger定时触发、Webhook手动触发、RSS订阅触发。我常用的是Schedule Trigger,并留了一个Webhook用于临时加推。

处理端是大模型生成。我在这里把“标题-正文-摘要”一次性生成好,并且要求模型返回JSON,方便后续节点引用。

输出端是公众号API,包含获取access_token、创建草稿、提交发布三个调用。因为我要人工确认,所以发布这一步拆成两段:先创建草稿并推送提醒给我,等我确认后再通过另一个Webhook触发真正发布。

中间还有一些辅助节点,比如图片处理、格式转换、通知消息。整个流程在n8n里表现得非常线性:触发器 -> 生成文章 -> 清洗数据 -> 转成公众号HTML -> 获取token -> 创建草稿 -> 通知 -> 等待确认 -> 发布。

从需求出发选架构,千万不要一上来就把所有能力堆进去,后面我会讲到怎么一步步搭。

2. 核心模块与关键配置

这一章把每个核心节点拆开讲,先说为什么这么配,再说容易踩的坑。我把流程里最重要的四个模块单列出来:触发器、AI生成、排版图片、公众号发布。

2.1 触发器节点:定时与主动触发

在n8n里添加一个Schedule Trigger节点,设置成每天固定时间执行。cron表达式不熟的话,UI里有“每分钟/每小时/每天”的快捷选项。我实际设置了“每天07:10”,尽量避开公众号后台和大模型接口的高峰。

这里要特别提醒时区问题。我的云主机默认是UTC,n8n的Schedule Trigger默认也按服务器时区走。第一次跑完发现实际执行时间比预期晚了8小时,导致后台推送时间是下午。解决办法是在n8n的启动环境里指定时区,用Docker方式时增加环境变量GENERIC_TIMEZONE=Asia/Shanghai,同时在触发节点的配置里选择正确时区。

除了定时,我还挂了两个Webhook Trigger:一个用于“立即生成一篇”,另一个用于“确认发布”。前者适用于临时想加更,后者是整个流程的人工闸门。Webhook URL生成后,可以用公众号后台的素材变化或自己手机端触发,只要能把Webhook地址调起来就行。

2.2 AI生成节点:模型选择与提示词设计

n8n没有内置某一个模型的节点,但这反而是好事。我用HTTP Request节点直接调用大模型的Chat接口,原因是接口官方文档写得清楚,n8n里只需要配置一个POST请求。

请求体长这样:

{ "model": "<具体使用的模型名>", "messages": [ { "role": "system", "content": "你是公众号编辑,写文章时注重逻辑清晰,观点明确,不出现复制粘贴感。" }, { "role": "user", "content": "请根据话题「{{ $json.topic }}」生成一篇文章,返回JSON,字段为title、summary、content。content是Markdown格式,正文约1200字,以小标题分段。不要返回其他说明。" } ], "temperature": 0.7 }

关键是“让AI输出JSON”。如果不做约束,模型会连解释和代码块一起返回,后续解析很痛苦。我通常会在提示词里明确写“返回纯JSON,不要用Markdown代码块”,并且在Code节点里再做一次清洗,双保险。

温度要分场景。生成标题可以调到0.9,让它稍微发散;正文我一般用0.7左右,太低了容易车轱辘话,太高了容易跑题。这里的“温度”对应的是采样随机性,并非质量高低,所以不要误区中。

另一个容易忽略的是单次请求长度。一篇1200字的正文,加上系统提示词和返回JSON,预料消耗可能在3000 token上下。如果你的话题背景资料很长,建议拆成两段:第一步生成标题和提纲,第二步再把提纲和素材一起给模型生成正文。不要让模型在一段提示词里同时消化太多长文本,否则结果容易失控。

2.3 排版与图片处理:从Markdown到公众号HTML

公众号API要求正文字段是HTML字符串,不能直接传纯文本。我的做法是先让模型返回Markdown格式正文,然后在Code节点里把Markdown转换成简单HTML。

转换逻辑不用很复杂。我写了一个简化版函数,在n8n Code节点里运行:

function md2html(md) { let html = md .replace(/^### (.*)$/gm, '<h3>$1</h3>') .replace(/^## (.*)$/gm, '<h2>$1</h2>') .replace(/^# (.*)$/gm, '<h2>$1</h2>') .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') .replace(/!\[(.*?)\]\((.*?)\)/g, '<img src="$2" alt="$1" />') .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>') .replace(/\n{2,}/g, '</p><p>') .replace(/\n/g, '<br />'); return '<p>' + html + '</p>'; } const item = $input.first().json; const contentMd = item.content; const contentHtml = md2html(contentMd); return [{ json: { title: item.title, summary: item.summary, contentHtml: contentHtml } }];

这里有两个地方容易出错。第一是内容中如果含有代码块,我这种简单替换会破坏格式,所以我在提示词里要求AI少写Markdown代码块,如果确实需要代码,就用行内代码;第二是图片标签,公众号HTML支持img,但图片URL不能是任意外链,必须来自公众号素材库,否则发布后无法显示。更稳妥的做法是在提示词里不给AI加图片URL,正文里只放文字和结构,封面图单独处理。

图片处理我推荐两种路径。如果对封面图要求不高,可以在公众号后台手动上传一张图片,然后调用“获取素材列表”接口拿到该图片的media_id,填入流程参数。如果一定要全自动,需要把生成的图片上传到素材库,再拿media_id。这个上传接口要用multipart/form-data,n8n的HTTP Request节点可以配置请求体为Form-Data,但要注意必须先把二进制文件准备好,不能只传URL。实操中我建议第一步用“HTTP Request下载图片到临时文件”,第二步再上传。如果个人订阅号权限受限,就先走后台上传的“半自动”路径。

2.4 公众号发布节点:接口调用与鉴权

公众号后台接口的调用凭证是access_token,有效期两小时。调用流程是:

第一步,用AppID和AppSecret请求获取token,地址是公众号开发文档里的/cgi-bin/token接口。n8n里直接配置GET请求,把AppID和AppSecret作为Query参数。返回结果里有access_token和expires_in。

第二步,创建草稿。接口是/cgi-bin/draft/add,需要把文章信息放在articles数组里,里面字段包括title、author、digest、content、thumb_media_id等。这里必须注意,content字段要是完整的HTML字符串,如果直接从JSON读取并嵌入,可以这样写:

{ "articles": [ { "title": "{{ $json.title }}", "author": "某编辑名", "digest": "{{ $json.summary }}", "content": "{{ $json.contentHtml }}", "thumb_media_id": "{{ $json.thumbMediaId }}", "need_open_comment": 1 } ] }

这里n8n表达式会自动把contentHtml的内容注入到JSON,但需要注意的是,公众号API要求content是HTML,如果里面包含引号或特殊字符,直接用JSON表达式可能在HTTP Request里被转义。我建议在Code节点里把整个文章对象组装好,再交给HTTP Request的JSON Body,避免表达式转义问题。

第三步,从草稿箱提交发布。发布接口是/cgi-bin/freepublish/submit,参数是刚刚创建草稿返回的media_id。调用成功后公众号会进入发布流程,可能在几分钟后正式发布,也可能需要状态查询。

还有一点要提醒:access_token在n8n流程里必须只取一次。如果你在创建草稿节点和发布节点分别都调用获取token接口,后一次调用会刷新前一次,导致前一次已经发出的请求报“凭证无效”。正确做法是第一个HTTP Request节点获取token后,把它作为{{ $json.access_token }}传递给后续所有需要鉴权的节点,不要再单独获取。

3. 实战搭建:从空工作流到自动发布

空谈没意思,我直接用一次完整搭建过程说明。我假设你已经有n8n实例,并且注册好了公众号,拿到了AppID和AppSecret。

3.1 初始化n8n服务

如果你还没有n8n,最省事的是用Docker跑。命令是:

docker run -d \ --name n8n \ -p 5678:5678 \ -e GENERIC_TIMEZONE=Asia/Shanghai \ -v n8n_data:/home/node/.n8n \ n8nio/n8n

启动后打开http://服务器IP:5678,第一次会让你创建管理员账号。值得注意的是,如果你把n8n部署在云服务器上,记得在防火墙和安全组里放行5678端口。公众号接口调试时会校验来源IP,这个IP必须是公网出口IP,所以建议直接把n8n放到公网可达的云主机上,别用自己的家庭宽带折腾。

3.2 搭一条最简内容生成链

先建一个工作流,放入Schedule Trigger节点,配置每天执行。再放一个HTTP Request节点,对接大模型的Chat接口,请求体里引用一个“话题”变量。没有话题来源时,可以在HTTP Request节点Body里直接写死一个默认主题,先把流程跑通。

跑通后可以看到返回的JSON里有大模型输出的内容,但往往带有多余字符。我要在它后面接一个Code节点,把正文拆出来并清洗。Code节点的核心逻辑是:从响应里拿到content字符串,再用正则匹配第一对花括号,JSON.parse之后返回关键字段。这样后续节点就能拿到干净的title、summary、content。

为了验证,我通常在这个阶段加一个“Set”节点,把title和summary打印到日志,或者用“Output”节点查看。n8n里可以直接点击节点右上角的“执行节点”按钮测试单个节点,这比运行整个工作流快很多。

3.3 接入公众号后台

有了干净的文章JSON,接下来接公众号。先在n8n里建一个HTTP Request节点获取access_token。我建议把这个节点放在流程较早的部分,并且让后续节点都引用它输出的access_token字段。

配置GET请求时,参数对应关系是:grant_type=client_credential,appid=你的AppID,secret=你的AppSecret。返回结果是:

{ "access_token": "xxxxx", "expires_in": 7200 }

下一步调创建草稿接口。这里需要一个thumb_media_id。如果你已经有了封面图media_id,直接把它写成一个变量;如果没有,可以在后台素材库上传一张图,然后用“获取素材列表”接口查一下,把第一个图片素材的media_id复制出来。

草稿创建成功后,接口会返回media_id,这个ID是草稿编号。先不要急着自动发布,我建议把整个流程停在这个位置,通过一个消息通知节点把草稿链接或文章标题推送到手机。确认没问题后,再继续配置发布接口。

3.4 把人工确认和发布接上

很多自动生成项目“死”在最后一步,因为未经审核的内容被直接发出去了。我给流程加了一个人工确认闸门:在创建草稿并通知之后,流程进入一个Wait节点,等待一个Webhook回传的“确认”信号。

具体做法是:工作流A负责定时生成草稿并通知;工作流B监听一个Webhook,当我在手机上请求这个Webhook时,工作流B会去查询最新的草稿media_id,然后调用发布接口。这比在同一个流程里写死更灵活,也方便我临时决定“这篇不发了”。

如果你觉得维护两个工作流麻烦,也可以在同一个流程中设置Wait节点。n8n的Wait节点能等待Webhook响应,自定义等待条件,等确认信号后再继续执行后续的发布节点。这样整个链路还是一张图,只是中间多了一个“人”。

配置完成后,先手动执行一次完整流程,确认每一步都成功,再启用Schedule Trigger的激活开关。以后每天文章会在指定时间自动生成草稿,我只需要在手机上点一下确认。

4. 常见问题与排查技巧实录

这部分是我实际跑流程过程中印象比较深的坑,按发生频率排个序。

4.1 大模型返回的JSON解析失败

最常遇到的是模型输出的字符串里有Markdown代码块,例如开头是json,结尾是。直接用JSON.parse会报错。我的办法是在Code节点里加一步正则提取,只取第一个{...}部分,同时把开头的说明文字去掉。示例:

const raw = $input.first().json.content; const start = raw.indexOf('{'); const end = raw.lastIndexOf('}'); const jsonStr = raw.substring(start, end + 1); return [{ json: JSON.parse(jsonStr) }];

这样处理之后,基本不会再因为格式问题卡住。如果偶尔还是解析失败,可以再试一次“重新生成”。n8n里可以用IF节点判断Code节点是否抛错,抛错就走重试循环,重试次数控制在三次以内。

另外一个坑:模型虽然返回了JSON,但content字段是空的或者特别短。这多半是Max Tokens设置不够。生成时被截断的部分往往直接结束在JSON字符串中,导致解析失败。我把Max Tokens设到2500以上,正文有保证才够。

4.2 access_token被反复刷新

我之前提到过,在一个流程里多次获取token会导致旧的token失效。公众号有一个机制:每次取得新token,旧token在文档中说明还会有五分钟有效期,但在高并发场景下不稳定。为了不在发布时报40001,我在流程中专门加了一个判断:第一步先查缓存,如果全局变量里有未过期的token就直接用,没有再去请求。

在n8n里最简单的“缓存”方式是用一个独立执行的工作流去刷新并存储token到某个文件或Redis;如果不想引入外部依赖,可以接受每次执行流程都新取一次,但确保整条链路只有最开始那一次获取。后续节点引用同一个access_token字段即可。

4.3 调用频率限制

大模型API和公众号API都不喜欢被集中调用。个人场景虽然不容易触发,但如果你配置了多个AI节点、或者流程失败后反复重试,就可能撞限。

对于大模型接口,如果遇到429或限流错误,建议在HTTP Request节点的“Resource”设置里打开重试,并设置指数退避。n8n可以配置“Retry on Fail”的固定次数,但默认间隔比较短,不如在失败分支上加一个Wait节点,等待60秒再重试。

对于公众号接口,遇到45009错误时,不要无脑重试。公众号频率限制往往是分钟级或每日级,等几秒没用。我遇到这种情况会直接让流程停下来,并发送一条失败通知,等下一个调度周期再跑。因为自动生成的文章本来就不是强时效内容,少发一篇比发一篇失败状态要好处理。

4.4 封面图和图片外链问题

公众号文章正文里的图片URL必须是可以公开访问且可以上传到素材库的地址。如果你让AI生成一个“图片占位符”URL,那么上传后很可能是404,因为模型臆造了地址。正确做法是:在提示词里明确不要包含图片,封面由专门步骤处理。

上传图片到素材库在n8n里的配置有些特殊。获取素材列表接口是/cgi-bin/material/batchget_material,可以拿到已有的图片media_id。如果你需要自动上传新图片,必须用POST multipart/form-data,字段名是media,同时附带type=image。在n8n的HTTP Request节点中,“Body Content Type”选“Form-Data”,然后把文件路径或二进制字段填进去。如果图片来自互联网,先用一个HTTP Request节点下载图片并输出二进制数据,再作为上游数据传给上传节点。

一个更省事的替代方案是:所有封面图都在公众号后台手动上传,只让n8n负责文章正文生成。这样整个流程里不需要处理图片上传,稳定性高很多。适合个人号每天输出频率不高的情况。

4.5 内容被平台过滤或样式异常

公众号后台会过滤不安全标签,比如script、iframe,还有非白名单的CSS。如果你直接用大模型生成的完整HTML,很可能发出来之后样式错乱。我在Code节点里做了两件事:一是用正则把script和iframe删除;二是把大模型输出中的标题等级限制在h2/h3,并要求所有样式使用行内style。经测试,公众号编辑器支持的常用标签是section、p、span、img、a、blockquote、ul、ol、li,行内样式可以用font-size、color、background、margin这类基础属性。

如果你嫌正则维护麻烦,可以让大模型专门输出“公众号适配HTML”,提示词里写明“只使用section和p标签,不要内嵌样式,所有强调使用strong或em”。然后Code节点只做少量转换。实践下来,这套方式比让模型输出完整HTML更可控。

5. 扩展玩法与实际维护心得

流程跑通只是开始,真正有价值的是怎么让它长期稳定为你服务,以及怎么扩展。

5.1 多账号和定时发布队列

n8n可以同时管理多个公众号配置。做法有两种:一种是在工作流里用“Loop Over Items”节点遍历多个公众号配置,分别调用接口;另一种是每个公众号一个工作流,公用的生成逻辑抽成一个子流程。我个人推荐后者,因为不同账号的IP白名单、AppSecret不同,拆开更好排查。

如果你希望在固定时间点发多篇,可以在内容生成后写入一个发布队列,再让“定时发布”工作流从队列里取一条发出去。n8n里可以用数据库节点或简单用Google Sheets/Airtable做队列。没有外部数据库时,把待发布文章写成JSON文件也存在云主机,发布后再删除。

5.2 给自动生成加一个人工审核环

自动生成不意味着无人值守。我的策略是“自动生成草稿,人工确认发布”,而不是直接自动发布。这样做理由是AI生成的内容虽然有模板和约束,仍可能有事实错误、表达偏差或敏感表述,而这些错误一旦发布到公开账号上,影响比晚发一小时大得多。

人工审核环可以用Webhook实现。n8n生成草稿后,通过通知机器人把标题和摘要推到手机,我点一下“确认”按钮,手机就回调n8n的Webhook,流程继续执行发布。如果我不点,文章就安安静静躺在草稿箱里,不会造成事故。

5.3 我的实际维护体会

最后说几句个人感受。第一,不要追求一次把流程做得完美,先让一条“生成草稿并通知我”的链路跑起来,再加发布能力。第二,n8n的执行日志非常有用,每次失败我都会去翻节点输出,而不是盲目改配置。第三,大模型API的支出要控制,可以把系统提示词和正文生成拆开,让模型在上下文里少读重复素材,能省不少token。

我跑这套流程跑了三个多月,最深的体会是:自动化的价值不在“少动手”,而在“把固定动作变成标准动作”,让我把精力留给真正需要判断的事情。如果你也打算入坑n8n,建议初始化时就把时区、重试、通知这些基础设施配好,后面会省很多事。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进