每次写完公众号文章,最烦的不是写作本身,是排版。
打开微信编辑器,粘贴文字,调字号,加间距,插图片,预览,发现格式乱了,删掉重来——这套流程我做过几十次,每次至少半小时。写代码的人都知道,重复操作应该自动化。问题是微信编辑器不提供任何批量接口。
直到我发现了微信公众号的草稿箱 API。
整体思路
核心想法很简单:Markdown 写文章,脚本转成微信能认的 HTML,通过 API 推到草稿箱。同步生成一个静态网站,部署到 Cloudflare Pages。
Markdown → 微信 HTML → API 推送草稿 → 网站部署
整条链路由 Python 脚本串联,一条命令跑完。

下面是实际搭建的过程。
第一步:Markdown 转微信 HTML
微信编辑器对 HTML 的支持和浏览器差距很大。标准的 <ul><li> 列表会被拆成独立块,<table> 常常跑版,<section> 加背景色会出现双层卡片。
市面上有现成的工具,比如 npm 包 md2wechat-skill,提供 40 多个主题。我试了一下,npm 安装依赖链太长,在部分环境上会失败。最后决定自己写一个 Python 版的 md2wechat.py。
写的时候踩了几个坑:
列表变空行。微信编辑器把 <ul>/<li> 拆开渲染,每个 <li> 变成独立段落。解决办法是放弃标准列表标签,改用 <p> + • 前缀:
# 不要用 <ul><li>,微信会拆开
# 改用 <p> 标签 + bullet 前缀
html = f'<p>• {text}</p>'
表格管道符。Markdown 表格用 | 分隔列,但单元格内容也可能包含转义的 \|。直接 split("|") 会导致列错位。需要先做占位符替换:
line = line.replace('\\|', '\x00') # 转义符替换为占位符
cols = line.split('|')
cols = [c.replace('\x00', '|') for c in cols] # 还原
背景框嵌套。我在 <section> 上加了白色背景和圆角阴影,在浏览器里看起来挺好,到微信里变成两层框——外层直角,内层圆角。原因是微信编辑器会额外包一层容器。最终方案:去掉所有卡片样式,内容直接平铺。
第二步:对接微信 API
微信公众号的 API 调用分三步:拿 token,传封面图,推草稿。

获取 access_token:
url = (f"https://api.weixin.qq.com/cgi-bin/token"
f"?grant_type=client_credential&appid={APPID}&secret={SECRET}")
token = json.loads(urllib.request.urlopen(url).read())["access_token"]
这里有个坑。2025 年 12 月起,AppSecret 的获取入口从 mp.weixin.qq.com 迁移到了微信开发者平台(developers.weixin.qq.com)。我找了半天才发现路径变了:开发者平台 → 我的业务 → 公众号 → 基础信息 → 开发密钥。
另外,需要在公众号后台把你的服务器 IP 加到白名单,否则 API 直接返回 401。
推送草稿用 draft/add 接口。注意 content 字段只接受正文 HTML,不能发送包含 <!DOCTYPE>、<html>、<body> 的完整文档——我一开始就是犯了这个错,推到微信后文章里全是 HTML 标签原文。
所以需要一层包装剥离:
def extract_section(html_doc):
m = re.search(r'<section.*?>(.*)</section>', html_doc, re.DOTALL)
return m.group(1) if m else html_doc
还有 frontmatter 的问题。我的 Markdown 文件开头有 YAML 头:
---
title: 文章标题
date: 2026-06-04
---
推送到微信后,title: xxx 这些代码会原封不动显示在正文里。处理方式是转换前先剥离:
def strip_frontmatter(md_text):
return re.sub(r'^---\n.*?\n---\n', '', md_text, flags=re.DOTALL)
更新草稿用 draft/update。这个接口有个反直觉的设计:articles 字段必须是对象,不是数组。而且必须带 index: 0:
# 正确格式
data = {
"media_id": "已有ID",
"index": 0,
"articles": {"title": "新标题", "content": "新内容"} # 对象,不是数组
}
如果写成 [{"title": ...}] 会返回 47001 错误。微信 API 文档里没写得很清楚。
封面图限制 2MB。AI 生成的图通常 3-5MB,需要压缩到 JPEG 85%:
from PIL import Image
img = Image.open('cover.png')
img.save('cover.jpg', 'JPEG', quality=85, optimize=True)
第三步:网站部署
选了 Cloudflare Pages 而不是 GitHub Pages。原因是 GitHub Pages 的域名在国内被屏蔽,Cloudflare Pages 的 *.pages.dev 域名可以直连。
部署流程:
# 构建网站(扫描 articles/*.md,生成 site/ 目录)
python3 scripts/update-index.py
# 部署到 Cloudflare Pages
npx wrangler pages deploy site --project-name=ruite-ai-practice
# 推送到 GitHub
git add -A && git commit -m "post: 文章标题" && git push
这里也有一个坑。git push 偶尔会报 could not read Username,原因是 credential helper 没配好。最稳妥的方式是在 push 前用环境变量里的 token 重设 remote URL:
git remote set-url origin "https://${GITHUB_TOKEN}@github.com/user/repo.git"
git push origin main
git remote set-url origin "https://github.com/user/repo.git" # 恢复
第四步:自动化流水线
手动跑脚本比手动排版快多了,但还不够。我想让整个流程——从选题到发布——都自动化。
用 Kanban 流水线把每篇文章拆成 4 个阶段:

素材收集 → 撰写文章 → 安全检查 → 发布
每个阶段是独立的卡片,由 AI Agent 自动调度执行。凌晨定时任务扫描近期对话提取选题,自动创建流水线,第二天起来检查结果就行。
踩坑总结
整理一下最容易踩的几个坑:
- draft/update 的 articles 是对象不是数组——写错直接 47001
- content 不能包含完整 HTML 文档——否则标签作为文本显示
- frontmatter 会在正文出现——转换前必须剥离
- 列表用
<ul>/<li>会变成空行——改用<p>+ bullet - 封面图超过 2MB 会失败——AI 生图默认太大,要压缩
- AppSecret 入口已迁移——不在 mp.weixin.qq.com 了
- Git push 认证丢失——push 前重设 remote URL
这些坑大部分是试出来的。微信 API 文档对格式细节的描述不够精确,尤其是 draft/update 的字段要求,官方示例和实际行为有出入。
最终效果
文章写完后一条命令:
python3 scripts/publish-wechat.py articles/2026-06-04-article.md --theme ocean-calm
Markdown → 微信 HTML → 上传封面 → 推送草稿箱。去微信后台预览一下,没问题就发。
网站同步更新在 Cloudflare Pages 上。两套 HTML 从同一个 Markdown 源文件生成,改一次文章两边都生效。
手动排版 30 分钟的事,现在 30 秒搞定。省下来的时间写代码不好吗。