🤖 第三章 应用实战:公众号创作智能助手
📄 一句话概括

到这一步,稿子已经写好了、排版也好了,只剩最后一段路:把它送到公众号里。本章走的是官方接口这条路(建草稿箱),而不是模拟人点界面;而这条路的尽头有一个权限现状造成的天然停靠点——机器只能把稿子送进草稿箱,最后一下必须人点。 这不是妥协,这正是本章想要的设计。

📝 本节和前面几节的关系

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 → 去后台【草稿箱】就能看到

四步里有三个容易忽略的点:

  1. ① 的票有两小时有效期,要缓存复用。 每次都重新取票,会浪费调用次数,还可能被限流。做法是:取一次存起来,快过期了再取;
  2. ② 必须是【永久素材】,不能用【临时素材】。 用错了会报错(见第五节的错误码 40007),而且错误提示很不直观;
  3. ③ 正文里的每一张图都要单独上传。 接口不接受外链图,这是 3.4.3 那个【坑三】在接口侧的对应版本。

三、参数硬限制:一张必须背下来的表

接口对字段有硬性的长度与格式限制。超一个字,整个请求失败:

字段 限制 备注
title 标题 不超过 64 字 建议控制在 20 字内(太长在卡片上会被截断)
digest 摘要 不超过 120 字 不填时平台自动截取正文开头
content 正文 不超过 10 万字符 这里算的是 HTML 字符数,转成 HTML 后会比 Markdown 长不少
thumb_media_id 封面 必须是永久素材的 media_id 关联的那张图必须是 JPG/JPEG
author 作者 一般不超过 8 字 可按需填写

两条实用经验:

四、错误码:四个最常遇到的

调用接口失败时,微信返回的是一串错误码。下面四个覆盖了绝大多数情况:

错误码 含义 真正的原因 怎么修
40165 无效 IP 你的出口 IP 不在白名单里 把当前公网 IP 加进后台的 IP 白名单
40007 无效的媒体 ID 封面用了临时素材 改用【上传永久素材】接口重新上传
48001 API 功能未授权 账号没有这个接口的权限(未认证/权限被回收) 换认证账号,或换一条路(界面路)
40008 消息长度超限 标题/摘要/正文超了长度 按第三节的表压缩
⚠️ 40165 是最烦人的一个

因为它跟代码没关系——是你网络出口的公网 IP 变了(换网络、重启路由器、运营商重分配都会变)。 结论:写自动化流程时,要把【IP 白名单】当作一个会变的配置来管理,在流程里加一步【调用失败且错误码是 40165 时,提示人去更新白名单】——让它变成一个可读的提示,而不是一个看不懂的报错。

五、⚠️ 2026 现状:发布权限已被收回——而这正是本章想要的

这是本节最重要的一条现状,它把本章的设计从【谨慎】变成了【必然】:

自 2025 年 7 月起,微信官方已回收【个人主体账号】与【未认证企业账号】的 freepublish(提交发布)接口权限。

所以当前的权限格局是这样:

能力 个人订阅号 认证过的账号
获取 access_token 可以 可以
素材管理(传图) 可以 可以
建草稿(draft/add) 可以 可以
发布(freepublish) 已回收 可以

这张表恰好说明了三件事:

  1. 本章的流程【机器建草稿、人点发布】,不是保守,而是接口权限本来就这样——约束与现状完全吻合;
  2. 对个人号来说,draft/add 就是这条路的终点。这一步之后,发布只能人在后台点;
  3. 对认证号来说,接口上是可以继续走的,但请不要走——理由在 3.1:自动发布落在平台打击【AI 托管运营】的范围内,而且 AI 生成内容需要人确认并主动声明。
📝 一个漂亮的巧合

3.1 里定下的定盘星(机器送到草稿箱门口、最后一下交给人),当时是从【合规】角度推出来的;到了本节,你会发现它同时是【权限】的现状。 当一条合规的设计恰好也是最可行的实现时,说明这个设计找对了。 反过来也成立:网上那些教你【全自动发布】的教程,之所以要用各种绕路的办法,正是因为它们同时撞上了合规和权限两堵墙。

六、【AI 生成内容声明】放在哪

这是本章唯一一个【法规要求但接口里没有对应字段】的东西。接口的请求体里没有【声明】这一项,所以要分两个落点:

落点 形式 谁做 说明
落点一:正文显式声明 在正文末尾加一段固定文字(例如【本文由 AI 辅助创作,经人工编辑与核对】) RPA 自动加 这是显式标识的落地——用户能明显感知到
落点二:发布时主动声明 在后台发布环节,勾选平台的 AI 生成内容声明选项 人做 平台公告明确要求:发布 AI 生成合成内容需主动声明
⚠️ 为什么必须两个落点都做

只做落点一:正文里写了声明,但平台的声明字段没勾——不符合平台的要求; 只做落点二:平台勾了,但读者在文章里看不到——显式标识的意义就弱了; 两个都做,才是完整的:正文里让人看得见,平台字段上让机器读得到。 这也是为什么本节的预检器里,ai_declare 是一个【不确认就阻断】的必填项——它是法规要求落在流程里的那个点。

七、密钥安全:三条不能破的规矩

AppSecret 是你公众号的钥匙。泄露的后果是:别人可以以你的名义建草稿、发消息、调用你的接口。 三条规矩:

规矩 具体做法
不要写进流程文件 不放明文在 RPA 流程、代码、脚本里;放环境变量或凭据管理
不要提交到代码仓库 用 .gitignore 挡住配置文件;已经提交过的要立即更换密钥
不要传给第三方工具 除了官方接口,不要把 AppSecret 交给任何第三方服务(包括各种【代发】工具)

并且配合两条:

八、动手 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…
  → 必须移到环境变量或凭据管理里,不能写进流程文件

读结果,四个值得注意的地方:

  1. 七行数据里六行被拦住——这说明硬限制不是理论上的东西,它是每天都会踩的。而且每一条被拦的原因都不一样(长度、图床、格式、合规、上限),说明预检项要一项不落;
  2. id=006 被拦的原因是【AI 声明未确认】而不是任何技术问题——合规项和技术项在同一条流水线上,用同一种方式拦停。这是本章设计的一个特征;
  3. 干跑的输出里 thumb_media_id 是占位符——干跑的意义就在这:在真正上传封面之前,先把请求体的骨架打出来核一遍,结构错了不用等接口报错就知道;
  4. 密钥检查抓到了明文密钥——注意它不是靠报错发现的,而是主动扫出来的。这一类问题不会让流程失败,只会让流程【悄无声息地危险】。

九、备选方案:界面路怎么做

如果你没有认证公众号(或者暂时不想配 AppID/IP 白名单),可以走界面路:

① RPA 打开公众号后台 → 内容管理 → 草稿箱 → 新的创作 → 图文消息
② 填【标题】输入框
③ 填【作者】
④ 把 3.4.3 转换好的 HTML 放进剪贴板的 HTML 槽位 → 在正文区粘贴
⑤ 上传封面图(后台有上传控件)
⑥ 检查一遍 → 存为草稿

界面路的三个注意点:

  1. 正文粘贴必须走【富文本】——把 HTML 当纯文本打进去,你会看到满屏尖括号(3.4.3 第三节讲过);
  2. 图片上传要用后台的控件,不要自己拼 HTML——让后台把图片传到它的图床,这比接口路里的【手动上传正文图】省事;
  3. 每一步之后都要【回读校验】——界面的失败是静默的(粘贴没成功、图传了一半),不回读你发现不了。

30 秒实验

假设你手上有一个认证过的服务号(所以 freepublish 权限是有的),问你一个问题:

你会不会把流程做成【自动发布】?

答案:不要。 三个理由:

  1. 合规:自动发布落在平台打击【AI 托管运营】的范围内(3.1);
  2. 法规:AI 生成合成内容应当由发布者主动声明——这个动作需要人;
  3. 风险:发布是不可撤销的(草稿可以删,发出去的推文只能撤回,且已经被推到订阅者的消息列表里了)。

结论:有权限不等于应该用。 【能做到】和【应该做】之间隔着合规与风险判断——这是本章从头到尾都在强调的一件事。

常见误区

  1. 【既然有接口,那就全自动发布】——错。 权限上(个人号已被回收)与合规上(AI 托管运营、AI 内容需人确认声明)都不成立。机器建草稿、人点发布,是本章的定盘星。
  2. 【AppSecret 写在流程里方便,反正只有我自己用】——错。 流程文件会备份、会分享、会同步到云端。泄露 AppSecret 等于别人可以以你的名义操作公众号。 放环境变量或凭据管理,并且把 IP 白名单打开作为第二道门。
  3. 【封面图随便传一张就行,PNG 更清晰】——错。 封面必须是 JPG/JPEG,且必须上传到永久素材(不是临时素材)。这两条都会导致调用失败,而错误提示未必直观。
  4. 【正文里的图片可以用外链】——错。 接口只接受微信图床的地址;外链图在发布后会变成裂图。正文里每张图都要单独上传。
  5. 【失败了重试就行,不用预检】——错。 接口调用是整批失败的:一行超长,整批返回错误。预先按硬限制过一遍,比反复重试划算得多——本节的 Demo 就是这件事。
自测三问(点开对答案) 1. **接口路和界面路的本质差别是什么?为什么说【能走接口就走接口】?** 本质差别是**身份**:接口路是**以程序的身份**和平台说话(有明确的规则、限额与日志),界面路是**拿着你的账号密码去模拟人**——**而模拟人恰好是平台风控最敏感的地方**。而且界面自动化**页面一改版就要重做**,接口相对稳定。所以能走接口就走接口:**既是为了稳定性,也是合规姿态更好。** 2. **为什么说【微信 2025-07 回收发布接口】这件事,反而印证了本章的设计?** 因为本章的定盘星(**机器送到草稿箱门口、最后一下交给人**)当初是从**合规角度**推出来的;而微信回收个人号与未认证账号的 `freepublish` 权限,让**权限现状正好也是这个形状**。 **当合规的设计恰好也是最可行的实现时,说明设计找对了。** 反过来印证:网上那些【全自动发布】的教程之所以要绕路,正是因为同时撞上了合规与权限两堵墙。 3. **AI 生成内容声明为什么要【两个落点】,分别是什么?** **落点一:正文末尾的显式声明**(RPA 自动加一句固定文字)——让**读者**看得见;**落点二:后台发布时勾选平台的声明选项**——让**平台**读得到(平台公告明确要求发布 AI 生成合成内容需主动声明)。 只做落点一 → 平台的声明字段没勾,不符合要求;只做落点二 → 读者看不到显式标识。**两个都做才完整**——这也是预检器里 `ai_declare` 不确认就阻断的原因。

相关笔记