AI 日报封面自动化:Swiss 风格模板设计与 Playwright 渲染方案

2026-06-18

从默认 Logo 到每期专属封面

我每两天发一期 AI 资讯日报。推微信草稿箱时需要封面图——但很长一段时间里,那个封面就是一张默认的 logo 图片,所有日报共用同一张。

也不是没想过做专属封面。日报标题、日期、摘要都是现成的,每期手动做图太麻烦。用 AI 文生图?成本倒不高,但每 48 小时调一次文生图 API,总感觉太重了。

我想要的是:读 Markdown frontmatter → 套模板 → 出图 → 推微信,全自动,零人工介入。

最后做成了一套 Swiss IKB Blue 风格方案:法国艺术家 Yves Klein 的群青色(#002FA7)作为品牌色,白底排版,21:9 主封面 + 1:1 方封面各一张,Playwright 渲染 HTML 截图,PIL 压缩后自动传给发布流程。

架构:四步走

整套流程拆成四个环节:

  1. 从日报 Markdown 文件读取 frontmatter(title, date, description)
  2. 替换到预设的 HTML 模板中(4 个占位符)
  3. Playwright 加载本地 HTML,截图两个指定容器
  4. PIL 把 PNG 压缩为 JPEG,输出到 covers 目录
python3 generate-digest-cover.py digests/2026-06-17-ai-digest.md

跑完这条命令,covers/ 下就有 ai-digest-2026-06-17.jpg(21:9)和 ai-digest-2026-06-17-1x1.jpg(1:1)两张图。

如果脚本失败(网络超时、字体加载失败),降级到纯 PIL 生成的默认封面——画面上有品牌色底色 + 标题 + 日期,虽然不如专属封面精致,但至少比空着强。

HTML 模板:Swiss 风格的设计系统

模板是整套方案的核心。色调、字体、布局三个维度:

色调:纸白背景(#fafaf8)+ IKB Blue 强调色(#002FA7)+ 两种灰色辅助(#d4d4d2, #737373)。

字体:英文用 Inter,中文用 Noto Sans SC,数字/代码用 IBM Plex Mono。全部通过 Google Fonts 在线加载。

尺寸:21:9 版(2100×900)左右两栏——左栏标题 + 日期 + 描述,右栏抽象几何图形。1:1 方封面(1080×1080)纯色背景 + 居中文字。

模板里留了 4 个占位符:__MAIN_TITLE____DESCRIPTION____DATE_LABEL____TITLE_SHORT__。脚本读取 Markdown frontmatter 后逐项替换。

设计时有个取舍:字体文件要不要打包进模板?不打包 → 依赖 Google Fonts 在线加载,需要网络 + 等待时间。打包 → 模板从 10KB 膨胀到 500KB+。我选了在线加载方案,后面会说它带来了什么问题。

Playwright 渲染的关键参数

Playwright 的配置有几个关键点:

page = await browser.new_page(viewport={"width": 2400, "height": 1600})
await page.goto(f"file://{tmp_path}", wait_until="networkidle", timeout=30000)
await asyncio.sleep(4)  # 等字体加载
  • Viewport 比实际截图尺寸大一些(2400×1600),避免截不全
  • file:// 协议加载本地 HTML,不走 HTTP 服务器
  • wait_until="networkidle" 确保 Google Fonts 下载完成
  • 额外 sleep(4) 是因为字体加载后的渲染还需要时间

截图后还有一个步骤:PIL 验证实际尺寸。

from PIL import Image
img = Image.open("temp.png")
if img.size != (2100, 900):
    img = img.crop((0, 0, 2100, 900))
img.convert("RGB").save("cover.jpg", "JPEG", quality=90, optimize=True)

quality=90 的 JPEG 压缩下来,21:9 主封面约 100KB,远低于微信的 2MB 限制。

踩了五个坑

1. 中文字体渲染成方框

SVG 生成封面依赖系统字体路径。换一台机器,或者同一台机器上没装对应的中文字体,渲染出来的中文就是一排方框。

解决:PIL 方案硬编码字体路径(NotoSansCJK-Bold.ttc),Playwright 方案用 Google Fonts CDN。两个方案各自指向明确的字体文件,不依赖系统回退。

2. Google Fonts 首次加载慢

截早了字体没加载完,文字渲染成 fallback 字体。截晚了浪费几秒。

解决wait_until="networkidle" + asyncio.sleep(4)。首次加载约 4 秒,后续由于浏览器缓存,速度快很多。如果部署在无外网环境,需要把字体文件 base64 内联到 HTML 中——模板会膨胀,但能离线工作。

3. 截图尺寸不对

Playwright 的 page.screenshot() 默认截取整个页面。如果 HTML 渲染高度和预期不符,可能多出白边或缺少底部内容。

解决:截图后用 PIL 检查尺寸,不匹配就裁剪。或者用 Playwright 的 element.screenshot() 定位到具体容器元素。

4. 短标题在方封面上放不下

日报标题 “2026年6月17日 AI编程工具/Agent资讯精选” 共 17 个字。方封面空间有限,硬塞进去要么字体小到看不清,要么超出边界被裁掉。

解决:先按标点分割取前半句,超过 12 字再截取前 12 字。17 字的标题变成 “2026年6月17日”,刚好放下。

5. 旧封面文件不同步

替换默认封面后,发布脚本跑在另一台服务器上,旧的封面文件还在那里。本地预览是新封面,推送出去是旧封面。

解决:新封面生成后用 rsync 同步到发布服务器。或者更好的方案——把封面生成脚本部署到发布服务器上一起跑,不需要跨机同步。

集成到发布流程

cron 定时任务每 2 天执行一次:

# 先生成封面
python3 generate-digest-cover.py digests/2026-06-17-ai-digest.md

# 再推微信(带封面)
python3 publish-wechat.py digests/2026-06-17-ai-digest.md \
  --cover covers/ai-digest-2026-06-17.jpg

两步之间不需要人工检查。如果第一步失败,第二步不传 --cover,自动降级到默认 logo 封面。

适合谁

如果你的项目也有定时发布需求,封面图一直是 logo 凑合:

  • 内容简单:只需从标题/描述自动生成封面 → 直接拿这个模板改配色
  • 内容复杂:需要每期不同视觉风格 → HTML 模板多准备几套,轮换使用
  • 不想碰 Playwright:纯 PIL 方案也可以,排版灵活度不如 HTML,但零浏览器依赖

总结

封面自动化的核心不是”用什么工具渲染”,而是”怎么把渲染放进流程”。

PIL 和 Playwright 都能出图,区别在维护层面:PIL 改排版要改 Python 代码,Playwright 渲染 HTML 改排版就是改 CSS。对经常调封面的场景,后者省事得多——编辑 CSS 比修改 Python 门槛低,甚至可以交给非技术人员维护。

至于 Swiss IKB Blue 风格本身,选择它不是因为它最花哨——是因为它足够简单。9 个 CSS 变量控制全部配色,4 个占位符控制全部文字内容。这种窄接口的设计对自动化流程来说,比花哨的模板可靠得多。

标签: AI 工具