我写了个脚本,Markdown 自动变公众号草稿

2026-06-04

每次写完公众号文章,最烦的不是写作本身,是排版。

打开微信编辑器,粘贴文字,调字号,加间距,插图片,预览,发现格式乱了,删掉重来——这套流程我做过几十次,每次至少半小时。写代码的人都知道,重复操作应该自动化。问题是微信编辑器不提供任何批量接口。

直到我发现了微信公众号的草稿箱 API。

整体思路

核心想法很简单:Markdown 写文章,脚本转成微信能认的 HTML,通过 API 推到草稿箱。同步生成一个静态网站,部署到 Cloudflare Pages。

Markdown → 微信 HTML → API 推送草稿 → 网站部署

整条链路由 Python 脚本串联,一条命令跑完。

Markdown 到微信草稿的流水线概念

下面是实际搭建的过程。

第一步: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,传封面图,推草稿。

API 调试与错误修复的视觉隐喻

获取 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 驱动的自动化工作流

素材收集 → 撰写文章 → 安全检查 → 发布

每个阶段是独立的卡片,由 AI Agent 自动调度执行。凌晨定时任务扫描近期对话提取选题,自动创建流水线,第二天起来检查结果就行。

踩坑总结

整理一下最容易踩的几个坑:

  1. draft/update 的 articles 是对象不是数组——写错直接 47001
  2. content 不能包含完整 HTML 文档——否则标签作为文本显示
  3. frontmatter 会在正文出现——转换前必须剥离
  4. 列表用 <ul>/<li> 会变成空行——改用 <p> + bullet
  5. 封面图超过 2MB 会失败——AI 生图默认太大,要压缩
  6. AppSecret 入口已迁移——不在 mp.weixin.qq.com 了
  7. 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 秒搞定。省下来的时间写代码不好吗。