高性能并发爬虫核心是控制并发量(5~10)、避免event loop阻塞、复用连接池;用promise.allsettled分批执行、safefetch封装指数退避重试、undici复用连接,禁用同步解析与瞬时高频请求。

用 Promise、async/await 和 Event Loop 编写高性能并发爬虫,核心不是堆叠并发数,而是控制并发量 + 避免事件循环阻塞 + 合理复用资源。JavaScript 单线程本质决定了:盲目开 100 个并发请求,反而会因 TCP 连接、内存、DNS 解析和事件队列积压导致性能下降甚至崩溃。
用 Promise.allSettled 控制固定并发数(推荐)
Promise.all 一失败就中断,不适合爬虫;Promise.allSettled 更健壮,但直接传入几百个请求仍会瞬间打满连接池。正确做法是分批执行:
- 定义最大并发数(如 5~10),避免触发浏览器/Node.js 默认限制(Chrome 约 6 个同域并发,Node 无硬限但受系统 socket 数影响)
- 把 URL 列表切片,每批启动不超过 maxConcurrent 个 fetch 请求
- 每批用 Promise.allSettled 等待全部完成,再处理下一批(不阻塞主线程,Event Loop 可调度其他任务)
示例(Node.js 环境,使用 node-fetch 或 undici):
async function fetchBatch(urls, maxConcurrent = 5) {
const results = [];
for (let i = 0; i
fetch(url, { signal: AbortSignal.timeout(8000) })
.then(r => r.text())
.catch(err => ({ error: err.message }))
);
const batchResults = await Promise.allSettled(promises);
results.push(...batchResults);
}
return results;
}用 async/await + 暂停策略防洪(节流与退避)
高频请求易被封 IP 或触发反爬。async/await 不仅让代码可读,还能自然插入延迟或重试逻辑:
- 每次请求后加
await new Promise(r => setTimeout(r, 200))实现简单节流(非必须,按目标站点容忍度调整) - 对失败请求做指数退避重试(最多 2~3 次),避免瞬时重试加重服务压力
- 把 fetch 封装成带重试的 async 函数,错误时 await 再试,不阻塞其他请求
示例重试封装:
async function safeFetch(url, retries = 2) {
for (let i = 0; i = 500 && i setTimeout(r, Math.pow(2, i) * 1000));
}
}
}利用 Event Loop 特性避免“假并发”陷阱
很多人以为 async/await 是多线程,其实仍是单线程调度。以下操作会意外阻塞 Event Loop:
- 在回调里做大量同步解析(如用 cheerio.load() 处理超大 HTML)——应拆成微任务或用 worker_threads(Node)
- 用 while(true) 等待条件满足 —— 改用 await sleep 或 Promise.resolve().then()
- 未限制日志/文件写入频率 —— 批量写入或用 stream 方式落盘
关键原则:所有耗时操作尽量异步化。比如解析 HTML 时:
// ✅ 推荐:用 setImmediate 或 queueMicrotask 让出控制权
function parseHTML(html) {
return new Promise(resolve => {
setImmediate(() => {
const $ = cheerio.load(html);
resolve({ title: $('title').text() });
});
});
}资源复用与连接管理(Node.js 场景重点)
默认 fetch 会为每次请求新建连接,开销大。Node.js 中应:
- 使用
undici(Node 官方 HTTP 客户端),支持连接池复用 - 创建全局 Agent 实例,设置 keepAlive、maxSockets(如 20)、timeout
- 避免在循环内重复 new Agent,否则失去复用意义
示例(undici):
import { Pool } from 'undici';
<p>const pool = new Pool('<a href="https://www.php.cn/link/b05edd78c294dcf6d960190bf5bde635">https://www.php.cn/link/b05edd78c294dcf6d960190bf5bde635</a>', {
connections: 20,
pipelining: 1,
headers: { 'user-agent': 'my-crawler' }
});</p><p>async function crawl(url) {
const { body, statusCode } = await pool.request({ path: url, method: 'GET' });
return body.text();
}</p>Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











