astro-to-wechat:把 Astro 博文同步到微信公众号草稿的开源小工具
原创 · 约 14 分钟阅读 · 阅读 --

astro-to-wechat:把 Astro 博文同步到微信公众号草稿的开源小工具

作者: Alex Xiang


古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。

如果你用 Astro(或其它静态站点)写博客,又运营 微信公众号,多半会遇到同一个痛点:同一篇文章,站点上一套 HTML/CSS,公众号编辑器里又是另一套规则——封面、正文图片、外链、样式都要重新适配。手工复制粘贴几次之后,你一定会想:能不能从已发布的页面自动生成草稿箱里的图文?

astro-to-wechat 就是为这个场景做的一个 Python 命令行工具:输入站点上的 URL 或文章 slug,调用微信公众平台接口,创建或更新草稿,并把 media_id 写回你仓库里的 Markdown frontmatter。它最初来自本站 www.zicode.com 的私有脚本,后来整理成可独立克隆的 MIT 开源仓库,方便任何人套在自己的 Astro 项目上。

下面从头说清楚:为什么有它、它做了什么、怎么装怎么用、和主站版本差在哪、以及我打算往哪走。

一、缘起:博客与公众号,两条流水线

很多技术作者的习惯是:博客首发(版本管理、代码高亮、长文结构清晰),公众号二次分发(触达微信内的读者、方便转发)。两条渠道的内容往往同源,但形态不同:

  • 站点是 你自己控制的 HTML,可以用任意布局、深色模式、组件;
  • 公众号图文受 素材上传、白名单标签、外链与样式限制,必须在后台或接口里走一遍「清洗 + 上图」。

早期我的做法是:在 Astro 里写好 Markdown,发布上线,再打开微信公众平台,把正文一段段贴进去、重新传封面——重复劳动多,还容易和线上版本不一致(比如你后来改了一个代码块或换了一张图,公众号草稿忘了同步)。

需求其实很明确:以线上(或本地 build 产物)为准,自动产出符合微信规则的草稿。脚本化的路径一旦走通,后面无论是个人博客还是专栏连载,都能省掉大量机械操作。

二、从 monorepo 里抽离:为什么单独开源

www.zicode.com 的仓库里,同步脚本长期和 整站 绑在一起:内容目录、构建输出路径、样式约定都写死在 astro-site/ 下面。这对本站维护很方便,但对「只想拿同步能力」的读者不友好。

于是把脚本抽成独立仓库 ax2/astro-to-wechat

  • 只保留:Python 脚本、config/wechat.example.json、最小 package.json(给 SVG 封面转 PNG 用的 sharp);
  • 不包含:站点主题、文章、部署配置、密钥;
  • 约定:通过配置项 content_rootpublic_rootdist_rootnode_modules_root 指向你的 Astro 工程,而不是写死路径。

这样你可以把仓库 clone 到任意位置,用相对路径指向 ../my-blog,与主站开发并行,互不污染。

三、它具体做了什么(能力一览)

核心流程可以概括成四步:拉页面 → 抽正文 → 处理图片与样式 → 调微信草稿接口

  1. 页面来源

    • 默认按 site_url + --slug 拼出文章 URL,用 HTTP 拉取;
    • 若配置了 dist_root(例如 Astro 的 dist/),且存在 dist/blog/<slug>/index.html,则 优先读本地构建结果,避免「线上还没部署完」或「想完全对齐本次 build」时的偏差。
  2. 正文抽取

    • 用 HTML 解析器在整页里找到 .prose 包裹的正文(与本站 Astro 布局一致;若你的主题类名不同,需要自行改解析逻辑或统一类名)。
    • 去掉标题区、只保留与阅读相关的块级内容。
  3. 微信侧 HTML 与图片

    • 将正文中的图片 上传到微信素材接口,把 src 换成微信返回的 URL;
    • 对允许的标签补充 内联样式,使在公众号里观感接近可读的技术文(表格、代码、引用等按白名单处理);
    • 若正文中使用带 post-site-intro 的导流块(本站与「字与码」统一的那块),会转成与博客浅色风格一致的 内联样式,避免草稿里样式全丢。
  4. 封面与草稿

    • 封面优先级大致为:--thumb → frontmatter heroImage → 配置里的默认图 → 页面 og:image → 正文首张图;
    • 支持 SVG 转 PNG(依赖本仓库 npm install 后的 sharp);
    • 调用 新增草稿 / 更新草稿;可选 发布群发;成功后可把 wechatDraftMediaIdwechatPublishId 写回 Markdown。

此外,本站实践里还加入了 按专栏批量同步--column,依赖 frontmatter 里的 column 字段),适合系列文章一次性进草稿箱。

四、开发过程里踩过的坑(简述)

实现上比较费时间的往往不是「调通 API」,而是 细节对齐

  • 图片:相对路径、public 目录、以及线上绝对 URL 三种来源要统一解析;失败时要否降级为原链、日志是否可读。
  • 样式:微信侧可用标签有限,复杂布局必须放弃,改成保守的段落 + 列表 + 表格 + 代码。
  • 外链:部分场景下会把外链收集到文末「参考链接」,避免正文里到处散落不可控的跳转。
  • 原创声明:微信 草稿接口文档里没有「声明原创」字段,脚本无法在接口层替你勾选;因此在配置里增加了 declare_original 与 frontmatter original 的约定,同步成功后 打印提醒,避免忘记在后台勾原创——这是产品规则限制,不是偷懒。

这些经验都反映在当前脚本的结构和 README 的「Behavior Notes」里。

五、安装与使用(给想直接上手的人)

环境:Python 3.10+;若要用 SVG 封面转 PNG,需要 Node 18+ 并在本仓库执行 npm install

git clone https://github.com/ax2/astro-to-wechat.git
cd astro-to-wechat
npm install
cp config/wechat.example.json config/wechat.local.json
# 编辑 wechat.local.json:app_id、app_secret、site_url、各 root 路径

常用命令示例:

# 只生成 JSON,不调微信(检查 payload)
npm run sync:wechat -- --slug your-post-slug --dry-run --output tmp/preview.json

# 创建草稿(会写回 wechatDraftMediaId 若配置了 content_root)
npm run sync:wechat -- --slug your-post-slug

# 用完整 URL
python3 scripts/sync_wechat_article.py https://example.com/blog/your-post/

# 更新已有草稿
npm run sync:wechat -- --slug your-post-slug --update-media-id MEDIA_ID

更完整的参数说明、字段表、安全提示见仓库 README

六、和 www.zicode.com 主站脚本的关系

主站 monorepo 里仍保留一份 路径写死 的同步脚本(直接指向 astro-site/,与本站 CI、技能文档一致);功能上会与 astro-to-wechat 保持 定期对齐,后者是 可移植、可配置路径 的版本。

如果你 fork 了开源仓库,又希望某能力与主站完全一致,可以关注主站仓库里 scripts/sync_wechat_article.py 的变更,或提 issue 讨论合并策略。

七、今后的规划(想做的事)

短期:

  • 文档:补一页「主题适配指南」(.prose 以外的类名如何改、最小 Astro 布局示例)。
  • 健壮性:对常见失败(图片过大、HTML 超限)给出更明确的报错与拆分建议。
  • 测试:为 HTML 重写与路径解析加 纯函数级单元测试,减少对真实 API 的依赖。

中期:

  • 可选 配置文件外置样式映射(例如把部分内联样式抽到 JSON),方便不同品牌色站点复用。
  • 探索 一次同步多平台 的抽象(同一中间表示导出微信 / 其它渠道),但会谨慎控制范围,避免变成大而全的「发布系统」。

长期:

  • 若微信开放更多 与原创、合集相关 的接口能力,再评估是否自动化;在那之前,后台人工确认仍然是最稳妥的合规做法。

如果你也在维护 Astro 博客和公众号,欢迎试用 astro-to-wechat,到 GitHub 上提 issue 或 PR。工具本身不大,但能省下的心力,会积少成多。