根本原因是HTML渲染前字符编码链路断裂:文件实际编码非UTF-8、<meta charset>不在前1024字节、file://协议下fallback至GBK、headless浏览器忽略meta,或源头字节流已非UTF-8;同时CSS字体缺失或环境locale未设为zh_CN.UTF-8也会导致中文显示异常。

HTML 截图生成图片时中文乱码,根本原因不是截图工具本身,而是 HTML 渲染前的字符编码链路断裂——浏览器没按 UTF-8 解析 HTML,导致渲染出的 DOM 文本已是乱码,截图只是忠实地捕获了这个错误结果。
为什么 meta charset="UTF-8" 写对了还是乱码
常见现象:HTML 源码里明明写了 <meta charset="UTF-8">,但 Puppeteer / Playwright / wkhtmltopdf 截图后中文仍是方块或问号。这是因为:
- HTML 文件本身存储编码不是 UTF-8(比如被 VS Code 误存为 GBK 或 UTF-8 with BOM)
-
<meta charset>标签不在<head>开头 1024 字节内(浏览器只扫描前 1024 字节找 charset) - 截图服务通过
file://协议加载 HTML(无 HTTP 响应头),此时若文件含 BOM 或编码不匹配,浏览器会 fallback 到系统默认编码(Windows 是 GBK) - 某些 headless 浏览器(如旧版 Puppeteer)在无服务器环境里忽略
meta,纯靠响应头判断编码
Puppeteer 截图前必须显式设置 page.setContent() 的 encoding
直接 page.goto("file:///path/to/index.html") 风险极高。正确做法是读取 HTML 内容后手动注入,并指定编码:
const fs = require('fs');
const html = fs.readFileSync('./index.html', 'utf8'); // 必须显式声明 utf8
await page.setContent(html, { waitUntil: 'networkidle0' });
关键点:
立即学习“前端免费学习笔记(深入)”;
- 不用
fs.readFile+toString(),避免 Node.js 默认用系统编码(Windows 下是 cp1252/GBK)解码二进制 - 不能省略
'utf8'参数,否则 Buffer.toString() 会走默认编码 - 如果 HTML 来自数据库或 API,确保源头就是 UTF-8 字节流,而非被中间层转码过
CSS 中的 font-family 缺失或字体未加载
即使编码正确,截图仍可能显示方块——这是字体问题,不是编码问题:
- HTML 里没声明中文字体,浏览器 fallback 到无中文支持的字体(如 Helvetica、Arial)
- 截图环境(Docker 容器 / Linux 服务器)缺少中文字体文件,
font-family: "PingFang SC", "Microsoft YaHei"全部失效 - CSS 使用了
@import引入字体,但截图时网络被禁用或字体 CDN 不可达
解决方式:
- 在 CSS 中硬编码系统级中文字体栈:
body { font-family: "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif; } - Docker 环境下安装字体包:
apt-get install fonts-noto-cjk fonts-wqy-zenhei - 把字体文件转成 base64 内联到 CSS,绕过网络请求
Playwright 启动时需传入 --font-render-hinting=none
Linux headless 环境下,字体渲染 hinting 可能干扰中文像素对齐,导致截图模糊或部分字缺失。启动浏览器时加参数:
const browser = await chromium.launch({
args: ['--font-render-hinting=none']
});
这个参数不解决乱码,但能避免“编码对、字体对、却看起来像乱码”的视觉陷阱——比如“你好”显示成“你”或半截字。
最易被忽略的是:截图工具运行环境(尤其是 CI/CD 容器)的 locale 设置。LANG=C 会让很多字体子系统拒绝加载中文,必须显式设为 LANG=zh_CN.UTF-8 并安装对应语言包,否则前面所有编码和字体配置都白搭。



















