node.js - 如何将多个调用分组到在 GraphQL 中创建无头 Chrome 实例的函数
问题描述
我有一个运行 GraphQL 的 NodeJS 服务器。我的一个查询从 API 获取“项目”列表并返回一个 URL。然后将该 URL 传递给另一个函数,该函数获取该网站的屏幕截图(使用 NodeJS 包,它是 Puppeteer 的包装器)。
{
projects {
screenshot {
url
}
}
}
我的问题是,当我运行此程序时,如果有多个项目需要执行并为其生成屏幕截图。它为每个数据响应对象运行屏幕截图功能(见下文),因此在服务器上创建了一个单独的无头浏览器,因此我的服务器迅速耗尽内存并崩溃。
{
"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
});
});
};
我想知道的是如何修改这个逻辑,以:
- 避免为每次调用函数创建一个无头实例?基本上对响应中提供的每个 URL 进行分组/批处理并一次性处理它
- 当这个处理发生时,我能做些什么来帮助减少服务器上的负载,这样我就不会耗尽内存?
我现在已经在 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
已经出局了,但你有几个选择:
- 按顺序处理屏幕截图。这会很慢,但应确保一次只启动一个 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();
推荐阅读
- python - 产品向量化
- reactjs - 使用 onClick 双击手机屏幕
- linux - MariaDB:取决于:mariadb-server-10.6 (>= 1:10.6.4+maria~bionic) 但不会安装
- python - 在自定义 Shopify 应用中显示产品列表
- javascript - Convert mathjax output to png
- reactjs - 如何使条形图的chartjs列的宽度更小?
- matlab - Matlab - 在另一个应用程序应用程序中使用应用程序设计器应用程序
- node.js - 找不到模块 coa/compile.js
- php - WooCommerce Checkout 卡在纺纱上
- javascript - 如何通过使用 DOM 单击提交按钮来获取文本值并将其添加到列表中?