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

求助:Puppeteer脚本中随机触发Cannot read properties of undefined (reading '$eval')错误的排查方案

偶发TypeError: Cannot read properties of undefined (reading '$eval') in Puppeteer Script

首先,咱们先定位错误的核心:你看到的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 15:57:31