跳至正文

用 Python 写了个 Markdown 批量处理脚本,公众号排版终于不用手动了

问题

最近博客和公众号两边同时更新,内容都是从 Markdown 写的。博客那边 WordPress 对 Markdown 支持还行,但公众号这边就麻烦了——Markdown 的标题、代码块、链接、图片路径都得手动转成微信富文本格式。每次发一篇 1500 字的文章,光排版调整就要 20 分钟。

手动排版出了几次问题:代码块换行丢了、图片链接写死本地路径、<strong>** 混用。上周连着两天发出去的文章代码块格式都是乱的,读者留言说”代码糊成一团”——丢人啊。

决定写个脚本自动化这个流程。

Markdown转公众号排版示意图

方案

需求很简单:

  • 读 Markdown 文件
  • #/##/### 转成对应字号的 <p> 段落(公众号不支持 h1~h6)
  • 代码块用 <pre><code>,保留缩进和换行
  • 图片引用替换成已上传的 CDN URL
  • 链接 <a> 加上 target="_blank"
  • 列表 <ul><li> 正常处理
  • 输出纯 HTML 片段,可以直接粘贴到公众号编辑器

用了 Python 的 markdown 库做基础解析,然后自定义扩展处理公众号的特殊需求:

import markdown
from markdown.extensions import codehilite, fenced_code
import re

class WechatExtension(markdown.Extension):
    def extendMarkdown(self, md):
        md.postprocessors.register(
            HeaderToPara(md), 'header_to_para', 175)

class HeaderToPara(markdown.postprocessors.Postprocessor):
    def run(self, text):
        # h1: 18px bold center
        text = re.sub(r'<h1>(.*?)</h1>',
            r'<p style="font-size:18px;font-weight:bold;
            text-align:center;">\1</p>', text)
        # h2: 16px bold with color
        text = re.sub(r'<h2>(.*?)</h2>',
            r'<p style="font-size:16px;font-weight:bold;
            color:#2c3e50;">\1</p>', text)
        # h3: 15px bold
        text = re.sub(r'<h3>(.*?)</h3>',
            r'<p style="font-size:15px;font-weight:bold;">
            \1</p>', text)
        return text

图片替换的逻辑最麻烦。Markdown 里的路径是相对路径,但公众号需要图床 URL。脚本里建了个映射表:

IMG_MAP = {
    './img/cover.png': 'https://cdn.example.com/cover.png',
    './img/diagram.png': 'https://cdn.example.com/diagram.png',
}

class ImageRewriter(markdown.postprocessors.Postprocessor):
    def run(self, text):
        for local, remote in IMG_MAP.items():
            text = text.replace(f'src="{local}"', f'src="{remote}"')
        return text

完整脚本大概 120 行,核心就三个处理器:标题转换、图片重写、外链加 target。用法:

python3 md2wechat.py article.md --img-map img_map.json > output.html

输出的 HTML 直接粘到公众号编辑器里,格式完全对。

Python脚本处理流程图

踩坑

搞这个脚本踩了 3 个坑。

1. markdown 库默认不启用的扩展

想当然以为 markdown.markdown(text) 就能处理代码块,结果 <pre><code> 根本没出来。查了文档才知道 fenced_code 和 codehilite 得手动开:

md = markdown.Markdown(extensions=[
    'fenced_code',
    'codehilite',
    'tables',
    WechatExtension()
])

fenced_code 处理围栏代码块,tables 处理表格,缺一个都不行。

2. 正则把代码块里的标签也替换了

标题转换的 re.sub 最初没加边界限制,把代码块里展示的标签也给替换了,调试了半天才从输出的乱码定位到问题。

修复方案:先把代码块区域提取出来,做完替换再塞回去:

CODE_BLOCKS = []

def extract_code(text):
    def repl(m):
        CODE_BLOCKS.append(m.group(0))
        return f'%%CODEBLOCK_{len(CODE_BLOCKS)-1}%%'
    return re.sub(r'<pre><code.*?</code></pre>',
                  repl, text, flags=re.DOTALL)

def restore_code(text):
    for i, block in enumerate(CODE_BLOCKS):
        text = text.replace(f'%%CODEBLOCK_{i}%%', block)
    return text

3. 公众号对空行的处理不一致

编辑器里看是正常的空行,预览时多出一行空白。试出来是 <p><br></p> 渲染的问题。公众号编辑器会把连续空 <p> 标签吃掉但 <br> 留着。补救办法——输出时把空内容的 p 标签统一替换成 <p><br></p>

踩坑经验总结

总结

120 行代码省了每天 20 分钟的手动排版,一个月下来就是 10 个小时。值。

脚本丢在 GitHub 上了,有同样需求的朋友可以直接拿去改。AI 时代,能自动化的事就别手动做——打字再快也没脚本快。


写了 13 天自动化发布脚本的每日文章,今天这篇是关于这个流程里某个环节的优化。下一个想把封面图生成也接进去——markdown 写完,封面图自动出、排版自动转,一条命令搞定。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注