node.js - 如何对在 GraphQL 中创建 headless Chrome 实例的函数进行分组调用

标签 node.js graphql puppeteer

我有一个运行 GraphQL 的 NodeJS 服务器。我的一个查询从 API 获取“项目”列表并返回一个 URL。然后这个 URL 被传递到另一个函数,该函数获取该网站的屏幕截图(使用 NodeJS 包,它是 Puppeteer 的包装器)。

{
  projects {
    screenshot {
      url
    }
  }
}

我的问题是,当我运行这个程序时,如果有多个项目,它需要为其生成屏幕截图。它为每个数据响应对象运行屏幕截图功能(见下文),因此在服务器上创建一个单独的 headless 浏览器,因此我的服务器很快就会耗尽内存并崩溃。

{
  "data": {
    "projects": [
      {
        "screenshot": {
          "url": "https://someurl.com/randomid/screenshot.png"
        }
      },
      {
        "screenshot": {
          "url": "https://someurl.com/randomid/screenshot.png"
        }
      }
    ]
  }
}

这是我用于上下文屏幕截图逻辑的代码的简化版本:

const webshotScreenshot = (title, url) => {
  return new Promise(async (resolve, reject) => {

    /** Create screenshot options */
    const options = {
      height: 600,
      scaleFactor: 2,
      width: 1200,
      launchOptions: {
        headless: true,
        args: ['--no-sandbox']
      }
    };

    /** Capture website */
    await captureWebsite.base64(url.href, options)
      .then(async response => {
        /** Create filename and location */
        let folder = `output/screenshots/${_.kebabCase(title)}`;

        /** Create directory */
        createDirectory(folder);

        /** Create filename */
        const filename = 'screenshot.png';
        const fileOutput = `${folder}/${filename}`;

        return await fs.writeFile(fileOutput, response, 'base64', (err) => {
          if (err) {
            // handle error
          }

          /** File saved successfully */
          resolve({
            fileOutput
          });
        });
      })
      .catch(err => {
        // handle error
      });
  });
};

我想知道的是如何修改这个逻辑,以:

  1. 避免为每次调用函数创建 headless 实例?本质上是对响应中提供的每个 URL 进行分组/批处理,并一次性处理它
  2. 在进行此处理时,我可以采取哪些措施来帮助减少服务器的负载,以免内存不足?

我现在已经在 Node args 和设置内存限制等方面做了很多工作。但现在我认为最重要的是使其尽可能高效。

最佳答案

您可以使用dataloader批量调用任何获取屏幕截图的函数。此函数应采用 URL 数组并返回一个 Promise,该 Promise 可以使用结果图像数组进行解析。

const DataLoader = require('dataloader')

const screenshotLoader = new DataLoader(async (urls) => {
  // see below
})

// Inject a new DataLoader instance into your context, then inside your resolver
screenshotLoader.load(yourUrl)

capture-website 看起来不支持传入多个 URL。这意味着,每次调用 captureWebsite.base64 都会启动一个新的 puppeteer 实例。因此,Promise.all 已经退出,但您有几个选择:

  1. 按顺序处理屏幕捕获。这会很慢,但应该确保一次只有一个 puppeteer 实例启动。
const images = []
for (const url in urls) {
  const image = await captureWebsite.base64(url, options)
  images.push(image)
}
return images
  • 利用 bluebird 或类似的库同时运行请求,但有限制:
  • const concurrency = 3 // 3 at a time
    return Bluebird.map(urls, (url) => {
      return captureWebsite.base64(url, options)
    }, { concurrency })
    
  • 切换到直接使用 puppeteer,或使用支持多个屏幕截图的其他库。
  • const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
    const page = await browser.newPage();
    
    for (const url in urls) {
      const image = await captureWebsite.base64(url, options)
      await page.goto(url);
      await page.screenshot(/* path and other screenshot options */);
    }
    
    await browser.close();
    

    关于node.js - 如何对在 GraphQL 中创建 headless Chrome 实例的函数进行分组调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58823876/

    相关文章:

    angularjs - 使用 Generator-Angular-FullStack 构建 Heroku 应用程序失败

    javascript - 处理回调函数的响应

    graphql - gatsby.js - 高级入门 - 实现 2 个 url 前缀(站点的 2 个不同部分)?

    reactjs - 如何重置 Apollo Client 的 useMutation 钩子(Hook)

    graphql - 如何根据某个对象的 id 过滤订阅事件?

    javascript - Puppeteer 不会加载页面

    javascript - 从页面列表中查找单词

    node.js - express-generator-typescript 生成的包无法成功运行调试器

    javascript - 比较缓冲区

    javascript - Mongoose 找到多个匹配项