Node.js使用request和cheerio编写的爬虫执行中途卡住无报错如何解决?
问题原因
- 无限制并发请求:脚本一次性发起大量HTTP请求,触发目标服务器限流规则,后续请求被挂起无响应,由于默认无超时配置,脚本会一直卡住不会抛出错误。
- getData函数逻辑错误:所有请求共享同一个
nitems数组存储结果,每个Promise都返回完整的nitems,最终处理后会出现数倍的冗余链接,进一步放大并发压力。 - 缺少请求校验规则:仅判断了基础请求错误,未校验HTTP响应状态码(如429限流、403禁止访问等场景不会触发基础错误,代码会继续执行无效逻辑),也未设置超时时间,异常请求会无限等待。
- 域名拼接错误:第二步、第三步要抓取的页面域名为egpostal.com,代码统一用了egyptcodebase.com的base链接拼接,会生成大量无效链接。
修复方案
- 封装通用请求方法,新增超时配置和HTTP状态码校验,异常请求直接抛出错误避免卡住。
- 修复getData逻辑,每个请求单独维护返回的链接数组,避免生成冗余重复链接。
- 增加并发请求控制,限制同时发起的请求数量,避免触发服务器限流。
- 修正不同域名的拼接规则,保证链接有效性。
修改后代码示例
首先需要安装并发控制依赖:npm install p-limit
const request = require('request'); const cheerio = require('cheerio'); // 限制并发数为5,可根据实际访问情况上下调整 const limit = require('p-limit')(5); const firstPageLink = 'https://www.egyptcodebase.com/en/p/all'; const egyptBase = 'https://www.egyptcodebase.com/en/'; const egPostalBase = 'https://egpostal.com/en/'; // 通用请求封装,加超时和状态码校验 const fetchPage = (url) => { return new Promise((resolve, reject) => { request({ url: url, timeout: 10000 // 10秒超时,可自行调整 }, (error, response, html) => { if (error) return reject(new Error(`请求失败:${url},错误信息:${error.message}`)); if (response.statusCode < 200 || response.statusCode >= 400) { return reject(new Error(`请求异常:${url},状态码:${response.statusCode}`)); } resolve(cheerio.load(html)); }) }) } // 抓取省份链接 const getProvinceLinks = async (link) => { const $ = await fetchPage(link); const items = []; $('.table tbody tr').each(function() { items.push(egyptBase + $(this).find("a[href]").attr("href")); }); return items; }; // 抓取办事处链接 const getOfficeLinks = async (provinceLinks) => { const promises = provinceLinks.map(url => limit(async () => { const $ = await fetchPage(url); const currentItems = []; $('.table tbody tr').each(function() { currentItems.push(egPostalBase + $(this).find("a[href]").attr("href")); }); return currentItems; })) const result = await Promise.all(promises); return result.flat(1); } // 抓取详情页标题 const fetchTitles = async (officeLinks) => { const promises = officeLinks.map(url => limit(async () => { const $ = await fetchPage(url); const title = $(".home-title > h2").eq(0).text().trim(); console.log({ title: title, link: url }); return title; })) return Promise.all(promises); } // 主函数 (async () => { try { const provinceLinks = await getProvinceLinks(firstPageLink); console.log(`共抓取到${provinceLinks.length}个省份链接`); const officeLinks = await getOfficeLinks(provinceLinks); console.log(`共抓取到${officeLinks.length}个办事处链接`); const titles = await fetchTitles(officeLinks); console.log('===== 抓取完成,所有标题如下 ====='); titles.forEach(title => console.log(title)); } catch (err) { console.error('执行出错:', err); } })();
如果你不想安装额外依赖,可以用原生分批请求逻辑替换并发控制,每次处理固定数量的请求,等当前批次执行完成后再处理下一批即可。
内容的提问来源于stack exchange,提问作者MITHU
相关产品推荐
相关产品推荐

