读取含大量URL的CSV触发Promise内存泄漏告警,求分批处理方案
解决批量检测网站CSP/XFO策略时的内存泄漏与EventEmitter超限问题
你遇到的问题很典型:当用Promise.all一次性启动100个Puppeteer浏览器实例时,每个实例都会给process对象添加多组事件监听器(exit、SIGINT等),超出了Node.js默认的10个监听器上限,同时大量并发的浏览器进程也会快速耗尽内存,触发泄漏告警。
最根本的解决方案是分批处理任务——控制同时运行的检测任务数量,比如每次只跑10个,完成一批后再启动下一批。这样既能避免监听器超限,也能降低内存占用。下面是具体的实现方案:
第一步:编写分批处理工具函数
先封装一个通用的分批执行函数,用来精准控制并发数:
async function batchProcess(items, batchSize, processFn) { const results = []; // 循环分割数组,逐批处理 for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); // 等待当前批所有任务完成后,再处理下一批 const batchResults = await Promise.all(batch.map(processFn)); results.push(...batchResults); } return results; }
这个函数会把传入的任务数组按指定的batchSize拆分,确保同一时间只有指定数量的任务在运行,从根源上控制资源占用。
第二步:改造readCSV函数
修改原有的readCSV逻辑,不再一次性启动所有任务,而是用分批函数处理,同时过滤掉表头和无效空行:
function readCSV(csv) { const lines = csv.split("\n").filter(line => line.trim() !== ""); // 过滤空行 const headers = lines[0].split(","); const dataLines = lines.slice(1); // 跳过表头,只处理数据行 // 调用分批处理,批大小设为10(可根据机器性能调整,比如8或15) batchProcess(dataLines, 10, async (line) => { const currentline = line.split(","); const url = `https://www.${currentline[1]}`; console.log(`Processing site: ${url}`); return check_page(url); }) .then(() => console.log('All site checks completed successfully')) .catch(err => console.error('Error during batch processing:', err)); }
第三步:优化check_page函数
原函数里用XMLHttpRequest获取请求头是多余的,Puppeteer的page.goto会直接返回Response对象,通过它就能拿到响应头,还能避免额外引入第三方模块:
async function check_page(web_page) { let browser; try { browser = await puppeteer.launch(); const page = await browser.newPage(); // 捕获页面加载后的响应对象 const response = await page.goto(web_page, { waitUntil: 'domcontentloaded' }); const mainUrl = page.mainFrame().url(); console.log(`Checking headers for: ${mainUrl}`); // 直接从response提取响应头,转成小写键名的映射 const headers = response.headers(); const headerMap = Object.fromEntries( Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]) ); // 这里可以添加CSP/XFO的检测逻辑,比如: // const cspPolicy = headerMap['content-security-policy']; // const xfoPolicy = headerMap['x-frame-options']; } catch (err) { console.error(`Failed to check ${web_page}:`, err.message); } finally { // 确保浏览器实例被关闭,即使任务出错也不会残留进程 if (browser) await browser.close(); } }
额外说明:临时绕过监听器告警(不推荐)
如果你只是想临时跳过告警而不调整并发逻辑,可以在代码开头添加:
process.setMaxListeners(0); // 0表示取消监听器数量限制
但这只是治标不治本,大量并发的浏览器实例依然会占用极高内存,甚至导致进程崩溃,所以优先推荐分批处理方案。
内容的提问来源于stack exchange,提问作者Fra96
相关产品推荐
相关产品推荐

