215 lines
5.9 KiB
JavaScript
215 lines
5.9 KiB
JavaScript
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;
|