Node.js使用axios并行执行上千个请求出现连接报错如何解决?
报错原因
你当前的代码会一次性并发发起1417个HTTP请求,远超客户端系统的最大socket限制,同时也会触发目标站点的限流策略,因此会出现连接超时、连接重置、socket挂断这类错误。
解决方案
1. 限制请求并发数
不要直接把所有请求丢给Promise.all执行,控制同一时间发起的请求数量,建议设置为5-20之间(可根据目标站点的耐受度调整)。
原生JS分批实现(无需第三方库)
// 自定义分批执行函数,concurrency为并发数 async function batchRun(tasks, concurrency = 10) { const result = [] for (let i = 0; i < tasks.length; i += concurrency) { const batchTasks = tasks.slice(i, i + concurrency).map(task => scrapeContent(task)) const batchRes = await Promise.all(batchTasks) result.push(...batchRes) } return result } // 修改执行函数 async function scraper(links) { try { const newsContent = await batchRun(links, 8) // 一次并发跑8个请求 console.log(newsContent) } catch (error) { console.log({ in: "scraper", message: error.message }) } }
用p-limit库实现(更灵活)
先执行安装命令:npm i p-limit
const pLimit = require('p-limit') const limit = pLimit(8) // 并发数设为8 async function scraper(links) { try { const promises = links.map(item => limit(() => scrapeContent(item))) const newsContent = await Promise.all(promises) console.log(newsContent) } catch (error) { console.log({ in: "scraper", message: error.message }) } }
2. 优化axios配置
提前创建复用的axios实例,开启连接复用、设置超时、新增重试逻辑,减少连接开销和临时网络波动的影响:
const axios = require('axios') const https = require('https') // 创建复用的axios实例 const httpClient = axios.create({ timeout: 15000, // 15秒超时 // 开启keep-alive复用TCP连接,减少TLS握手开销 httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 10, // 最大socket数和并发数对应 // 仅当目标站点证书存在问题时开启以下配置,生产环境不建议开启 // rejectUnauthorized: false }), headers: { // 模拟浏览器UA,避免被反爬拦截 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' } }) // 自定义请求重试方法,最多重试2次 async function requestWithRetry(url, retries = 2) { try { return await httpClient.get(url) } catch (err) { if (retries > 0) { // 重试前等待1秒,避免触发站点限流 await new Promise(resolve => setTimeout(resolve, 1000)) return requestWithRetry(url, retries - 1) } throw err } } // 修改内容提取函数的请求逻辑 async function scrapeContent(item) { try { let content = ""; // 使用带重试的请求方法 const { data } = await requestWithRetry(item.newsLink); const $ = cheerio.load(data); const listItems = $(item.selector); listItems.each((_, item) => { content += $(item).text(); }); return { ...item, content: content.replace(/[\r\n\"\t]+/gm, "") }; } catch (error) { console.log({ in: "scrapeContent", link: item.newsLink, message: error.message }); // 失败请求返回带错误标记的结果,避免最终结果出现undefined return { ...item, content: "", error: error.message } } }
3. 额外优化建议
- 如果目标站点反爬严格,可以在两次请求之间加随机的延迟时间,进一步降低被限流的概率
- 并发数不要设置过高,建议先从5开始测试,慢慢上调到不报错的最大值
内容的提问来源于stack exchange,提问作者khaled taha
相关产品推荐
相关产品推荐

