You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js+Puppeteer中用异步IO循环实现多标签页爬取

在Node.js + Puppeteer中实现固定并发数的标签页爬取池

你在Go里用Worker Pool+通道控制并发的思路非常靠谱,放到Node.js+Puppeteer的场景里,我们可以通过异步任务队列+并发数控制来实现一模一样的效果——固定数量的标签页并行运行,同时在等待网络IO的间隙处理新任务,最大化爬取效率。

先给你一个完整的可运行示例,然后拆解关键逻辑:

const puppeteer = require('puppeteer');

async function runCrawler() {
  // 配置参数
  const maxThreads = 5; // 你想要的固定标签页数量
  const uncrawledLinks = new Map([
    ['https://example.com/page1', false],
    ['https://example.com/page2', false],
    ['https://example.com/page3', false],
    // 这里替换成你的实际链接集合
  ]);
  
  // 把未爬取链接转成任务队列(方便动态取任务)
  const taskQueue = Array.from(uncrawledLinks.keys());
  let runningThreads = 0;
  const browser = await puppeteer.launch({ headless: 'new' });

  // 单个任务的处理函数(Worker)
  async function handleLink(link) {
    let page = null;
    try {
      runningThreads++;
      console.log(`启动新任务:${link},当前并发数:${runningThreads}`);
      
      page = await browser.newPage();
      // 这里写你的爬取逻辑:比如跳转页面、提取数据等
      await page.goto(link, { waitUntil: 'networkidle2' });
      const pageTitle = await page.title();
      console.log(`完成爬取:${link},标题:${pageTitle}`);
      
      // 标记为已爬取
      uncrawledLinks.set(link, true);
    } catch (error) {
      console.error(`爬取失败:${link},错误:${error.message}`);
    } finally {
      // 不管成功失败,都要关闭页面、释放并发名额
      if (page) await page.close();
      runningThreads--;
      console.log(`任务结束:${link},当前并发数:${runningThreads}`);
      
      // 队列还有任务的话,继续启动下一个
      if (taskQueue.length > 0) {
        const nextLink = taskQueue.shift();
        await handleLink(nextLink);
      }
    }
  }

  // 初始化启动第一批任务,填满并发池
  while (runningThreads < maxThreads && taskQueue.length > 0) {
    const link = taskQueue.shift();
    // 这里用void,避免阻塞初始化流程,让任务异步执行
    void handleLink(link);
  }

  // 等待所有任务完成(可选,如果你需要在所有任务结束后做清理)
  while (runningThreads > 0) {
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  await browser.close();
  console.log("所有爬取任务完成");
}

runCrawler();

关键逻辑拆解

  1. 任务队列+并发计数器:

    • 用taskQueue存储待爬取的链接,避免直接在for...of里用await导致的串行执行问题。
    • runningThreads跟踪当前活跃的标签页数量,确保不超过maxThreads的限制,防止浏览器资源耗尽。
  2. Worker函数的自驱动:

    • 每个handleLink任务完成后,会自动检查队列是否还有剩余任务,如果有就启动下一个,这样就能在网络IO等待的间隙自动填充并发池,不用手动循环等待。
    • finally块里的清理逻辑非常重要:必须关闭页面释放资源,同时减少并发计数器,否则会导致浏览器资源泄漏或者并发数失控。
  3. 初始化并发池:

    • 启动时先填满maxThreads个任务,让并发池一开始就处于满负荷状态,最大化爬取效率。
    • 用void handleLink(link)是为了让任务异步执行,不会阻塞初始化的循环,确保第一批任务能同时启动。

优化建议

  • 页面复用:如果你的爬取逻辑比较固定,可以维护一个页面池(比如用数组存储闲置页面),不用每次都新建/关闭页面,能大幅提升性能。
  • 错误重试:可以在catch块里把失败的链接重新放回队列,设置重试次数上限,避免因网络波动导致的任务失败。
  • 动态添加任务:如果爬取过程中发现新的链接,可以直接push到taskQueue里,实现增量爬取。

内容的提问来源于stack exchange,提问作者Vega

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:12:23