BLOG / 技术踩坑

解决 karin-plugin-kkk 及 Puppeteer 截图中文显示为方块字的问题

记录在使用 Node.js 爬虫或 Bot 插件(如 Puppeteer、Playwright 等)在无桌面 Linux 服务器上截图时,中文变成方块字的排查过程及终极原生系统字体解决方案。

约 4 分钟0次浏览
  • #Puppeteer
  • #Linux
  • #踩坑
  • #字体渲染

🛑 问题背景

今天在 VPS 上部署了 Karin 机器人的网页解析插件 karin-plugin-kkk(基于 Puppeteer)。在生成(例如解析抖音)截图的时候遇到一个特别恶心的问题:所有的中文都变成了方块(或者变成了极其丑陋的系统默认英文字体),但英文和数字显示正常。

插件本身实际上是有加载华为「鸿蒙字体 (HarmonyOS Sans)」的逻辑的,其 CSS 是这样写的:

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 端口其实是正常工作的

shell
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 时,可能会因为以下几个原因拉不到字体:

  1. 容器网络隔离:Puppeteer 本身所在的网段/沙箱和开出 3780 端口的进程不在同一个 localhost 作用域内。
  2. 安全策略 (CORS):Chromium 严格遵循同源策略,有时候直接本地跑 file:// 协议去请求 http://localhost 的跨域字体资源时,会被拦截且没有任何显式报错。
  3. 加载超时:无头浏览器渲染截图时往往有极短的 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 的容器内):

bash
# 创建一个系统字体目录
mkdir -p /usr/share/fonts/harmony

# 将准备好的 TTF 字体复制进去
cp /在此替换为你上传的字体路径/HarmonyOS_Sans_SC_Regular.ttf /usr/share/fonts/harmony/

3. 刷新操作系统的字体缓存

这一步非常关键,让系统加载新字体:

bash
# 刷新缓存
fc-cache -fv

# 验证字体是否被系统识别
fc-list | grep "Harmony"

如果输出包含你刚放入的字体信息,说明安装成功!

4. 彻底重启进程

一定要重启你的 Bot 或相关 Node 进程! 因为 Chromium 经常在进程初始启动时就将系统的字体树缓存在内存中了。重启后,新的系统级中文字体才能对 Puppeteer 生效。

🎉 总结

在无桌面服务器跑自动化截图时,不要过度依赖 HTML 里的外部网络字体(尤其是带有反代端口的)。最稳妥的保底策略永远是:给 Linux 服务器装上中文字体!给 Linux 服务器装上中文字体!给 Linux 服务器装上中文字体!

只要系统底层有中文字库去 Fallback,哪怕前端资源炸了,出来的图至少字是能看清的,再也不用看恶心的方块字了。

DISCUSSION

文章评论

0
正在读取