原因:用puppeteer截图,每次都会产生一个chome实例进程,截图完了以后要关掉进程,如果一个接口一次访问100次截图会导致进程暂用而导致机器卡顿
1.安装
npm i generic-pool
npm i puppeteer
2.建puppeteerPool.js文件(用资源池管理截图进程实例)
const puppeteer = require('puppeteer')
const genericPool = require('generic-pool')
/**
* 初始化一个 Puppeteer 池
* @param {Object} [options={}] 创建池的配置配置
* @param {Number} [options.max=10] 最多产生多少个 puppeteer 实例 。如果你设置它,请确保 在引用关闭时调用清理池。 pool.drain().then(()=>pool.clear())
* @param {Number} [options.min=1] 保证池中最少有多少个实例存活
* @param {Number} [options.maxUses=2048] 每一个 实例 最大可重用次数,超过后将重启实例。0表示不检验
* @param {Number} [options.testOnBorrow=2048] 在将 实例 提供给用户之前,池应该验证这些实例。
* @param {Boolean} [options.autostart=false] 是不是需要在 池 初始化时 初始化 实例
* @param {Number} [options.idleTimeoutMillis=3600000] 如果一个实例 60分钟 都没访问就关掉他
* @param {Number} [options.evictionRunIntervalMillis=180000] 每 3分钟 检查一次 实例的访问状态
* @param {Object} [options.puppeteerArgs={}] puppeteer.launch 启动的参数
* @param {Function} [options.validator=(instance)=>Promise.resolve(true))] 用户自定义校验 参数是 取到的一个实例
* @param {Object} [options.otherConfig={}] 剩余的其他参数 // For all opts, see opts at https://github.com/coopernurse/node-pool#createpool
* @return {Object} pool
*/
const initPuppeteerPool = (options = {}) => {
const {
max = 10,
min = 2,
maxUses = 2048,
testOnBorrow = true,
autostart = false,
idleTimeoutMillis = 3600000,
evictionRunIntervalMillis = 180000,
puppeteerArgs = {},
validator = () => Promise.resolve(true),
...otherConfig
} = options
const factory = {
create: () =>
puppeteer.launch(puppeteerArgs).then(instance => {
// 创建一个 puppeteer 实例 ,并且初始化使用次数为 0
instance.useCount = 0
return instance
}),
destroy: instance => {
instance.close()
},
validate: instance => {
// 执行一次自定义校验,并且校验校验 实例已使用次数。 当 返回 reject 时 表示实例不可用
return validator(instance).then(valid => Promise.resolve(valid && (maxUses <= 0 || instance.useCount < maxUses)))
}
}
const config = {
max,
min,
testOnBorrow,
autostart,
idleTimeoutMillis,
evictionRunIntervalMillis,
...otherConfig
}
const pool = genericPool.createPool(factory, config)
const genericAcquire = pool.acquire.bind(pool)
// 重写了原有池的消费实例的方法。添加一个实例使用次数的增加
pool.acquire = () =>
genericAcquire().then(instance => {
instance.useCount += 1
return instance
})
pool.use = fn => {
let resource
return pool
.acquire()
.then(r => {
resource = r
return resource
})
.then(fn)
.then(
result => {
// 不管业务方使用实例成功与后都表示一下实例消费完成
pool.release(resource)
return result
},
err => {
pool.release(resource)
throw err
}
)
}
process.on("SIGINT", () => {
pool.drain().then(() => pool.clear()).then(()=>{
process.exit(0);
},()=>{
process.exit(1);
})
});
return pool
}
module.exports = initPuppeteerPool;
3.建screenshot.js(截图部分)
const initPuppeteerPool = require('../utils/puppeteerPool')
const onFinished = require('on-finished')
// 全局只应该被初始化一次
const pool = initPuppeteerPool({
puppeteerArgs: {
//设置该参数能解决网站证书无效问题(Error: net::ERR_CERT_COMMON_NAME_INVALID )
ignoreHTTPSErrors: true,
// 不使用 websocket
pipe: true,
//在linux运行要加这个参数,不加会报 Error: Failed to launch the browser process!
args:['--no-sandbox', '--disable-setuid-sandbox'],
}
})
async function screenshot(req, res) {
const { url,width,height } = req.query
if (!url||!width||!height) {
return res.json({
code: 1000,
msg: "缺少参数"
})
}
let isReturn = false;
onFinished(res, () => {
isReturn = true;
});
try {
// 在业务中取出实例使用
await pool.use(async browser=>{
if (isReturn) {
return;
}
let page;
try {
page = await browser.newPage();
const viewportOptions = {
width:Number(width),
height:Number(height),
deviceScaleFactor: 1,//这个参数设置为1(设置为2的时候截图会比较慢,截图50000高度的页面用时差不多25秒,设置为1的时候用时8秒,而且设置为2的时候截图50000高度的页面生成出来有一半是空白)
};
await page.setViewport(viewportOptions);
await page.goto(url, {
timeout: 60 * 1000,
waitUntil: 'networkidle2',
});
const imgBuffer = await page.screenshot();
//返回图片
res.set('Content-Type', 'image/png;');
res.send(imgBuffer);
}finally {
//关闭页面
if (page){
await page.close();
}
}
})
// 应该在服务重启或者关闭时执行
// pool.drain().then(() => pool.clear())
}catch (e) {
console.error(e,"screenshot-error")
res.json({
code: 1002,
msg: "截图失败"
})
}
}
module.exports = {
screenshot,
};