/** * puppeteer-api 共享常量与 Chrome 启动环境工具 */ const fs = require('fs'); const path = require('path'); const puppeteer = require('puppeteer-extra'); const DEFAULT_CHROME_EXECUTABLE = '/www/wwwroot/puppeteer-api/.cache/puppeteer/chrome/linux-149.0.7827.22/chrome-linux64/chrome'; const DEFAULT_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; const BASE_LAUNCH_ARGS = [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-extensions', '--disable-background-networking', '--disable-software-rasterizer', '--no-first-run', '--no-default-browser-check', '--disable-breakpad', '--disable-crash-reporter', ]; const RUNTIME_DIR = path.join(__dirname, '..', '.runtime'); function ensureRuntimeSubdir(name) { const dir = path.join(RUNTIME_DIR, name); fs.mkdirSync(dir, { recursive: true, mode: 0o777 }); return dir; } function resolveChromeExecutable() { const candidates = [ process.env.PUPPETEER_EXECUTABLE_PATH, process.env.CHROME_EXECUTABLE, DEFAULT_CHROME_EXECUTABLE, ].filter(Boolean); for (const candidate of candidates) { if (fs.existsSync(candidate)) { return candidate; } } try { const bundled = puppeteer.executablePath(); if (bundled && fs.existsSync(bundled)) { return bundled; } } catch (_) { /* ignore */ } return candidates[0] || DEFAULT_CHROME_EXECUTABLE; } function buildBrowserLaunchEnv() { const env = { ...process.env }; delete env.DBUS_SESSION_BUS_ADDRESS; delete env.DBUS_SYSTEM_BUS_ADDRESS; const xdgRuntime = ensureRuntimeSubdir('xdg-runtime'); env.XDG_RUNTIME_DIR = xdgRuntime; env.XDG_CONFIG_HOME = ensureRuntimeSubdir('xdg-config'); env.XDG_CACHE_HOME = ensureRuntimeSubdir('xdg-cache'); env.TMPDIR = ensureRuntimeSubdir('tmp'); return env; } function assertChromeExecutable() { const executablePath = resolveChromeExecutable(); if (!fs.existsSync(executablePath)) { throw new Error( `Chrome 可执行文件不存在: ${executablePath}。请在 puppeteer-api 目录执行: npx puppeteer browsers install chrome` ); } return executablePath; } const MAX_CONCURRENT_BROWSERS = Math.max(1, parseInt(process.env.MAX_CONCURRENT_BROWSERS || '4', 10)); const MAX_CONCURRENT_BROWSERS_REAL = Math.max(1, parseInt(process.env.MAX_CONCURRENT_BROWSERS_REAL || '2', 10)); const QUEUE_TIMEOUT_MS = Math.max(5000, parseInt(process.env.QUEUE_TIMEOUT_MS || '120000', 10)); const API_INTERCEPT_TIMEOUT_MS = Math.max(10000, parseInt(process.env.API_INTERCEPT_TIMEOUT_MS || '60000', 10)); const API_INTERCEPT_TIMEOUT_REAL_MS = Math.max( API_INTERCEPT_TIMEOUT_MS, parseInt(process.env.API_INTERCEPT_TIMEOUT_REAL_MS || '90000', 10) ); const NAVIGATION_TIMEOUT_MS = Math.max(15000, parseInt(process.env.NAVIGATION_TIMEOUT_MS || '60000', 10)); /** rebrowser-puppeteer-core / puppeteer-core 仅支持 load|domcontentloaded|networkidle0|networkidle2,不支持 commit */ const ALLOWED_NAVIGATION_WAIT_UNTIL = ['load', 'domcontentloaded', 'networkidle0', 'networkidle2']; function resolveNavigationWaitUntil() { const raw = (process.env.NAVIGATION_WAIT_UNTIL || 'domcontentloaded').trim(); if (ALLOWED_NAVIGATION_WAIT_UNTIL.includes(raw)) { return raw; } if (raw === 'commit') { console.warn('[constants] NAVIGATION_WAIT_UNTIL=commit 不被当前 Puppeteer 支持,已降级为 domcontentloaded'); return 'domcontentloaded'; } console.warn(`[constants] 未知 NAVIGATION_WAIT_UNTIL=${raw},已降级为 domcontentloaded`); return 'domcontentloaded'; } const NAVIGATION_WAIT_UNTIL = resolveNavigationWaitUntil(); const NAVIGATION_MAX_RETRIES = Math.max(0, parseInt(process.env.NAVIGATION_MAX_RETRIES || '1', 10)); const PAGE_DEFAULT_TIMEOUT_MS = Math.max(15000, parseInt(process.env.PAGE_DEFAULT_TIMEOUT_MS || '30000', 10)); const CAPTCHA_PROVIDER = (process.env.CAPTCHA_PROVIDER || 'capsolver').toLowerCase(); const CAPTCHA_API_KEY = process.env.CAPTCHA_API_KEY || ''; const CAPTCHA_MAX_WAIT_MS = Math.max(30000, parseInt(process.env.CAPTCHA_MAX_WAIT_MS || '120000', 10)); const SESSION_TTL_MS = Math.max(60000, parseInt(process.env.SESSION_TTL_MS || '7200000', 10)); const PERSIST_BROWSER_PROFILE = process.env.PERSIST_BROWSER_PROFILE !== '0'; const PROFILE_MAX_AGE_MS = Math.max(0, parseInt(process.env.PROFILE_MAX_AGE_MS || String(7 * 24 * 3600 * 1000), 10)); const POOL_IDLE_MS = Math.max(60000, parseInt(process.env.POOL_IDLE_MS || '300000', 10)); const MAX_POOLED_BROWSERS = Math.max(0, parseInt(process.env.MAX_POOLED_BROWSERS || '4', 10)); const POOL_ENABLED = process.env.POOL_ENABLED !== '0' && MAX_POOLED_BROWSERS > 0; module.exports = { DEFAULT_CHROME_EXECUTABLE, DEFAULT_UA, BASE_LAUNCH_ARGS, RUNTIME_DIR, ensureRuntimeSubdir, resolveChromeExecutable, buildBrowserLaunchEnv, assertChromeExecutable, MAX_CONCURRENT_BROWSERS, MAX_CONCURRENT_BROWSERS_REAL, QUEUE_TIMEOUT_MS, API_INTERCEPT_TIMEOUT_MS, API_INTERCEPT_TIMEOUT_REAL_MS, NAVIGATION_TIMEOUT_MS, NAVIGATION_WAIT_UNTIL, NAVIGATION_MAX_RETRIES, PAGE_DEFAULT_TIMEOUT_MS, CAPTCHA_PROVIDER, CAPTCHA_API_KEY, CAPTCHA_MAX_WAIT_MS, SESSION_TTL_MS, PERSIST_BROWSER_PROFILE, PROFILE_MAX_AGE_MS, POOL_IDLE_MS, MAX_POOLED_BROWSERS, POOL_ENABLED, };