import puppeteer from 'puppeteer'; import {existsSync} from 'fs'; const browserArgs = [ '--disable-gpu', '--disable-dev-shm-usage', '--disable-setuid-sandbox', '--no-first-run', '--no-sandbox', '--no-zygote', // '--single-process', '--disable-background-networking', '--enable-features=NetworkService,NetworkServiceInProcess', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-breakpad', '--disable-client-side-phishing-detection', '--disable-component-extensions-with-background-pages', '--disable-default-apps', '--disable-extensions', '--disable-features=TranslateUI', '--disable-hang-monitor', '--disable-ipc-flooding-protection', '--disable-popup-blocking', '--disable-prompt-on-repost', '--disable-renderer-backgrounding', '--disable-sync', '--force-color-profile=srgb', '--metrics-recording-only', '--no-first-run', '--enable-automation', '--password-store=basic', '--use-mock-keychain', ]; // 尝试查找系统中已安装的 Chrome 或 Chromium 可执行文件路径 function getChromeExecutablePath() { // 检查环境变量中是否指定了 Chromium 路径 if (process.env.PUPPETEER_EXECUTABLE_PATH) { return process.env.PUPPETEER_EXECUTABLE_PATH; } const paths = [ // Windows 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', // macOS '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', // Linux '/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium' ]; for (const path of paths) { if (existsSync(path)) { return path; } } return null; } class BrowserManager { constructor() { this.browserList = []; this.unusedBrowser = []; this.pages = []; } async createBrowser(max_wse) { // 尝试使用系统已安装的 Chrome/Chromium const executablePath = getChromeExecutablePath(); const launchOptions = { headless: false, // 默认使用无头模式 args: browserArgs }; // 如果找到了系统 Chrome,则使用它 if (executablePath) { console.log('Using system Chrome:', executablePath); launchOptions.executablePath = executablePath; } else { console.log('Using bundled Chromium'); } const browser = await puppeteer.launch(launchOptions); let pageCount = 0; // 监听到打开了一个新窗口 browser.on("targetcreated", (data) => { pageCount++; }); browser.on("targetdestroyed", () => { pageCount--; if (pageCount === 0 && this.unusedBrowser.includes(browser.wsEndpoint())) { console.log("命中unusedBrowser"); this.delBrowser(browser.wsEndpoint()); } }); // max_wse await browser.newPage(); await browser.newPage(); await browser.newPage(); const pages = await browser.pages(); for (const page of pages) { this.pages.push(page); } this.browserList.push(browser.wsEndpoint()); return browser; } async initBrowser(max_wse) { try { await this.createBrowser(max_wse).then(() => { console.log("初始化浏览器成功"); }); } catch (error) { console.error("初始化浏览器失败:", error); throw error; } // 每隔24小时去创建一个新的Browser实例,并销毁第一个实例 setInterval(() => { console.log(this.browserList.join(',')); console.log(this.unusedBrowser.join(',')); this.createBrowser().then(() => { // 把第一个浏览器置为不可用 this.getFirstBrowser().then(browser => { browser.pages().then((pages) => { console.log(`第一个浏览器实例打开的页面数为${pages.length}`); // 如果当前没有打开的页面,创建Browser实例时会默认初始化一个页面,所以这里判断的是<=1 if (pages.length <= 1) { this.delBrowser(browser.wsEndpoint()).then(() => console.log("关闭浏览器成功")); } else { this.unusedBrowser.push(browser.wsEndpoint()); } }); }); }); }, 24 * 60 * 60 * 1000); } /** * 获取页面 */ getTargetPage() { while (true) { const page = this.pages.pop(); if (page !== undefined) { // 检查页面是否仍然有效 if (!page.isClosed()) { return page; } // 如果页面已关闭,则丢弃并继续获取下一个 console.log('发现已关闭的页面,已丢弃'); } else { // 如果没有可用页面,则创建新页面 console.log('没有可用页面,需要创建新页面'); // 这里应该触发创建新浏览器实例的逻辑 return null; } } } /** * 归还页面 */ returnTargetPage(target) { // 只有当页面未关闭时才归还 if (target && !target.isClosed()) { this.pages.push(target); } } /** * 从browserList和unusedBrowser中删除 * @param browser */ async delBrowser(browser) { this.browserList = this.browserList.filter((item) => item !== browser); this.unusedBrowser = this.unusedBrowser.filter((item) => item !== browser); const browserWSEndpoint = browser; const browserInstance = await puppeteer.connect({browserWSEndpoint}); await browserInstance.close(); console.log("删除浏览器 %o %o", this.browserList.length, this.unusedBrowser.length); } /** * 始终返回最后一个浏览器实例 */ async getBrowser() { const browserWSEndpoint = this.browserList[this.browserList.length - 1]; return puppeteer.connect({browserWSEndpoint}); } /** * 始终返回第一个浏览器实例 */ async getFirstBrowser() { const browserWSEndpoint = this.browserList[0]; return puppeteer.connect({browserWSEndpoint}); } } export default BrowserManager;