最近用Puppeteer来做网页截图和自动化任务,开发中遇到Puppeteer运行一段时间后内存占用很高,重启后会降下来,及经常在新标签中打页面不方便自动化任务。特封装了puppeteer定时清理page和browser及禁止在新标签打开页面等功能。在代码层实现Browser实例的创建和定时销毁,具体逻辑参考代码:
import puppeteer from 'puppeteer';//Puppeteer库
import puppeteerExtra from 'puppeteer-extra';//Puppeteer扩展库
import UserAgents from 'user-agents';
import puppeteerStealth from 'puppeteer-extra-plugin-stealth';//Puppeteer扩展库伪装插件
puppeteerExtra.use(puppeteerStealth());// 使用Puppeteer扩展库伪装插件
const puppeteerBrowser = {
options: {
headless: true, // 无头模式
pageConsole: false, // 页面控制台输出
pageOpenSelf: true, // 是否当前页面打开
userAgentFilter: { deviceCategory: 'desktop' },
browserExpire: 600, // browser最大空闲存活时间(秒)
pageExpire: 60, // page最大空闲存活时间(秒)
},
data: {
userAgent: '',
},
browser: '',
checkTaskInterval: '',
getUserAgent() {
if (!this.data.userAgent) {
const userAgent = new UserAgents(this.options.userAgentFilter).toString();
this.data.userAgent = userAgent;
return userAgent;
}
return this.data.userAgent;
},
reset() {
for (const key in this.data) {
this.data[key] = '';
}
},
//创建Browser及初始化
getBrowser: async function () {
if (!this.browser || !this.browser.isConnected()) {
const browser = await puppeteerExtra.launch({
headless: this.options.headless,
executablePath: puppeteer.executablePath(),
ignoreHTTPSErrors: true,
ignoreDefaultArgs: ['--enable-automation'],
args: [
'--no-sandbox', // 不使用沙箱
'--disable-infobars', // 禁止信息提示栏
'--disable-gpu', // 不用gpu
'--disable-setuid-sandbox', // 与--no-sandbox配合
'--no-default-browser-check', // 不检查默认浏览器
'--disable-extensions', // 禁止扩展
'--disable-default-apps', // 禁止默认应用
'--disable-dev-shm-usage', // 禁止使用/dev/shm,防止内存不够用
'--disable-hang-monitor', // 禁止页面无响应提示
'--disable-popup-blocking', // 禁止popup
'--disable-prompt-on-repost', // 禁止重新发送post请求的提示
'--disable-sync', // 禁止同步
'--disable-translate', // 禁止翻译
'--disable-bundled-ppapi-flash', // 禁止内置的flash
'--disable-component-update', // 禁止组件升级
'--disable-client-side-phishing-detection', // 禁止危险页面检测
'--disable-logging',
'--mute-audio', // 静音
'--single-process', // 单进程
'--no-zygote', // 禁止zygote进程fork子进程
'--safebrowsing-disable-auto-update',
'--no-first-run', // 禁止首次运行界面
'--use-mock-keychain', // 使用mock-keychain防止提示权限提示
'--ignore-certificate-errors', // 忽略证书错误
],
});
await this.waitForTime(100);
browser.runTime = Math.round(new Date().getTime() / 1000);
console.log('browser:', 'launch', await browser.version());
browser.on('disconnected', () => {
console.log('browser:', 'disconnected');
});
browser.on('targetcreated', async (target) => {
try {
console.log('browser:', 'targetcreated', target.url());
target.browser().runTime = Math.round(new Date().getTime() / 1000);
if (target.type() == 'page') {
const page = await target.page();
page.runTime = Math.round(new Date().getTime() / 1000);
await page.setUserAgent(this.getUserAgent());
if (this.options.pageConsole) {
page.on('console', (msg) => {
const args = ['pageConsole'];
for (let i = 0; i < msg.args().length; i++) {
args.push(msg.args()[i].toString().replace('JSHandle:', ''));
}
console.log.apply(null, args);
});
}
//实现页面内链接不在新标签打开
if (this.options.pageOpenSelf) {
await page.evaluate(() => {
if (!window.pageOpen) {
window.pageOpen = window.open;
window.open = function (url) {
window.pageOpen(url, '_self');
};
}
if (!window.pageTarget) {
window.pageTarget = setInterval(function () {
const elms = document.querySelectorAll('a,form');
// console.log('querySelectorAll', 'a,form', elms.length);
for (const key in elms) {
elms[key].setAttribute && elms[key].setAttribute('target', '_self');
}
}, 1000);
}
});
}
}
} catch (error) {
console.log('error:', error.message);
}
});
browser.on('targetchanged', async (target) => {
try {
console.log('browser:', 'targetchanged', target.url());
target.browser().runTime = Math.round(new Date().getTime() / 1000);
target.browser().runTime = Math.round(new Date().getTime() / 1000);
if (target.type() == 'page') {
const page = await target.page();
page.runTime = Math.round(new Date().getTime() / 1000);
await page.setUserAgent(this.getUserAgent());
if (this.options.pageConsole) {
page.on('console', (msg) => {
const args = ['pageConsole'];
for (let i = 0; i < msg.args().length; i++) {
args.push(msg.args()[i].toString().replace('JSHandle:', ''));
}
console.log.apply(null, args);
});
}
if (this.options.pageOpenSelf) {
await page.evaluate(() => {
if (!window.pageOpen) {
window.pageOpen = window.open;
window.open = function (url) {
window.pageOpen(url, '_self');
};
}
if (!window.pageTarget) {
window.pageTarget = setInterval(function () {
const elms = document.querySelectorAll('a,form');
// console.log('querySelectorAll', 'a,form', elms.length);
for (const key in elms) {
elms[key].setAttribute && elms[key].setAttribute('target', '_self');
}
}, 1000);
}
});
}
}
} catch (error) {
console.log('error:', error.message);
}
});
browser.on('targetdestroyed', (target) => {
console.log('browser:', 'targetdestroyed', target.url());
});
this.browser = browser;
this.runCheckTask();
return browser;
}
return this.browser;
},
//获取新标签页
getPage: async function () {
const browser = await this.getBrowser();
const page = await browser.newPage();
page.runTime = Math.round(new Date().getTime() / 1000);
page.waitForTime = this.waitForTime;
page.close;
return page;
},
waitForTime: function (ms) {
return new Promise((r) => setTimeout(r, ms));
},
//检测page和browser关闭
checkBrowserExpire: async function () {
if (this.browser && this.browser.isConnected()) {
const nowTime = Math.round(new Date().getTime() / 1000);
if (this.browser.runTime && this.browser.runTime + this.options.browserExpire < nowTime) {
console.log('checkBrowserExpire:', 'browser.close');
await this.browser.close();
this.browser = '';
} else {
const pages = await this.browser.pages();
for (const key in pages) {
const page = pages[key];
if (page.runTime && page.runTime + this.options.pageExpire < nowTime) {
if (!page.isClosed()) {
console.log('checkBrowserExpire:', 'page.close', page.url());
await page.close();
}
}
}
}
}
},
//定时清理page和browser
runCheckTask: function () {
if (!this.checkTaskInterval) {
this.checkTaskInterval = setInterval(() => {
puppeteerBrowser.checkBrowserExpire();
}, this.options.pageExpire * 500);
}
},
};
export default puppeteerBrowser;