使用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
相关产品推荐
相关产品推荐

