Nightmare.js网页爬取超时与串行执行请求问题求助
解决Nightmare.js爬取时的超时、多实例与串行执行问题
看起来你遇到的几个问题核心都是并发实例过多和缺少串行控制导致的,咱们一步步拆解解决:
1. 为什么会弹出20个Electron实例?
你的getPrecos函数每次被调用都会新建一个Nightmare实例,而getUrls里用forEach直接批量触发getPrecos,相当于同时启动了20个Electron进程,自然会弹出一堆窗口。
解决办法:复用同一个Nightmare实例,或者串行处理每个链接(一次只爬一个详情页),这样只会有一个Electron窗口在运行。
2. 部分请求超时的原因
- 默认的
.wait()超时是30秒,你只设置了gotoTimeout,没覆盖waitTimeout,所以元素加载慢的时候就会触发超时。 - 并发过多会导致Electron资源占用过高,页面加载速度被拖慢,进一步加剧超时问题。
3. 如何实现前一个请求完成再触发下一个?
用async/await配合循环来实现串行执行,避免并行调用导致的资源混乱。
修改后的完整代码
const { csvFormat } = require('d3-dsv'); const Nightmare = require('nightmare'); const { writeFileSync } = require('fs'); const url = 'https://lojaonline.claro.com.br/celular'; // 全局复用一个Nightmare实例,避免重复创建 const nightmare = new Nightmare({ show: true, // 这里只需要设置一次,全程只会弹出一个窗口 gotoTimeout: 999999, waitTimeout: 60000, // 把wait超时改成60秒,适配页面加载慢的情况 }); async function getUrls() { console.log('Extraindo Links...'); const p1 = '51030'; const p2 = '560'; try { // 执行初始页面的CEP输入操作 const result = await nightmare .goto(url) .wait('input[id="edit-cep-part1"]') .type('input[id="edit-cep-part1"]', p1) .wait('input[id="edit-cep-part2"]') .type('input[id="edit-cep-part2"]', p2) .click('input[value="Confirmar"]') .wait('#products-container .products-list') .evaluate(() => { return Array.from(document.querySelectorAll('.offer')).map(element => element.href); }); const listaUrls = result; // evaluate返回的已经是数组,无需Object.values转换 console.log(`共找到 ${listaUrls.length} 个商品链接`); // 用for...of循环配合await实现串行处理 for (const link of listaUrls) { console.log('Pegando preços de ' + link); await getPrecos(link); } // 所有任务完成后关闭实例 await nightmare.end(); } catch (e) { console.error('获取链接时出错:', e); await nightmare.end(); } }; async function getPrecos(endereco) { console.log('Extraindo preços...'); const p1 = '51030'; const p2 = '560'; try { const result = await nightmare .goto(endereco) .wait('input[id="edit-cep-part1"]') .type('input[id="edit-cep-part1"]', p1) .wait('input[id="edit-cep-part2"]') .type('input[id="edit-cep-part2"]', p2) .click('input[value="Confirmar"]') .wait('#plans-tab') .evaluate(() => { return Array.from(document.querySelectorAll('tr.body')).map(element => element.innerText); }); const listaPrecos = result; console.log(listaPrecos); // 这里可以把价格数据存入数组,最后统一写入CSV文件 } catch (e) { console.error(`获取${endereco}价格时出错:`, e); } }; // 启动爬取流程 getUrls();
关键优化点说明:
- 复用Nightmare实例:全程只创建一个实例,既避免多窗口弹窗,也减少系统资源占用。
- 串行执行控制:用
for...of循环配合await,确保前一个详情页爬取完成后再处理下一个,彻底解决并发冲突。 - 统一超时配置:给实例设置
waitTimeout,覆盖默认的30秒,适应网站加载慢的情况。 - 完善错误处理:每个异步操作都用
try/catch包裹,出错时保证实例能正常关闭,避免进程残留。 - 精简冗余代码:移除不必要的
Object.values转换,evaluate返回的结果已经是目标数组。
如果还是遇到超时,可以尝试:
- 进一步增加
waitTimeout的时长(比如120秒) - 在
.wait()前添加.wait(2000)强制等待2秒,确保页面DOM完全渲染 - 检查选择器是否准确,部分网站可能会动态修改DOM结构,导致选择器匹配失败
内容的提问来源于stack exchange,提问作者Joao Victor
相关产品推荐
相关产品推荐

