Promise分组串行执行异常:爬虫任务未按组分批启动
问题根源
你当前代码的核心问题是:用links.map(async ...)生成Promise数组时,所有爬虫任务已经立刻启动执行了。
因为async函数只要被调用,就会立即触发内部的异步逻辑(比如await page.browser().newPage()),所以不管你后续怎么分组、怎么调用await Promise.all,所有爬虫任务在map阶段就已经全部并行跑起来了,完全达不到"组间串行"的效果。
解决方案
要实现分批串行+组内并行,核心是延迟Promise的创建与执行——只在处理当前批次时,才为该批次的链接生成Promise并启动任务。
方案1:直接分批遍历链接(最简洁)
不需要额外的拆分函数,直接在循环里按批次处理链接:
async function crawler(links: string[], page: Page): Promise<MyData[]> { const list: MyData[] = []; const batchSize = 3; // 每组最大并行数 // 按批次遍历链接 for (let i = 0; i < links.length; i += batchSize) { // 取出当前批次的链接 const currentBatchLinks = links.slice(i, i + batchSize); // 为当前批次的链接创建并启动爬虫任务(此时才真正开始执行) const batchPromises = currentBatchLinks.map(async (link) => { try { const newPage = await page.browser().newPage(); const detail = await crawlLink(link, newPage); await newPage.close(); return detail; } catch (e) { console.log(e); return null as MyData; } }); // 等待当前批次所有任务完成,再进入下一批 const batchResults = await Promise.all(batchPromises); // 过滤无效结果并收集 const validResults = batchResults.filter(detail => detail !== null) as MyData[]; list.push(...validResults); } return list; }
方案2:复用拆分逻辑(拆分链接数组而非Promise数组)
如果你想保留拆分逻辑,需要修改拆分函数的处理对象——拆分原始链接数组,而非提前生成的Promise数组:
// 修改拆分函数,处理普通数组而非Promise数组 export function splitArray<T>(arr: T[], maxPerItem: number): T[][] { const splitList: T[][] = []; let currentSplit: T[] = []; for (let i = 0; i < arr.length; i++) { currentSplit.push(arr[i]); if (currentSplit.length === maxPerItem || i === arr.length - 1) { splitList.push(currentSplit); currentSplit = []; } } return splitList; } // 修改爬虫函数 async function crawler(links: string[], page: Page): Promise<MyData[]> { const list: MyData[] = []; // 拆分链接数组为批次 const groupedLinks = splitArray(links, 3); for (const linkGroup of groupedLinks) { // 为当前批次链接创建并启动任务 const batchPromises = linkGroup.map(async (link) => { try { const newPage = await page.browser().newPage(); const detail = await crawlLink(link, newPage); await newPage.close(); return detail; } catch (e) { console.log(e); return null as MyData; } }); // 等待当前批次完成 const batchResults = await Promise.all(batchPromises); const validResults = batchResults.filter(detail => detail !== null) as MyData[]; list.push(...validResults); } return list; }
关键总结
- 不要提前生成所有Promise,async函数一调用就会启动异步任务,这是你之前代码失效的核心原因
- 正确的逻辑是:先拆分任务单元(链接),再逐批处理,每批内部用
Promise.all并行执行,批与批之间用await保证串行
内容的提问来源于stack exchange,提问作者Andurit
相关产品推荐
相关产品推荐

