求助:Puppeteer脚本中随机触发Cannot read properties of undefined (reading '$eval')错误的排查方案
首先,咱们先定位错误的核心:你看到的TypeError本质是firstNews变量是undefined——也就是allNews[0]取不到值,哪怕HTML里确实有article.news元素,也说明在执行$$("article.news")的时候,页面里还没有这个元素(或者元素还没被Puppeteer正确捕获)。结合你的代码和描述,我整理了几个调试和修复的方向:
1. 先修正代码里的变量作用域问题(大概率是根源)
看你的代码,在while循环里,你先遍历所有tab执行reload,但之后的try块直接用了tab变量——这个tab并没有在当前while循环块里定义!它其实是之前for (let tab of tabs)循环的最后一个tab的引用,这会导致你每次只处理最后一个tab,而且如果那个tab的页面状态不稳定,就会偶发错误。
你需要在reload之后,重新遍历每个tab来处理内容:
while (true) { //Refresh all tabs var promises = []; for (let tab of tabs ) { // 重载时等待网络空闲,确保页面加载更彻底 promises.push(tab.reload({waitUntil: 'networkidle2'})); } await Promise.all(promises); // 遍历每个tab处理内容,而不是用未定义的tab变量 for (let tab of tabs) { try{ let allNews = ''; let firstNews = ''; let pseudo = ''; // 先等待元素可见,而不是只等存在 await tab.waitForSelector("article.news", {visible: true, timeout: 10000}); //Get the first news on the page allNews = await tab.$$("article.news"); console.log(`Tab ${tab.id} found ${allNews.length} news elements`); // 调试日志,看元素数量 if (!allNews.length) { console.log(`Tab ${tab.id} has no news elements after waiting`); await tab.screenshot({path: `tab-${tab.id}-no-news.png`}); continue; } firstNews = allNews[0]; // 分步获取子元素,避免链式调用隐藏错误 const pseudoElement = await firstNews.$('.user.userInfo span'); if (!pseudoElement) { console.log(`Tab ${tab.id}: Pseudo element not found in first news`); await tab.screenshot({path: `tab-${tab.id}-no-pseudo.png`}); continue; } pseudo = await pseudoElement.evaluate(s => s.textContent.trim()); console.log(new Date(), 'Pseudo : ', pseudo, '...'); //Log HTML content for debug console.log('html when all ok : '); let bodyHTML = await tab.evaluate(() => document.body.innerHTML); console.log(bodyHTML); await tab.screenshot({path: `tab-${tab.id}-ok.png`}); }catch (err){ console.log(`Error in tab ${tab.id} with URL: ${await tab.url()}`); // 打印出错的tab信息 console.log('html when not ok : '); let bodyHTML = await tab.evaluate(() => document.body.innerHTML); console.log(bodyHTML); await tab.screenshot({path: `tab-${tab.id}-ko.png`}); console.log(err); } } ... }
2. 优化元素等待逻辑
你之前用waitForSelector效果不好,可能是因为默认只等待元素存在于DOM,但元素可能还没完全渲染或可见。加上{visible: true}选项,确保元素不仅存在,而且是可见的,这样能避免元素短暂出现又消失的情况。
另外,把waitForSelector放在$$调用之前,确保执行元素查询时,目标元素已经稳定存在。
3. 分步调试,定位具体失败环节
不要直接链式调用firstNews.$eval(...),而是分步获取元素:先获取firstNews,检查是否存在;再获取子元素.user.userInfo span,检查是否存在。这样你能明确知道是哪个元素找不到,而不是只看到一个笼统的错误。
同时,添加调试日志:打印每个tab的ID、URL,以及allNews的长度,这样你能精准定位是哪个tab、什么情况下出现问题。
4. 重载时等待更彻底的页面状态
默认的tab.reload()只会等待domcontentloaded,但很多现代页面依赖JS动态渲染内容,网络请求可能还在进行。改用tab.reload({waitUntil: 'networkidle2'}),等待页面只有2个以下的网络请求时再继续,确保页面内容完全渲染完成。
5. 处理可能的重试逻辑
如果偶发元素获取失败,可以在捕获错误时添加重试机制,比如:
catch (err) { console.log(`Retrying tab ${tab.id} after error...`); await tab.reload({waitUntil: 'networkidle2'}); await tab.waitForSelector("article.news", {visible: true, timeout: 10000}); // 重新执行获取逻辑,或者跳过当前循环 }
按照这些步骤调整代码后,你应该能定位到具体的问题点,比如是某个特定tab的页面加载异常,还是元素渲染的时机问题。
内容的提问来源于stack exchange,提问作者user2178964

