到这一步,稿子已经写好了、排版也好了,只剩最后一段路:把它送到公众号里。本章走的是官方接口这条路(建草稿箱),而不是模拟人点界面;而这条路的尽头有一个权限现状造成的天然停靠点——机器只能把稿子送进草稿箱,最后一下必须人点。 这不是妥协,这正是本章想要的设计。
3.4.3 产出了 body_html,本节把它连同标题、摘要、封面、声明一起送进公众号。这是本章的出口,也是全章唯一一次【真正和平台对话】。它是 1.6.2【RPA 获取 Agent 结果】的镜像——那一步是【取回】,这一步是【送出】。
一、两条路:接口路与界面路
把内容送进公众号,有两条完全不同的路:
| 接口路(本节主路) | 界面路(备选) | |
|---|---|---|
| 怎么送 | 调用官方开放接口 draft/add |
RPA 打开后台,模拟人填标题、贴正文、传封面、存草稿 |
| 需要什么 | 认证的公众号 + AppID/AppSecret + IP 白名单 | 一个能登录后台的浏览器 |
| 稳定性 | 高(接口稳定,不受页面改版影响) | 中(后台一改版就要重做) |
| 内容怎么进 | 直接把 HTML 字符串交给接口 | 要靠剪贴板粘贴富文本(3.4.3 第三节) |
| 适合谁 | 有认证公众号、要做长期自动化的人 | 没有认证号、或只是偶尔跑一次的人 |
不是【哪个技术更高级】,而是——接口路是【以程序的身份】和平台说话,界面路是【拿着你的账号密码】去模拟人。 前者:平台知道你在做什么,有明确的调用规则与限额; 后者:平台会把它当作【人】的行为来看待——而这恰好是平台风控最敏感的地方。 能走接口就走接口。 这不仅是稳定性的问题,也是合规姿态的问题。
二、接口路:四步舞
一次完整的建草稿,是四个动作按顺序做:
① 换票 获取 access_token(拿 AppID + AppSecret 去换一张两小时有效的通行证)
│
▼
② 传封面 上传封面图到【永久素材】,拿到 thumb_media_id
│
▼
③ 传正文图(如果正文里有图)上传到微信图床,把 HTML 里的 src 换成返回的地址
│
▼
④ 建草稿 调 draft/add,把标题/摘要/正文/封面/media_id 一起交上去
│
▼
拿到草稿 media_id → 去后台【草稿箱】就能看到
四步里有三个容易忽略的点:
- ① 的票有两小时有效期,要缓存复用。 每次都重新取票,会浪费调用次数,还可能被限流。做法是:取一次存起来,快过期了再取;
- ② 必须是【永久素材】,不能用【临时素材】。 用错了会报错(见第五节的错误码 40007),而且错误提示很不直观;
- ③ 正文里的每一张图都要单独上传。 接口不接受外链图,这是 3.4.3 那个【坑三】在接口侧的对应版本。
三、参数硬限制:一张必须背下来的表
接口对字段有硬性的长度与格式限制。超一个字,整个请求失败:
| 字段 | 限制 | 备注 |
|---|---|---|
title 标题 |
不超过 64 字 | 建议控制在 20 字内(太长在卡片上会被截断) |
digest 摘要 |
不超过 120 字 | 不填时平台自动截取正文开头 |
content 正文 |
不超过 10 万字符 | 这里算的是 HTML 字符数,转成 HTML 后会比 Markdown 长不少 |
thumb_media_id 封面 |
必须是永久素材的 media_id | 关联的那张图必须是 JPG/JPEG |
author 作者 |
一般不超过 8 字 | 可按需填写 |
两条实用经验:
- 正文字符数是【转成 HTML 之后】算的。 一段 2000 字的 Markdown 文章,转成带内联样式和图片标签的 HTML 后,字符数可能是原文的两三倍——长文要留意这个上限;
- 标题 64 字是【接口上限】,不是【推荐值】。 公众号的推送卡片宽度有限,标题超过一定长度就会在卡片上被截断,读者看不到后半句。
四、错误码:四个最常遇到的
调用接口失败时,微信返回的是一串错误码。下面四个覆盖了绝大多数情况:
| 错误码 | 含义 | 真正的原因 | 怎么修 |
|---|---|---|---|
| 40165 | 无效 IP | 你的出口 IP 不在白名单里 | 把当前公网 IP 加进后台的 IP 白名单 |
| 40007 | 无效的媒体 ID | 封面用了临时素材 | 改用【上传永久素材】接口重新上传 |
| 48001 | API 功能未授权 | 账号没有这个接口的权限(未认证/权限被回收) | 换认证账号,或换一条路(界面路) |
| 40008 | 消息长度超限 | 标题/摘要/正文超了长度 | 按第三节的表压缩 |
因为它跟代码没关系——是你网络出口的公网 IP 变了(换网络、重启路由器、运营商重分配都会变)。 结论:写自动化流程时,要把【IP 白名单】当作一个会变的配置来管理,在流程里加一步【调用失败且错误码是 40165 时,提示人去更新白名单】——让它变成一个可读的提示,而不是一个看不懂的报错。
五、⚠️ 2026 现状:发布权限已被收回——而这正是本章想要的
这是本节最重要的一条现状,它把本章的设计从【谨慎】变成了【必然】:
自 2025 年 7 月起,微信官方已回收【个人主体账号】与【未认证企业账号】的 freepublish(提交发布)接口权限。
所以当前的权限格局是这样:
| 能力 | 个人订阅号 | 认证过的账号 |
|---|---|---|
获取 access_token |
可以 | 可以 |
| 素材管理(传图) | 可以 | 可以 |
建草稿(draft/add) |
可以 | 可以 |
发布(freepublish) |
已回收 | 可以 |
这张表恰好说明了三件事:
- 本章的流程【机器建草稿、人点发布】,不是保守,而是接口权限本来就这样——约束与现状完全吻合;
- 对个人号来说,
draft/add就是这条路的终点。这一步之后,发布只能人在后台点; - 对认证号来说,接口上是可以继续走的,但请不要走——理由在 3.1:自动发布落在平台打击【AI 托管运营】的范围内,而且 AI 生成内容需要人确认并主动声明。
3.1 里定下的定盘星(机器送到草稿箱门口、最后一下交给人),当时是从【合规】角度推出来的;到了本节,你会发现它同时是【权限】的现状。 当一条合规的设计恰好也是最可行的实现时,说明这个设计找对了。 反过来也成立:网上那些教你【全自动发布】的教程,之所以要用各种绕路的办法,正是因为它们同时撞上了合规和权限两堵墙。
六、【AI 生成内容声明】放在哪
这是本章唯一一个【法规要求但接口里没有对应字段】的东西。接口的请求体里没有【声明】这一项,所以要分两个落点:
| 落点 | 形式 | 谁做 | 说明 |
|---|---|---|---|
| 落点一:正文显式声明 | 在正文末尾加一段固定文字(例如【本文由 AI 辅助创作,经人工编辑与核对】) | RPA 自动加 | 这是显式标识的落地——用户能明显感知到 |
| 落点二:发布时主动声明 | 在后台发布环节,勾选平台的 AI 生成内容声明选项 | 人做 | 平台公告明确要求:发布 AI 生成合成内容需主动声明 |
只做落点一:正文里写了声明,但平台的声明字段没勾——不符合平台的要求;
只做落点二:平台勾了,但读者在文章里看不到——显式标识的意义就弱了;
两个都做,才是完整的:正文里让人看得见,平台字段上让机器读得到。
这也是为什么本节的预检器里,ai_declare 是一个【不确认就阻断】的必填项——它是法规要求落在流程里的那个点。
七、密钥安全:三条不能破的规矩
AppSecret 是你公众号的钥匙。泄露的后果是:别人可以以你的名义建草稿、发消息、调用你的接口。 三条规矩:
| 规矩 | 具体做法 |
|---|---|
| 不要写进流程文件 | 不放明文在 RPA 流程、代码、脚本里;放环境变量或凭据管理 |
| 不要提交到代码仓库 | 用 .gitignore 挡住配置文件;已经提交过的要立即更换密钥 |
| 不要传给第三方工具 | 除了官方接口,不要把 AppSecret 交给任何第三方服务(包括各种【代发】工具) |
并且配合两条:
- 后台的 IP 白名单一定要开——它是密钥之外的第二道门;
- 定期更换密钥,尤其是在换电脑、共用过代码、人员变动之后。
八、动手 Demo:建草稿前的安全预检
为什么要有这一步:接口调用的失败是整批失败的——一行数据有问题,整个请求返回错误。所以在真正提交之前,把所有字段按硬限制过一遍,比【提交失败再重试】划算得多。
下面这段代码可以直接跑。它做三件事:① 按硬限制预检;② 干跑(把要提交的请求体组装出来但不发送);③ 检查流程文件里有没有明文密钥。
# -*- coding: utf-8 -*-
"""3.4.4 Demo:建草稿前的安全预检(dry-run,真发之前先核对)"""
import json
import re
TITLE_MAX, DIGEST_MAX, CONTENT_MAX = 64, 120, 100000
IMG_OK = "mmbiz.qpic.cn"
def precheck(row):
p = []
title = row.get("title", "").strip()
if not title:
p.append("标题为空")
elif len(title) > TITLE_MAX:
p.append("标题 %d 字,超 %d 字上限" % (len(title), TITLE_MAX))
digest = row.get("digest", "").strip()
if not digest:
p.append("摘要为空(平台会截正文开头,效果差)")
elif len(digest) > DIGEST_MAX:
p.append("摘要 %d 字,超 %d 字上限" % (len(digest), DIGEST_MAX))
content = row.get("body_html", "")
if len(content) > CONTENT_MAX:
p.append("正文 %d 字符,超 %d 上限" % (len(content), CONTENT_MAX))
for src in re.findall(r'<img[^>]+src="([^"]+)"', content):
if IMG_OK not in src:
p.append("正文图片非微信图床:%s(发布后变裂图)" % src[:36])
break
cover = row.get("cover_path", "")
if not cover.lower().endswith((".jpg", ".jpeg")):
p.append("封面图必须是 JPG,当前:%s" % (cover or "(空)"))
if row.get("ai_declare") != "已确认":
p.append("AI 生成内容声明未确认(法规要求,必须人工确认)")
return p
实际运行输出(真跑,不是示意):
==================================================================
建草稿前安全预检(dry-run)
==================================================================
[通过] id=001 → 可以进 draft/add
[阻断] id=002
- 标题 70 字,超 64 字上限
[阻断] id=003
- 摘要 140 字,超 120 字上限
[阻断] id=004
- 正文图片非微信图床:https://cdn.example.com/a.png(发布后变裂图)
[阻断] id=005
- 封面图必须是 JPG,当前:cover_005.png
[阻断] id=006
- AI 生成内容声明未确认(法规要求,必须人工确认)
[阻断] id=007
- 正文 100007 字符,超 100000 上限
------------------------------------------------------------------
通过 1 行,阻断 6 行
==================================================================
干跑:id=001 将要提交的请求体(不含真实 token)
==================================================================
{
"articles": [
{
"title": "玄关收纳:我家最后只留下三件东西",
"author": "波仔",
"digest": "把玄关收拾成只剩三件东西的清爽空间,先减后加,量好尺寸再买。",
"content": "<p style=\"font-size:15px;\">玄关永远堆着鞋和快递盒。</p><img src=\"https://mmbiz.qpic.cn/mmbiz_jpg/xxx/0?wx_fmt=jpeg\">",
"thumb_media_id": "「上传封面后回填」",
"show_cover_pic": 1,
"need_open_comment": 0,
"only_fans_can_comment": 0
}
]
}
...(正文略,共 104 字符)
==================================================================
密钥卫生检查:流程文件里有没有明文 AppSecret
==================================================================
[X] 发现疑似明文密钥:7f3a9c2e…
→ 必须移到环境变量或凭据管理里,不能写进流程文件
读结果,四个值得注意的地方:
- 七行数据里六行被拦住——这说明硬限制不是理论上的东西,它是每天都会踩的。而且每一条被拦的原因都不一样(长度、图床、格式、合规、上限),说明预检项要一项不落;
id=006被拦的原因是【AI 声明未确认】而不是任何技术问题——合规项和技术项在同一条流水线上,用同一种方式拦停。这是本章设计的一个特征;- 干跑的输出里
thumb_media_id是占位符——干跑的意义就在这:在真正上传封面之前,先把请求体的骨架打出来核一遍,结构错了不用等接口报错就知道; - 密钥检查抓到了明文密钥——注意它不是靠报错发现的,而是主动扫出来的。这一类问题不会让流程失败,只会让流程【悄无声息地危险】。
九、备选方案:界面路怎么做
如果你没有认证公众号(或者暂时不想配 AppID/IP 白名单),可以走界面路:
① RPA 打开公众号后台 → 内容管理 → 草稿箱 → 新的创作 → 图文消息
② 填【标题】输入框
③ 填【作者】
④ 把 3.4.3 转换好的 HTML 放进剪贴板的 HTML 槽位 → 在正文区粘贴
⑤ 上传封面图(后台有上传控件)
⑥ 检查一遍 → 存为草稿
界面路的三个注意点:
- 正文粘贴必须走【富文本】——把 HTML 当纯文本打进去,你会看到满屏尖括号(3.4.3 第三节讲过);
- 图片上传要用后台的控件,不要自己拼 HTML——让后台把图片传到它的图床,这比接口路里的【手动上传正文图】省事;
- 每一步之后都要【回读校验】——界面的失败是静默的(粘贴没成功、图传了一半),不回读你发现不了。
30 秒实验
假设你手上有一个认证过的服务号(所以 freepublish 权限是有的),问你一个问题:
你会不会把流程做成【自动发布】?
答案:不要。 三个理由:
- 合规:自动发布落在平台打击【AI 托管运营】的范围内(3.1);
- 法规:AI 生成合成内容应当由发布者主动声明——这个动作需要人;
- 风险:发布是不可撤销的(草稿可以删,发出去的推文只能撤回,且已经被推到订阅者的消息列表里了)。
结论:有权限不等于应该用。 【能做到】和【应该做】之间隔着合规与风险判断——这是本章从头到尾都在强调的一件事。
常见误区
- 【既然有接口,那就全自动发布】——错。 权限上(个人号已被回收)与合规上(AI 托管运营、AI 内容需人确认声明)都不成立。机器建草稿、人点发布,是本章的定盘星。
- 【AppSecret 写在流程里方便,反正只有我自己用】——错。 流程文件会备份、会分享、会同步到云端。泄露 AppSecret 等于别人可以以你的名义操作公众号。 放环境变量或凭据管理,并且把 IP 白名单打开作为第二道门。
- 【封面图随便传一张就行,PNG 更清晰】——错。 封面必须是 JPG/JPEG,且必须上传到永久素材(不是临时素材)。这两条都会导致调用失败,而错误提示未必直观。
- 【正文里的图片可以用外链】——错。 接口只接受微信图床的地址;外链图在发布后会变成裂图。正文里每张图都要单独上传。
- 【失败了重试就行,不用预检】——错。 接口调用是整批失败的:一行超长,整批返回错误。预先按硬限制过一遍,比反复重试划算得多——本节的 Demo 就是这件事。
自测三问(点开对答案)
1. **接口路和界面路的本质差别是什么?为什么说【能走接口就走接口】?** 本质差别是**身份**:接口路是**以程序的身份**和平台说话(有明确的规则、限额与日志),界面路是**拿着你的账号密码去模拟人**——**而模拟人恰好是平台风控最敏感的地方**。而且界面自动化**页面一改版就要重做**,接口相对稳定。所以能走接口就走接口:**既是为了稳定性,也是合规姿态更好。** 2. **为什么说【微信 2025-07 回收发布接口】这件事,反而印证了本章的设计?** 因为本章的定盘星(**机器送到草稿箱门口、最后一下交给人**)当初是从**合规角度**推出来的;而微信回收个人号与未认证账号的 `freepublish` 权限,让**权限现状正好也是这个形状**。 **当合规的设计恰好也是最可行的实现时,说明设计找对了。** 反过来印证:网上那些【全自动发布】的教程之所以要绕路,正是因为同时撞上了合规与权限两堵墙。 3. **AI 生成内容声明为什么要【两个落点】,分别是什么?** **落点一:正文末尾的显式声明**(RPA 自动加一句固定文字)——让**读者**看得见;**落点二:后台发布时勾选平台的声明选项**——让**平台**读得到(平台公告明确要求发布 AI 生成合成内容需主动声明)。 只做落点一 → 平台的声明字段没勾,不符合要求;只做落点二 → 读者看不到显式标识。**两个都做才完整**——这也是预检器里 `ai_declare` 不确认就阻断的原因。- ☐ 下一节(3.5 本章小结)
相关笔记
- 排版 GPT 创作结果 —— 本节要提交的 HTML 从哪来,以及外链图的另一个版本
- 公众号创作智能助手场景概述 —— 两条红线与定盘星的出处
- RPA上传小红书 —— 上一章的出口(只能走界面),对照看接口路的差别
- 技术融合价值 —— 为什么要把 Agent 与 RPA 接起来
- 🤖 Agent+RPA实战 · 索引 —— 全课路线图