最近用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;