Node.js Puppeteer Core导航超时问题及爬取笔记本规格优化求助
爬取bhphotovideo.com笔记本规格的问题分析与解决方案
问题描述
作为网页爬取新手,在爬取bhphotovideo.com笔记本规格时遇到两类问题:
- 初始代码循环执行
await page.waitForSelector(specSelector)时频繁触发TimeoutError: Navigation timeout of 30000 ms exceeded - 参考修改代码后,爬取18个条目时出现
ProtocolError: Target.attachToTarget timed out,任务失败
初始代码核心逻辑是复用单个页面跳转产品规格页,爬取后返回列表页;修改后改为循环创建新页面爬取,但引入了CDP会话处理验证码。
错误原因分析
初始代码超时原因
- 单个页面反复跳转+
page.goBack(),容易触发网站反爬机制,导致页面加载被限制 - 默认30秒导航超时过短,部分产品规格页加载资源较多,无法在超时内完成
waitForSelector未设置合理超时,且未处理元素不存在的情况
修改后ProtocolError原因
- 循环创建大量新页面+CDP会话,浏览器进程资源占用过高,导致Chrome DevTools Protocol连接不稳定
- CDP会话创建时机错误:在
page.goto()之前调用page.target().createCDPSession(),此时页面尚未完全初始化,容易触发连接超时 - 未设置
protocolTimeout,仅调整了导航超时,CDP命令本身的超时未覆盖 - 固定5秒延迟不足以规避反爬,网站可能针对高频请求限制连接
具体解决方案
1. 优化页面资源使用,减少页面创建
复用单个页面进行爬取,避免频繁创建销毁页面导致的资源浪费:
// 初始化单个页面 const page = await browser.newPage(); page.setDefaultNavigationTimeout(2 * 60 * 1000); page.setDefaultTimeout(2 * 60 * 1000); // 爬取所有链接后,循环复用该页面 for (const [index, link] of allLinks.entries()) { try { await page.goto(`${link}/specs`, { waitUntil: "domcontentloaded", timeout: 60 * 1000 }); // 等待规格元素加载,增加重试逻辑 await page.waitForSelector("tr.pair_KqJ3Q3GPKv", { timeout: 60 * 1000 }); // 验证码处理(如果需要) const client = await page.target().createCDPSession(); const { status } = await client.send("Captcha.solve", { detectTimeout: 30 * 1000 }); // 爬取逻辑... } catch (err) { console.error(`爬取${link}失败: ${err}`); // 失败后刷新页面,继续下一个 await page.reload({ waitUntil: "domcontentloaded" }); continue; } }
2. 调整CDP会话创建时机
将CDP会话创建移至page.goto()之后,确保页面初始化完成:
// 错误写法:在goto前创建CDP // const client = await page.target().createCDPSession(); // await page.goto(...) // 正确写法 await page.goto(`${link}/specs`, { waitUntil: "domcontentloaded" }); const client = await page.target().createCDPSession(); // 页面加载完成后再创建会话
3. 完善全局超时设置
启动浏览器时设置protocolTimeout,覆盖CDP命令的超时限制:
const browser = await puppeteer.launch({ // 其他配置 protocolTimeout: 3 * 60 * 1000, // 设置为3分钟 });
4. 增强反爬规避策略
- 随机延迟替代固定5秒延迟:
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 5000 + 3000)); // 3-8秒随机延迟 // 在page.goto前调用 await randomDelay();
- 设置真实用户代理:
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36');
- 禁用图片和非必要资源加载,加快页面加载速度:
await page.setRequestInterception(true); page.on('request', (req) => { if (['image', 'stylesheet', 'font', 'script'].includes(req.resourceType()) && !req.url().includes('bhphotovideo.com')) { req.abort(); } else { req.continue(); } });
5. 优化错误处理逻辑
移除return语句,避免单个条目失败导致整个任务终止;增加重试机制:
// 初始代码中的错误处理 catch (error) { console.error(`Error occurred during scraping laptop ${i + 1}: ${error}`); // return; // 移除这个return,改为继续循环 continue; }
最终优化后核心代码示例
const puppeteer = require('puppeteer'); const urls = [/* 你的过滤页面链接 */]; const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 5000 + 3000)); const scrapeBHPhotoVideo = async () => { const browser = await puppeteer.launch({ headless: 'new', protocolTimeout: 3 * 60 * 1000, // 设置CDP协议超时 }); const allLinks = []; // 第一步:爬取所有产品链接,复用页面 const linkPage = await browser.newPage(); await linkPage.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'); linkPage.setDefaultNavigationTimeout(2 * 60 * 1000); for (const url of urls) { await randomDelay(); try { await linkPage.goto(url, { waitUntil: "domcontentloaded", timeout: 60 * 1000 }); await linkPage.waitForSelector('a[data-selenium="miniProductPageProductNameLink"]', { timeout: 60 * 1000 }); const links = await linkPage.evaluate(() => [...document.querySelectorAll('a[data-selenium="miniProductPageProductNameLink"]')].map(link => link.href) ); allLinks.push(...links); } catch (err) { console.error(`获取链接失败 ${url}: ${err}`); continue; } } await linkPage.close(); // 第二步:爬取产品规格,复用单个页面 const specPage = await browser.newPage(); await specPage.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'); specPage.setDefaultNavigationTimeout(2 * 60 * 1000); specPage.setDefaultTimeout(2 * 60 * 1000); // 禁用非必要资源加载 await specPage.setRequestInterception(true); specPage.on('request', (req) => { if (['image', 'font'].includes(req.resourceType())) { req.abort(); } else { req.continue(); } }); for (const [index, link] of allLinks.entries()) { await randomDelay(); try { await specPage.goto(`${link}/specs`, { waitUntil: "domcontentloaded", timeout: 60 * 1000 }); await specPage.waitForSelector("tr.pair_KqJ3Q3GPKv", { timeout: 60 * 1000 }); // 验证码处理(如果需要) const client = await specPage.target().createCDPSession(); const { status } = await client.send("Captcha.solve", { detectTimeout: 30 * 1000 }); console.log(`验证码状态: ${status}`); const specs = await specPage.evaluate(() => { const specs = { laptopbrand: document.querySelector('h1[data-selenium="productTitle"]')?.innerText || '', processor: '', cpu: '', gpu: '', graphics_type: '', ram: '', storage: '', display: '', resolution: '', refresh_rate: '', panel_type: '', built_in_speaker: '', built_in_microphone: '', network_io: '', usb_ports: '', webcam: '', cellular: '', bluetooth: '', wifi: '', battery: '', dimension: '', weight: '', }; const rows = document.querySelectorAll("tr.pair_KqJ3Q3GPKv"); rows.forEach((el) => { const label = el.querySelector("td.label_KqJ3Q3GPKv")?.textContent.trim() || ''; const value = el.querySelector("td.value_KqJ3Q3GPKv span:not(.notSpecfMon)")?.textContent.trim() || ''; switch (label) { case "Processor": specs.processor = value; break; case "CPU": specs.cpu = value; break; case "GPU": specs.gpu = value; break; case "Graphics Type": specs.graphics_type = value; break; case "Total Installed Memory": specs.ram = value; break; case "Total Installed Capacity": specs.storage = value; break; case "Display Size": specs.display = value; break; case "Resolution": specs.resolution = value; break; case "Refresh Rate": specs.refresh_rate = value; break; case "Panel Type": specs.panel_type = value; break; case "Built-In Speakers": specs.built_in_speaker = value; break; case "Built-In Microphones": specs.built_in_microphone = value; break; case "Network I/O": specs.network_io = value; break; case "Inputs/Outputs": specs.usb_ports = value; break; case "Webcam": specs.webcam = value; break; case "Cellular Support": specs.cellular = value; break; case "Bluetooth": specs.bluetooth = value; break; case "Wi-Fi": specs.wifi = value; break; case "Capacity": specs.battery = value; break; case "Dimensions": specs.dimension = value; break; case "Weight": specs.weight = value; break; default: break; } }); return specs; }); console.log(`已爬取第${index+1}个产品:`, specs); // 这里调用saveToDatabase // await saveToDatabase([specs], "Dell"); } catch (err) { console.error(`爬取第${index+1}个产品失败 ${link}: ${err}`); // 失败后刷新页面,避免页面状态异常 await specPage.reload({ waitUntil: "domcontentloaded" }); continue; } } await specPage.close(); await browser.close(); }; scrapeBHPhotoVideo();
内容的提问来源于stack exchange,提问作者CFCIFE
相关产品推荐
相关产品推荐

