You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Puppeteer截图文本缺失问题排查求助

Puppeteer生成截图时文本缺失的问题解决

问题背景

我最近碰到一个非常诡异的问题:用Puppeteer把Express生成的带本地CSS的HTML转成截图时,截图里的文本居然凭空消失了!但直接在本地Chrome里打开这段HTML,所有文本和元素都显示正常;如果移除本地样式,Puppeteer的截图又完全正常。更奇怪的是,偶尔不改代码也能生成正常截图,这让我怀疑是不是存在未处理的竞态条件,但一开始完全摸不着头绪。

重现环境

  • Puppeteer版本:0.12.0
  • 运行平台:Docker/Ubuntu
  • Node.js版本:8

相关代码片段

Express路由代码(index.js)

app.post('/img', function (req, res) {
  const puppeteer = require('puppeteer');
  let css = [];
  let stylesheets = [];
  // 分离本地样式表并读取文件内容
  css = req.body.stylesheets.filter(sheet => {
    return sheet.indexOf('https') === -1 && sheet.indexOf('http') === -1;
  });
  css = css.map(sheet => {
    return fs.readFileSync(path.join(__dirname, sheet));
  });
  // 分离外部样式表(如bootstrap等)
  stylesheets = req.body.stylesheets.filter(sheet => {
    return sheet.indexOf('https') > -1 || sheet.indexOf('http') > -1;
  });
  // 用HTML和样式编译模板
  app.render('img', { stylesheets: stylesheets, content: req.body.content, css: css }, function (err, html) {
    console.log('html\n', html);
    (async() => {
      const browser = await puppeteer.launch({args: ['--no-sandbox']});
      const page = await browser.newPage();
      await page.setViewport({width: 1300, height: 1200});
      // 将HTML加载至Chrome
      try {
        const loaded = page.waitForNavigation({ waitUntil: 'load' });
        await page.setContent(html);
        await loaded
      } catch(err) {
        console.log(err);
        res.status(err.status).send('There was an error loading the page.');
      }
      // 保存图片
      const filename = `${req.body.title}.png`;
      const filepath = path.join(__dirname, 'img', filename);
      try {
        await page.screenshot({ path: filepath });
        console.log(`${filename} saved`);
      } catch(err) {
        console.log(err);
        res.status(err.status).send('There was a problem saving the image.');
      }
      res.status(201).send(filename);
    })();
  });
});

HTML模板(img.html)

<!DOCTYPE html>
<html>
<head>
  <title>{{title}}</title>
  {{#stylesheets}}
  <link rel="stylesheet" type="text/css" href="{{{.}}}">
  {{/stylesheets}}
  {{#css}}
  <style type="text/css">
    {{{.}}}
  </style>
  {{/css}}
</head>
<body>
  {{{content}}}
</body>
</html>

问题排查与原因分析

经过反复测试,我发现核心问题出在页面加载的等待逻辑上:
原来的代码中,用page.waitForNavigation配合page.setContent的写法是错误的——page.setContent是直接将HTML内容注入到当前页面,并不会触发浏览器的导航事件,这就导致loaded这个Promise一直处于pending状态,后续的截图操作会跳过等待直接执行。这时候页面可能还在解析本地CSS、渲染元素,尤其是文本样式还没完全应用,就被截图了,所以出现文本缺失的情况。而偶尔的正常截图,完全是因为页面渲染速度刚好赶在了截图操作之前,属于随机事件。

另外,还有一个潜在问题:fs.readFileSync读取本地CSS时没有指定编码格式,可能导致CSS内容出现乱码,进而影响样式解析,加重渲染异常的情况。

解决方案

针对这些问题,我做了三处关键修改:

1. 修正页面加载的等待逻辑

把原来错误的waitForNavigation写法,改成等待页面完全渲染完成的正确方式:

try {
  await page.setContent(html);
  // 等待页面网络完全空闲(所有资源加载完成),确保样式解析渲染完毕
  await page.waitForNavigation({ waitUntil: 'networkidle0' });
  // 如果networkidle0太严格,也可以改用等待DOM加载完成+样式就绪:
  // await page.waitForFunction('document.readyState === "complete"');
} catch(err) {
  console.log(err);
  res.status(500).send('There was an error loading the page.');
}

2. 完善本地CSS读取的编码处理

在读取本地CSS文件时,明确指定utf-8编码,避免乱码:

css = css.map(sheet => {
  return fs.readFileSync(path.join(__dirname, sheet), 'utf8');
});

3. 优化Docker环境下的Puppeteer启动参数

添加--disable-dev-shm-usage参数,避免Docker环境下的共享内存限制导致渲染异常:

const browser = await puppeteer.launch({
  args: ['--no-sandbox', '--disable-dev-shm-usage']
});

验证结果

修改完成后,测试结果完全符合预期:

  • 无本地样式:截图显示正常,和之前一致
  • 有本地样式:每次都能生成包含完整文本的截图,再也没有出现文本缺失的情况!

内容的提问来源于stack exchange,提问作者ebbishop

火山引擎 最新活动