Puppeteer 的性能优化对于提高爬虫效率、降低资源消耗和提升测试速度至关重要。以下是一些关键的优化策略和最佳实践。
1. 浏览器启动优化
使用合适的启动参数:
javascriptconst browser = await puppeteer.launch({ headless: 'new', // 使用新的无头模式(更快) args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', // 避免内存问题 '--disable-accelerated-2d-canvas', '--disable-gpu', '--window-size=1920,1080' ] });
复用浏览器实例:
javascript// 不好的做法:每次任务都启动新浏览器 async function badApproach(urls) { for (const url of urls) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); await browser.close(); } } // 好的做法:复用浏览器实例 async function goodApproach(urls) { const browser = await puppeteer.launch(); for (const url of urls) { const page = await browser.newPage(); await page.goto(url); await page.close(); } await browser.close(); }
2. 页面加载优化
优化 waitUntil 选项:
javascript// 根据需求选择合适的等待策略 await page.goto(url, { waitUntil: 'domcontentloaded' // 最快,DOM 加载完成 }); await page.goto(url, { waitUntil: 'load' // 默认,所有资源加载完成 }); await page.goto(url, { waitUntil: 'networkidle0' // 500ms 内没有网络请求 }); await page.goto(url, { waitUntil: 'networkidle2' // 500ms 内不超过 2 个网络请求 });
禁用不必要的资源:
javascriptawait page.setRequestInterception(true); page.on('request', (request) => { const resourceType = request.resourceType(); // 阻止图片、字体、媒体等资源 if (['image', 'font', 'media', 'stylesheet'].includes(resourceType)) { request.abort(); } else { request.continue(); } });
缓存策略:
javascript// 启用缓存 await page.setCacheEnabled(true); // 禁用缓存(每次都重新加载) await page.setCacheEnabled(false);
3. 并发处理
使用 Promise.all 并行处理:
javascriptconst urls = ['url1', 'url2', 'url3']; const browser = await puppeteer.launch(); // 并行处理多个页面 await Promise.all(urls.map(async (url) => { const page = await browser.newPage(); await page.goto(url); await page.screenshot({ path: `${url}.png` }); await page.close(); })); await browser.close();
控制并发数量:
javascriptasync function processWithConcurrency(urls, concurrency = 3) { const browser = await puppeteer.launch(); const results = []; for (let i = 0; i < urls.length; i += concurrency) { const batch = urls.slice(i, i + concurrency); const batchResults = await Promise.all( batch.map(async (url) => { const page = await browser.newPage(); await page.goto(url); const data = await page.evaluate(() => document.body.innerText); await page.close(); return data; }) ); results.push(...batchResults); } await browser.close(); return results; }
4. 内存管理
及时关闭页面:
javascript// 不好的做法:不关闭页面 async function badMemoryUsage(urls) { const browser = await puppeteer.launch(); for (const url of urls) { const page = await browser.newPage(); await page.goto(url); // 没有关闭页面,内存会持续增长 } await browser.close(); } // 好的做法:及时关闭页面 async function goodMemoryUsage(urls) { const browser = await puppeteer.launch(); for (const url of urls) { const page = await browser.newPage(); await page.goto(url); await page.close(); // 及时关闭页面 } await browser.close(); }
使用上下文隔离:
javascriptconst context = await browser.createIncognitoBrowserContext(); const page = await context.newPage(); // 操作页面 await context.close(); // 关闭上下文,清理所有资源
清理 Cookie 和存储:
javascript// 清除 Cookie await page.deleteCookie(...await page.cookies()); // 清除所有存储 await page.evaluate(() => { localStorage.clear(); sessionStorage.clear(); });
5. 选择器优化
使用高效的选择器:
javascript// 不好的做法:使用通用选择器 const elements = await page.$$('div'); // 慢 // 好的做法:使用具体的选择器 const elements = await page.$$('.item'); // 快 // 更好的做法:使用 ID 选择器 const element = await page.$('#unique-id'); // 最快
避免重复查询:
javascript// 不好的做法:重复查询 const text1 = await page.$eval('.title', el => el.textContent); const text2 = await page.$eval('.title', el => el.textContent); // 好的做法:缓存元素 const element = await page.$('.title'); const text1 = await element.evaluate(el => el.textContent); const text2 = await element.evaluate(el => el.textContent);
6. 网络优化
使用 CDN 加速:
javascript// 如果有本地 Chromium,使用本地版本 const browser = await puppeteer.launch({ executablePath: '/path/to/local/chrome' });
设置超时时间:
javascript// 设置合理的超时时间 await page.goto(url, { timeout: 30000 }); await page.waitForSelector('.element', { timeout: 5000 });
使用连接池:
javascript// 复用浏览器实例作为连接池 class BrowserPool { constructor(size = 3) { this.size = size; this.browsers = []; this.queue = []; } async init() { for (let i = 0; i < this.size; i++) { this.browsers.push(await puppeteer.launch()); } } async getBrowser() { if (this.browsers.length > 0) { return this.browsers.pop(); } return new Promise(resolve => this.queue.push(resolve)); } releaseBrowser(browser) { if (this.queue.length > 0) { this.queue.shift()(browser); } else { this.browsers.push(browser); } } }
7. 实际优化案例
案例 1:批量截图优化
javascriptasync function optimizedBatchScreenshots(urls) { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] }); // 禁用不必要的资源 await page.setRequestInterception(true); page.on('request', (request) => { if (['image', 'font', 'media'].includes(request.resourceType())) { request.abort(); } else { request.continue(); } }); // 并行处理 await Promise.all(urls.map(async (url, index) => { const page = await browser.newPage(); await page.goto(url, { waitUntil: 'domcontentloaded' }); await page.screenshot({ path: `screenshot-${index}.png` }); await page.close(); })); await browser.close(); }
案例 2:数据抓取优化
javascriptasync function optimizedScraping(urls) { const browser = await puppeteer.launch({ headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ] }); const results = []; for (const url of urls) { const page = await browser.newPage(); // 禁用图片加载 await page.setRequestInterception(true); page.on('request', (request) => { if (request.resourceType() === 'image') { request.abort(); } else { request.continue(); } }); // 快速加载 await page.goto(url, { waitUntil: 'domcontentloaded' }); // 批量获取数据 const data = await page.evaluate(() => { return Array.from(document.querySelectorAll('.item')).map(item => ({ title: item.querySelector('.title')?.textContent, price: item.querySelector('.price')?.textContent })); }); results.push(...data); await page.close(); } await browser.close(); return results; }
案例 3:监控和性能分析
javascriptasync function monitorPerformance(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 启用性能监控 const client = await page.target().createCDPSession(); await client.send('Performance.enable'); const startTime = Date.now(); await page.goto(url, { waitUntil: 'networkidle2' }); const loadTime = Date.now() - startTime; // 获取性能指标 const metrics = await client.send('Performance.getMetrics'); console.log('Load time:', loadTime); console.log('Metrics:', metrics); await browser.close(); }
8. 性能监控工具
使用 Chrome DevTools Protocol:
javascriptconst client = await page.target().createCDPSession(); // 启用性能监控 await client.send('Performance.enable'); // 获取性能指标 const metrics = await client.send('Performance.getMetrics'); // 启用网络监控 await client.send('Network.enable'); // 监听网络事件 client.on('Network.requestWillBeSent', (params) => { console.log('Request:', params.request.url); });
使用 Puppeteer 的性能追踪:
javascript// 开始追踪 await page.tracing.start({ path: 'trace.json' }); // 执行操作 await page.goto('https://example.com'); // 停止追踪 await page.tracing.stop();
9. 最佳实践总结
1. 启动优化:
- 使用
headless: 'new'模式 - 添加合适的启动参数
- 复用浏览器实例
2. 加载优化:
- 选择合适的
waitUntil策略 - 禁用不必要的资源
- 使用缓存
3. 并发优化:
- 使用
Promise.all并行处理 - 控制并发数量
- 使用连接池
4. 内存优化:
- 及时关闭页面和浏览器
- 使用上下文隔离
- 清理 Cookie 和存储
5. 选择器优化:
- 使用高效的选择器
- 避免重复查询
- 缓存元素引用
6. 网络优化:
- 设置合理的超时时间
- 使用本地 Chromium
- 优化网络请求
10. 常见性能问题及解决方案
问题 1:内存泄漏
javascript// 解决方案:及时清理资源 async function fixMemoryLeak() { const browser = await puppeteer.launch(); try { // 操作代码 } finally { await browser.close(); } }
问题 2:页面加载慢
javascript// 解决方案:优化加载策略 await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 10000 });
问题 3:并发过高导致崩溃
javascript// 解决方案:限制并发数量 const CONCURRENCY = 3; // 使用连接池或分批处理
问题 4:CPU 使用率过高
javascript// 解决方案:禁用不必要的功能 const browser = await puppeteer.launch({ args: [ '--disable-gpu', '--disable-dev-shm-usage' ] });