公众号粘贴样式全丢?我用 Python 实现了一个「HTML → 公众号兼容」转换器(附skill安装地址)
公众号粘贴样式全丢?我用 Python 实现了一个「HTML → 公众号兼容」转换器
写公众号的朋友大概都踩过这个坑:用 HTML / Markdown 精心排好的文章,渐变背景、卡片、金句框、圆角阴影都安排上了,粘进公众号编辑器——一瞬间全没了。渐变变透明,卡片塌成光秃秃的文字,class 样式一个不剩。
这不是你不会排版,是公众号编辑器压根不认你写的那些东西。本文记录我怎么从零写一个转换脚本把内容「拍平」成公众号认的格式,含整体架构、四个关键代码点和几个最痛的踩坑。
一、先定位:样式到底丢在哪
公众号新版编辑器底层是 ProseMirror,它的白名单里根本没有 `<div>`。一旦你用 div 包着卡片 / 封面 / 金句框,整个 div 连同它的背景、圆角、阴影会被整段吞掉,只剩文字——表面看就是「整篇没 CSS」。这比「渐变被吃」更隐蔽,是粘贴后样式全失的头号元凶。
*图片说明:粘进去之前样式还在,粘进去之后只剩纯文字,问题往往出在 div 被整段吞。*
- **渐变 / 阴影 / 圆角 / flex 被吃**:白名单里没有这些,粘进去直接丢弃。
- **头号元凶是 div 被吞**:上面说过,白名单里根本没有 div,div 包的块整段消失。
- **旧方案也不省心**:135 编辑器 / 秀米要么收费、要么学习成本高,导出的代码里照样是 div,粘进公众号还是被吞。
- **标题违规词**:发出去才被限制,没人提前告诉你哪句踩线。
- **跨平台更绝望**:腾讯新闻、小红书、知乎的编辑器更封闭,常常只有「一键复制」,卡片背景圆角全崩。

二、核心思路:让脚本一次性把内容拍平
公众号编辑器只接受「内联 style + 实色背景 + 静态布局」。它要的不是你写得对,而是你写得符合它的白名单。所以真正的解法不是逼自己手改每个样式,而是让脚本一次性把内容拍平成公众号认的格式:你继续用 class 舒服地写,转换器负责把 class 拍平、把 div 换成 section、把渐变降级成实色。
> 一个反常识的点:过去「粘贴后样式全丢」,根因是转换器只内联了主题 CSS,把你文章自带的 `<style>` 整段丢弃。修复方式是抽取文章自己的样式,和主题合并,确定性内联每一个 class(含后代选择器、复合 class)。
## 三、整体架构:四环节流水线
我把「HTML 导入 → 排版 → 发布」拆成四个环节,每个环节一个脚本,串成流水线:

```
- **① 内容获取**:HTML / Markdown / Word / 网页链接 / AI 生成稿,统一归一为干净 HTML。Markdown 内置识别,表格代码块换行都保住。
- **② 格式转换**:一行命令,把任意内容变公众号能认的版本:内联 CSS、div→section、渐变→实色、图片转 base64 内嵌。
- **③ 排版美化**:套主题一步完成(fresh 紫蓝 / minimal 极简 / news 红头 / warm 暖橙),想微调改模板 CSS 重跑即可。
- **④ 发布前诊断**:六维体检:标题评分、爆款度、创意差异化、审美排版、传播力、违规敏感词扫描,还能出可视化报告。
*图片说明:转换后的内联样式在 375px 移动端预览里所见即所得,复制进公众号编辑器样式不丢。*
## 四、关键实现:四个难点怎么破
### 4.1 确定性内联 class
难点在于要把「主题 + 文章自带 style」的全部选择器拍平为内联 style,且要支持后代选择器(`.fact-box b`)和复合 class(`.a.b`),跳过 `:hover` / `@media`。核心思路:先建一个「选择器 → 样式声明」的字典,再遍历每个元素,匹配它自身 + 它满足的后代/复合规则,把声明拼成一个 style 字符串写回。
```
def inline_rules(soup, theme_css, user_css):
rules = parse_all_selectors(theme_css + user_css) # 跳过 :hover / @media
for el in soup.find_all(True):
decls = []
decls += match_self(el, rules) # .a.b / tag
decls += match_descendant(el, rules) # .parent .child
if decls:
el['style'] = merge(el.get('style',''), decls)
# 命中后清掉 class,避免重复
el.attrs.pop('class', None)
```
### 4.2 div → section
ProseMirror 白名单没有 div,但认 section。转换时把全部 `<div>` 重命名为 `<section>`,卡片 / 封面 / 金句框就能保住背景圆角。这也要求手写 HTML 时直接用 section,别留 div 等转换器擦屁股。
### 4.3 渐变 → 实色
公众号不支持 `linear-gradient`,sanitize 阶段把渐变背景替换为同色系实色,box-shadow / transform / flex 一并丢弃,只保留白名单属性。这是优雅降级,不是报错——原样保留做不到,因为平台就不支持。
### 4.4 图片 base64 内嵌
本地图自动转 base64 内嵌进 HTML,复制时图片跟着富文本走,粘进编辑器不丢图。关键点:图必须先压到 100KB 以内(宽 ≤1000px、JPEG 质量 80),否则内嵌后公众号会丢图。
```
from PIL import Image
im = Image.open('cover.png').convert('RGB')
w, h = im.size
if w > 1000:
im = im.resize((1000, int(h * 1000 / w)), Image.LANCZOS)
im.save('cover.jpg', 'JPEG', quality=80, optimize=True)
```
五、跨平台:图片版才是通用解
*图片说明:把排版好的页面渲染成图片,杂乱样式收敛成干净排版,图片是所有平台都认的通用格式。*
公众号还能靠内联样式粘贴救一救;但腾讯新闻、小红书这类编辑器更封闭,几乎过滤所有 CSS。真正完整保真的办法是把排版好的页面渲染成图片——图片是所有平台都认的通用格式,再简陋的编辑器也认。
- **长图**:整篇一张,存手机相册直接上传。
- **分段图**:按高度切多张,适合手机逐张上传 / 九宫格。
调用 `html_to_image.py` 即可生成,支持指定系统 Chrome 跳过 Chromium 下载(playwright 渲染)。腾讯新闻其实有电脑网页版(shizi.qq.com),PC 端支持文档导入整篇,比手机一键复制强得多。
六、稳定性三道锁(踩坑后补的)
- **清洗完全在 Python 完成**:预览页 JS 只复制、不转换。曾因把渐变转换、div→section 逻辑写回 `<script>`,那段 JS 嵌在 Python f-string 里,`\'` 被 f-string 吞掉反斜杠,导致 JS 语法崩、复制没反应。彻底改成「Python 管道清洗,JS 只复制」后故障消失。
- **出厂质检硬门禁**:写文件前断言 0 div / 0 渐变 / 0 脚本 / 0 样式表,任一不过直接报错退出。
- **回归测试 + git 强制卡口**:改完跑 `run_regression.py`,不过则提交不了——想回退也回退不了。
七、踩坑记录(最痛的几个)
- **「复制没反应」**:JS 写在 Python f-string 里,`\'` 被吞反斜杠 → JS 语法崩。根因是越界在浏览器里做转换,正确做法是任何兼容化都在 Python 管道完成。
- **大图不压缩内嵌 → 丢图**:ImageGen 出图约 1.3MB/张,必须压到 100KB 内才稳。
- **外链图不内嵌**:依赖图床稳定,公众号可能仍丢,优先用本地图转 base64。
- **src 用绝对路径但 base_dir 是 HTML 目录**:图找不到,统一用相对路径。
八、局限(诚实讲)
- **平台硬限制无法突破**:公众号本身不支持渐变 / 阴影 / flex / transform,工具会优雅降级(渐变→实色)而非报错,但「原样保留」做不到——这是平台规矩,不是工具缺陷。
- **图片版牺牲可编辑性**:渲染成图后完整保真,但读者不能复制文字、搜索引擎也不好收录,适合「秀」不适合「存」。
- **图片版需装 playwright / Chromium**(一次下载体积不小),或指定本机已装 Chrome 跳过。
- **转换依赖本地 Python 环境**(仅你这边;读者无感)。
- **外链图片保留但不内嵌**,需联网才显示;超大图 base64 会膨胀体积。
- **诊断 / 敏感词是辅助参考**,以《微信公众平台运营规范》为准。
九、怎么跑起来
环境:Python + `beautifulsoup4 lxml markdown`(转换用);图片版需 `playwright`(或用本机 Chrome)。
**基础:转成公众号兼容 HTML**
```
PY="你的/python.exe"
SKILL="你的/wechat-article-publisher"
"$PY" "$SKILL/scripts/wechat_convert.py" 输入.html \
--theme fresh --title "标题" --outdir .
```
**发布前诊断 + 可视化报告**
```
"$PY" "$SKILL/scripts/check_publish.py" \
--title "标题" --html 输入_wechat.html --report 输入_report.html
```
**跨平台图片版(长图 + 分段图)**
```
"$PY" "$SKILL/scripts/html_to_image.py" 输入_wechat.html \
--outdir . --width 750 --split 1200
```
速查清单:写 HTML 用 `<section>` 别用 `<div>`;放心用 class 排版,转换器会确定性内联;两份产物都要(_preview 一键复制 + _wechat 兜底全选);跨平台保真走图片版;发之前先跑 check_publish 排雷标题与违规词。
手动调 / 135 秀米 vs 本技能(一张表看清)
| 维度 | 手动改样式 | 135 / 秀米 | 本技能(脚本) |
|---|---|---|---|
| div 被吞 | 照吞 | 导出仍含 div,照吞 | 自动 div→section,规避 |
| 自定义 class | 手写 style | 模板受限 | 确定性内联,放心用 |
| 是否收费 | 免费 | 部分收费 | 免费,仅本地 Python |
| 跨平台图片版 | 无 | 弱 | 长图 + 分段图 |
| 发布前诊断 | 无 | 无 | 六维 + 违规词扫描 |
| 可复用 / 批处理 | 难 | 中 | 命令化,可反复跑 |
为什么值得信任:它给自己上了三道锁
稳定性不是嘴上说说:
- 清洗完全在 Python 完成,预览页 JS 只复制、不转换,杜绝「复制没反应」老故障。
- 出厂质检硬门禁:写文件前断言 0 div / 0 渐变 / 0 脚本 / 0 样式表,任一不过直接报错退出。
- 回归测试 + git 强制卡口:改完跑
run_regression.py,不过则提交不了——想回退也回退不了。
你读的这篇,就是它排的、它查的。眼见为实。
下回再写文章,别再手动调样式、别再发出去才知踩雷。把内容交给它,你只管写。
如果这篇帮你少掉几根头发,点个「观众」,让更多写公众号的人看见。
关于如何获取这个技能、看真实生成效果,见文末引导。想先试手,本地有 Python 加几个库即可,复制上面的命令就能跑。

请根据 https://skillhub.cn/install/skillhub.md,安装 wechat-article-publisher-v3。
更多推荐


所有评论(0)