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

使用Puppeteer-Cluster并行爬虫:仅首页执行完成,其余页面无限加载

Puppeteer-Cluster并行爬取表单仅第一个页面执行成功问题解决

问题描述

我用puppeteer-cluster做并行网页爬取,脚本需要遍历多个网页并填写表单,但只有队列里的第一个页面能完整执行,后续页面都无限加载,没法完成任务。以下是我的代码:

const { Cluster } = require('puppeteer-cluster');

const args = [
    '--disable-features=site-per-process',
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-infobars',
    '--no-zygote',
    '--no-first-run',
    '--window-position=0,0',
    '--ignore-certificate-errors',
    '--ignore-certificate-errors-skip-list',
    '--disable-dev-shm-usage',
    '--disable-accelerated-2d-canvas',
    '--disable-gpu',
    '--hide-scrollbars',
    '--disable-notifications',
    '--disable-background-timer-throttling',
    '--disable-backgrounding-occluded-windows',
    '--disable-breakpad',
    '--disable-component-extensions-with-background-pages',
    '--disable-extensions',
    '--disable-features=TranslateUI,BlinkGenPropertyTrees',
    '--disable-ipc-flooding-protection',
    '--disable-renderer-backgrounding',
    '--enable-features=NetworkService,NetworkServiceInProcess',
    '--force-color-profile=srgb',
    '--metrics-recording-only',
    '--mute-audio'
];

(async () => {
  const cluster = await Cluster.launch({
    concurrency: Cluster.CONCURRENCY_PAGE,
    maxConcurrency: 4,
    puppeteerOptions: {
        headless: false,
        args: args,
        timeout: 300000, // Increased timeout to 5 minutes
        monitor: true
    }
  });

  await cluster.task(async ({ page, data: url }) => {
    await page.goto(url);
    await page.waitForSelector('#Office');
    await page.select('#Office', 'PEKING');
    await Promise.all([
        page.waitForNavigation({ timeout: 0 }), 
        page.click('input[type=submit][value=Next]')
    ]);

    await page.waitForSelector('#CalendarId');
    await page.select('#CalendarId', '20804258');
    await page.waitForSelector('input[type=submit][value=Next]');

    await Promise.all([
        page.waitForNavigation({ timeout: 0 }), 
        page.click('input[type=submit][value=Next]')
    ]);

    await page.waitForSelector('input[type=submit][value=Next]');

    await Promise.all([
        page.waitForNavigation({ timeout: 0 }), 
        page.click('input[type=submit][value=Next]')
    ]);

    await page.waitForSelector('input[type=submit][value=Next]');

    await Promise.all([
        page.waitForNavigation({ timeout: 0 }), 
        page.click('input[type=submit][value=Next]')
    ]);

    await page.waitForSelector('input[type=radio]');
    await page.click('input[type=radio]');

    await Promise.all([
        page.waitForNavigation({ timeout: 0 }), 
        page.click('input[type=submit][value=Next]')
    ]);

    await page.waitForSelector('#main > form');

    await page.type('#Lastname', 'Lastname');
    await page.type('#Firstname', 'Lastname');
    await page.type('#DateOfBirth', '15.03.2016');
    await page.type('#TraveldocumentNumber', '9999999999');
    await page.select('#Sex', '2');
    await page.type('#Street', 'Street');
    await page.type('#Postcode', '25001');
    await page.type('#City', 'City');
    await page.select('#Country', '3');
   });
  cluster.on('taskerror', (err, data, willRetry) => {
    if (willRetry) {
      console.warn(`Encountered an error while crawling ${data}. ${err.message}\nThis job will be retried`);
    } else {
      console.error(`Failed to crawl ${data}: ${err.message}`);
    }
  });

  cluster.queue('<some url>');
  cluster.queue('<some url>');
  // many more pages

  await cluster.idle();
  await cluster.close();
})();

问题分析与修复方案

1. 移除无限等待的timeout: 0

你在page.waitForNavigation中设置了timeout: 0,这会让页面无限等待导航完成,一旦后续页面出现资源加载卡住、跳转逻辑异常等情况,就会导致任务永久挂起。需要给所有导航和元素等待操作设置合理的超时时间,并指定明确的等待事件(比如networkidle2)。

2. 添加异常捕获与重试机制

在任务函数中加入try/catch块,捕获执行过程中的错误并抛出,配合集群的重试配置,避免单个任务失败阻塞整个队列。

3. 降低并发数避免反爬限制

目标网站可能对并发请求有限制,过高的并发会被拦截,导致页面加载异常。可以适当降低maxConcurrency的值。

4. 模拟真实用户输入

给page.type添加delay参数,模拟真实用户的输入速度,降低触发反爬机制的概率。

修改后的代码

const { Cluster } = require('puppeteer-cluster');

const args = [
    '--disable-features=site-per-process',
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-infobars',
    '--no-zygote',
    '--no-first-run',
    '--window-position=0,0',
    '--ignore-certificate-errors',
    '--ignore-certificate-errors-skip-list',
    '--disable-dev-shm-usage',
    '--disable-accelerated-2d-canvas',
    '--disable-gpu',
    '--hide-scrollbars',
    '--disable-notifications',
    '--disable-background-timer-throttling',
    '--disable-backgrounding-occluded-windows',
    '--disable-breakpad',
    '--disable-component-extensions-with-background-pages',
    '--disable-extensions',
    '--disable-features=TranslateUI,BlinkGenPropertyTrees',
    '--disable-ipc-flooding-protection',
    '--disable-renderer-backgrounding',
    '--enable-features=NetworkService,NetworkServiceInProcess',
    '--force-color-profile=srgb',
    '--metrics-recording-only',
    '--mute-audio'
];

(async () => {
  const cluster = await Cluster.launch({
    concurrency: Cluster.CONCURRENCY_PAGE,
    maxConcurrency: 2, // 降低并发数,规避网站限制
    puppeteerOptions: {
        headless: false,
        args: args,
        timeout: 300000, // 全局超时设为5分钟
        monitor: true
    },
    retryLimit: 2, // 失败任务重试次数
    retryDelay: 1000 // 重试间隔1秒
  });

  await cluster.task(async ({ page, data: url }) => {
    try {
        // 页面加载等待网络空闲,设置超时
        await page.goto(url, { 
            waitUntil: 'networkidle2', 
            timeout: 60000 
        });

        await page.waitForSelector('#Office', { timeout: 10000 });
        await page.select('#Office', 'PEKING');
        
        // 导航操作绑定等待事件与超时
        await Promise.all([
            page.waitForNavigation({ 
                waitUntil: 'networkidle2', 
                timeout: 30000 
            }), 
            page.click('input[type=submit][value=Next]', { timeout: 10000 })
        ]);

        await page.waitForSelector('#CalendarId', { timeout: 10000 });
        await page.select('#CalendarId', '20804258');
        
        await Promise.all([
            page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 }), 
            page.click('input[type=submit][value=Next]', { timeout: 10000 })
        ]);

        // 后续步骤统一设置合理超时
        await Promise.all([
            page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 }), 
            page.click('input[type=submit][value=Next]', { timeout: 10000 })
        ]);

        await Promise.all([
            page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 }), 
            page.click('input[type=submit][value=Next]', { timeout: 10000 })
        ]);

        await page.waitForSelector('input[type=radio]', { timeout: 10000 });
        await page.click('input[type=radio]', { timeout: 10000 });

        await Promise.all([
            page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 }), 
            page.click('input[type=submit][value=Next]', { timeout: 10000 })
        ]);

        await page.waitForSelector('#main > form', { timeout: 10000 });

        // 模拟真实用户输入速度
        await page.type('#Lastname', 'Lastname', { delay: 50 });
        await page.type('#Firstname', 'Firstname', { delay: 50 });
        await page.type('#DateOfBirth', '15.03.2016', { delay: 50 });
        await page.type('#TraveldocumentNumber', '9999999999', { delay: 50 });
        await page.select('#Sex', '2');
        await page.type('#Street', 'Street', { delay: 50 });
        await page.type('#Postcode', '25001', { delay: 50 });
        await page.type('#City', 'City', { delay: 50 });
        await page.select('#Country', '3');

        console.log(`成功处理页面: ${url}`);
    } catch (err) {
        console.error(`处理页面${url}出错:`, err.message);
        throw err; // 抛出错误触发集群重试
    }
  });

  cluster.on('taskerror', (err, data, willRetry) => {
    if (willRetry) {
      console.warn(`爬取${data}出错: ${err.message},将重试`);
    } else {
      console.error(`爬取${data}失败: ${err.message}`);
    }
  });

  cluster.queue('<some url>');
  cluster.queue('<some url>');
  // 更多页面

  await cluster.idle();
  await cluster.close();
})();

额外注意事项

  • 修正了原代码中Firstname字段填写Lastname的错误。
  • 所有等待操作都设置了明确的超时时间,避免任务无限挂起。
  • 加入重试机制,提升任务执行的稳定性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:44:51