- #Puppeteer
- #Linux
- #踩坑
- #字体渲染

🛑 问题背景
今天在 VPS 上部署了 Karin 机器人的网页解析插件 karin-plugin-kkk(基于 Puppeteer)。在生成(例如解析抖音)截图的时候遇到一个特别恶心的问题:所有的中文都变成了方块(或者变成了极其丑陋的系统默认英文字体),但英文和数字显示正常。
插件本身实际上是有加载华为「鸿蒙字体 (HarmonyOS Sans)」的逻辑的,其 CSS 是这样写的:
@font-face {
font-family: HarmonyOSHans-Regular;
src: url("http://localhost:3780/config/commonResource/font/HarmonyOS_Sans_SC_Light.1.woff2") format("woff2");
}
body {
font-family: HarmonyOSHans-Regular, bilifont, -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}插件本身试图在本地 3780 端口起一个微型服务器去反代华为的 CDN,但是截图出来依然全是方块。
🔍 问题排查与分析
起初我以为是本地没有字体文件,于是直接用脚本把官方的 89 个 woff2 切片字体全下载到了本地,并尝试把 CSS 里的 http://localhost:3780 改成本地路径,但由于插件是经过 esbuild 或者被宿主环境包装过的,各种路径错位导致改起来极其心累。
后来我通过 curl 测试,发现 3780 端口其实是正常工作的:
curl -I http://localhost:3780/config/commonResource/font/HarmonyOS_Sans_SC_Light.1.woff2
# 返回 HTTP/1.1 200 OK这说明本地的代理服务没问题,问题出在截图工具(Puppeteer/无头 Chrome)的请求上!
为什么 Puppeteer 无法加载 localhost 的字体?
在服务端的 Docker 容器或无头环境下启动 Chromium 时,可能会因为以下几个原因拉不到字体:
- 容器网络隔离:Puppeteer 本身所在的网段/沙箱和开出
3780端口的进程不在同一个localhost作用域内。 - 安全策略 (CORS):Chromium 严格遵循同源策略,有时候直接本地跑
file://协议去请求http://localhost的跨域字体资源时,会被拦截且没有任何显式报错。 - 加载超时:无头浏览器渲染截图时往往有极短的
waitUntil时间限制,中文字体动辄几 MB,走本地微型服务器中转如果没有命中缓存,极易超时直接降级(Fallback)。
为什么降级后变成了方块字?
当 Chrome 内核加载 @font-face 失败时,它会去宿主机操作系统的系统字体路径里找 font-family 声明的后备字体(如 PingFang SC, Microsoft YaHei 等)。
但我的 VPS 是一台纯净版无桌面的 Debian/Ubuntu Linux 服务器,它本地连最基础的中文字体库都没装!所以当网络字体挂掉,系统本地又借不到中文字体时,就彻底拉胯变成了方块字 □□□。
💡 终极解决方案:原生系统字体挂载
既然网络加载不可控,最稳妥、最彻底、最不需要改插件代码的方案,就是直接把中文字体安装到 Linux 的系统字体库里!
让 Puppeteer 在渲染请求断掉时,能直接用系统底层的字体兜底,瞬间就能出图。
解决步骤:
1. 准备字体文件
找一个你喜欢的 TTF 字体(例如鸿蒙字体 HarmonyOS_Sans_SC_Regular.ttf 或者微软雅黑)。
2. 将字体传入系统字体目录
登录到你的 VPS 或进入运行 Puppeteer 的 Docker 容器(比如 bot 的容器内):
# 创建一个系统字体目录
mkdir -p /usr/share/fonts/harmony
# 将准备好的 TTF 字体复制进去
cp /在此替换为你上传的字体路径/HarmonyOS_Sans_SC_Regular.ttf /usr/share/fonts/harmony/3. 刷新操作系统的字体缓存
这一步非常关键,让系统加载新字体:
# 刷新缓存
fc-cache -fv
# 验证字体是否被系统识别
fc-list | grep "Harmony"如果输出包含你刚放入的字体信息,说明安装成功!
4. 彻底重启进程
一定要重启你的 Bot 或相关 Node 进程! 因为 Chromium 经常在进程初始启动时就将系统的字体树缓存在内存中了。重启后,新的系统级中文字体才能对 Puppeteer 生效。
🎉 总结
在无桌面服务器跑自动化截图时,不要过度依赖 HTML 里的外部网络字体(尤其是带有反代端口的)。最稳妥的保底策略永远是:给 Linux 服务器装上中文字体!给 Linux 服务器装上中文字体!给 Linux 服务器装上中文字体!
只要系统底层有中文字库去 Fallback,哪怕前端资源炸了,出来的图至少字是能看清的,再也不用看恶心的方块字了。
DISCUSSION
文章评论
昵称、头像与账号资料会自动同步,无需手动填写。