使用Puppeteer逐个爬取多URL并定时重复时的两类错误排查求助
嘿,我来帮你搞定这两个问题,咱们逐个拆解:
问题1:循环内函数引用外部变量的警告
这个警告是ESLint这类代码检查工具抛出的,核心原因是你在for循环内部重复定义了page.evaluate()里的箭头函数——虽然当前这个函数没直接引用循环变量,但循环里反复声明函数确实容易引发闭包语义混淆(比如后续如果不小心在函数里用到url这类外部变量,就可能出现意料之外的问题)。
修复方案:
把page.evaluate()里的数据提取逻辑抽成一个独立函数,放在循环外面,既避免了循环内重复声明函数,代码也更清晰:
// 抽离数据提取函数,放在循环外 const extractVideoData = () => { return [ JSON.stringify(document.querySelector('#text > a')?.innerText || ''), JSON.stringify(document.querySelector('#container > h1')?.innerText || ''), JSON.stringify(document.querySelector('.view-count')?.innerText || ''), JSON.stringify(document.querySelector('#owner-sub-count')?.innerText || '') ]; };
之后在循环里直接调用这个函数即可:
const data = await page.evaluate(extractVideoData);
另外我还加了?.可选链操作符和空值兜底,避免因为元素找不到导致爬取中断。
这个错误的常见原因有几个,咱们针对性解决:
- 重复等待导航:你先调用
page.goto(url),又紧接着调用page.waitForNavigation()——这会导致代码等待两次导航,很容易超时。其实page.goto()本身支持通过waitUntil参数指定等待条件,不需要额外调用waitForNavigation。 - 反爬机制:YouTube会检测无头浏览器,可能限制页面加载,咱们可以设置模拟真实浏览器的User-Agent,降低被识别的概率。
- 超时时间不足:部分YouTube页面加载较慢,默认30秒超时不够用,可以延长超时时间。
- 单个URL错误影响全局:之前的代码如果某一个URL超时,会直接终止整个爬取任务,需要给单个URL添加独立的错误捕获。
修复方案:
- 移除多余的
page.waitForNavigation(),把等待条件整合到page.goto中; - 设置自定义User-Agent,模拟真实浏览器;
- 延长
page.goto和waitForSelector的超时时间; - 给单个URL添加错误捕获,确保一个页面报错不影响其他URL的爬取。
完整修复后的代码
const puppeteer = require("puppeteer"); const urls = [ 'https://www.youtube.com/watch?v=cw9FIeHbdB8', 'https://www.youtube.com/watch?v=imy1px59abE', 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' ]; // 抽离数据提取函数,避免循环内声明函数的警告 const extractVideoData = () => { return [ JSON.stringify(document.querySelector('#text > a')?.innerText || ''), JSON.stringify(document.querySelector('#container > h1')?.innerText || ''), JSON.stringify(document.querySelector('.view-count')?.innerText || ''), JSON.stringify(document.querySelector('#owner-sub-count')?.innerText || '') ]; }; const scrape = async() => { let browser, page; try { browser = await puppeteer.launch({ headless: "new", // 使用新版无头模式,更接近真实浏览器 args: ['--no-sandbox', '--disable-setuid-sandbox'] // 避免沙箱权限问题(根据运行环境调整) }); page = await browser.newPage(); // 设置模拟真实浏览器的User-Agent,规避反爬检测 await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'); // 使用for...of循环,比传统for循环更简洁,也能避免闭包潜在问题 for (const url of urls) { try { // 整合导航等待逻辑,延长超时到60秒 await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 }); // 等待目标元素可见,超时设为60秒 await page.waitForSelector('.view-count', { visible: true, timeout: 60000 }); const data = await page.evaluate(extractVideoData); const [channel, title, views, subs] = data.map(item => JSON.parse(item)); console.log({ channel, title, views, subs }); } catch (urlErr) { // 单个URL报错不终止全局任务,打印错误后继续爬取下一个 console.error(`爬取${url}失败:`, urlErr.message); } } } catch(err) { console.error('全局爬取错误:', err.message); } finally { if (browser) { await browser.close(); } // 注意:setTimeout不需要await,它是异步非Promise API setTimeout(scrape, 60000); // 一轮爬取完成后1分钟启动下一轮 } }; scrape();
额外小提示
- 新版Puppeteer推荐使用
headless: "new",比旧版headless: true更接近真实浏览器,能降低被反爬的概率; - 如果还是遇到反爬限制,可以尝试添加更多模拟设置,比如设置viewport尺寸、启用Cookie等(Puppeteer默认已启用大部分常用设置);
- 如果你需要更稳定的爬取,可以考虑给每个URL的爬取添加随机延迟,避免请求频率过高触发限制。
内容的提问来源于stack exchange,提问作者user16703372
相关产品推荐
相关产品推荐

