Files
2026-07-03 20:12:20 +08:00

157 lines
4.9 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* BrowserFactorystandard / real 双轨启动 + 温池 + 独立并发限流
*/
const {
DEFAULT_UA,
MAX_CONCURRENT_BROWSERS,
MAX_CONCURRENT_BROWSERS_REAL,
QUEUE_TIMEOUT_MS,
NAVIGATION_TIMEOUT_MS,
PAGE_DEFAULT_TIMEOUT_MS,
} = require('./constants');
const { BrowserConcurrencyLimiter } = require('./concurrency');
const { launchStandardBrowser } = require('./launch-standard');
const { launchRealBrowser, isRealBrowserAvailable } = require('./launch-real-browser');
const { browserPool } = require('./browser-pool');
const standardLimiter = new BrowserConcurrencyLimiter(MAX_CONCURRENT_BROWSERS);
const realLimiter = new BrowserConcurrencyLimiter(MAX_CONCURRENT_BROWSERS_REAL);
function normalizeAntiBot(antiBot) {
if (!antiBot || !antiBot.enabled) {
return { enabled: false, profile: 'standard' };
}
return {
enabled: true,
profile: antiBot.profile === 'real' ? 'real' : 'standard',
turnstile: antiBot.turnstile !== false,
solverFallback: antiBot.solverFallback !== false,
cfClearanceRequired: antiBot.cfClearanceRequired !== false,
sessionKey: antiBot.sessionKey || '',
challengeTimeoutMs: antiBot.challengeTimeoutMs || 60000,
postCfReadyUrlContains: antiBot.postCfReadyUrlContains || '',
postCfReadySelector: antiBot.postCfReadySelector || '',
postCfReadyTimeoutMs: antiBot.postCfReadyTimeoutMs || 0,
postCfSettleMs: antiBot.postCfSettleMs || 0,
postCfSpaWaitMs: antiBot.postCfSpaWaitMs || 0,
businessHostHint: antiBot.businessHostHint || '',
landingUrlHint: antiBot.landingUrlHint || '',
};
}
function getLimiter(profile) {
return profile === 'real' ? realLimiter : standardLimiter;
}
/**
* 冷启动 Browser(供温池 launcher 使用)
* @param {{ profile?: 'standard'|'real', extraArgs?: string[], sessionKey?: string }} options
*/
async function createBrowserSession(options = {}) {
const profile = options.profile === 'real' ? 'real' : 'standard';
const extraArgs = options.extraArgs || [];
const sessionKey = options.sessionKey || '';
if (profile === 'real') {
const { browser, page } = await launchRealBrowser(extraArgs, { sessionKey, profile: 'real' });
return {
browser,
page,
profile: 'real',
cleanup: async (destroy = false) => {
if (!destroy) {
return;
}
try {
await browser.close();
} catch (_) { /* ignore */ }
},
};
}
const browser = await launchStandardBrowser(extraArgs, { sessionKey, profile: 'standard' });
return {
browser,
page: null,
profile: 'standard',
cleanup: async (destroy = false) => {
if (!destroy) {
return;
}
try {
await browser.close();
} catch (_) { /* ignore */ }
},
};
}
/**
* 从温池或冷启动获取 Browser
* @param {{ profile?: 'standard'|'real', extraArgs?: string[], sessionKey?: string }} options
*/
async function acquireBrowserSession(options = {}) {
const profile = options.profile === 'real' ? 'real' : 'standard';
const sessionKey = options.sessionKey || '';
const extraArgs = options.extraArgs || [];
return browserPool.acquire(sessionKey, profile, async () => createBrowserSession({
profile,
extraArgs,
sessionKey,
}));
}
async function runWithProfileLimit(taskName, profile, fn) {
const limiter = getLimiter(profile);
try {
return await limiter.run(taskName, fn);
} catch (err) {
if (err.message === 'QUEUE_TIMEOUT') err.code = 'QUEUE_TIMEOUT';
throw err;
}
}
async function createManagedPage(browser, existingPage, options = {}) {
const page = existingPage || await browser.newPage();
page.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT_MS);
page.setDefaultTimeout(PAGE_DEFAULT_TIMEOUT_MS);
if (!options.skipUserAgent) {
const userAgent = options.userAgent || DEFAULT_UA;
await page.setUserAgent(userAgent);
}
if (options.viewport) await page.setViewport(options.viewport);
if (options.cookies && options.cookies.length > 0) {
await page.setCookie(...options.cookies);
}
page.on('error', (err) => console.error('[Page Error]', err.message));
page.on('pageerror', (err) => console.error('[Page JS Error]', err.message));
return page;
}
function getFactoryStats() {
return {
standard: standardLimiter.getStats(),
real: realLimiter.getStats(),
realBrowserReady: isRealBrowserAvailable(),
pool: browserPool.getStats(),
};
}
module.exports = {
normalizeAntiBot,
getLimiter,
createBrowserSession,
acquireBrowserSession,
runWithProfileLimit,
createManagedPage,
getFactoryStats,
standardLimiter,
realLimiter,
browserPool,
QUEUE_TIMEOUT_MS,
};