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

Puppeteer报错:Cannot find context with specified id 求助

问题原因与修复方案

报错根源

你遇到的ProtocolError: Protocol error (DOM.describeNode): Cannot find context with specified id,本质是元素句柄失效导致的:

  • 初始通过page.$$获取的卡片元素句柄,绑定在第一次加载的主页DOM上下文里
  • 当你跳转到详情页、目标页再返回主页时,主页DOM已经重新渲染,旧元素句柄和当前页面上下文脱离,再操作就会触发找不到上下文的错误

修复方案

核心思路是提前提取所有必要数据到内存,避免持有失效的元素句柄,同时优化导航逻辑确保页面状态稳定。以下是修改后的完整代码:

const puppeteer = require('puppeteer');
const categoriesArray = require('./categories');

function formatCategoryString(str) {
  return str.toLowerCase().replace(/\s+/g, '-');
}

(async () => {
  try {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();

    await page.goto('https://www.discudemy.com/all', { waitUntil: 'networkidle2' });
    console.log('Scraping started');

    await page.waitForSelector('body > article section.card');

    // 提前提取所有符合条件的卡片数据到内存数组,不持有元素句柄
    const cardsData = await page.evaluate((categories) => {
      const formatCategory = (str) => str.toLowerCase().replace(/\s+/g, '-');
      const cards = Array.from(document.querySelectorAll('body > article section.card'));
      
      return cards.map(card => {
        const categoryEl = card.querySelector('.extra.content .right.floated.author a .catSpan');
        if (!categoryEl) return null;
        
        const category = categoryEl.innerText.trim();
        const formattedCategory = formatCategory(category);
        if (!categories.includes(formattedCategory)) return null;

        return {
          title: card.querySelector('.content .header a').innerText.trim(),
          description: card.querySelector('.content .description').innerText.trim(),
          imageURL: card.querySelector('.image img').src,
          price: card.querySelector('.meta span:last-child').innerText.trim(),
          category,
          titleLink: card.querySelector('.content .header a').href
        };
      }).filter(item => item !== null);
    }, categoriesArray);

    console.log(`Number of valid cards to process: ${cardsData.length}`);

    // 循环内存中的数据,直接用链接跳转,不依赖旧元素句柄
    for (const card of cardsData) {
      try {
        // 跳转到详情页
        await page.goto(card.titleLink, { waitUntil: 'networkidle2' });

        // 处理优惠券页面跳转
        const couponPageLink = await page.$('div a.ui.green.discBtn');
        if (couponPageLink) {
          await Promise.all([
            couponPageLink.click(),
            page.waitForNavigation({ waitUntil: 'networkidle2' })
          ]);
        }

        // 提取优惠券链接
        const couponLink = await page.$('.ui.segment a');
        const hrefValue = await page.evaluate(el => el.getAttribute('href'), couponLink);

        // 输出数据
        console.log('Title:', card.title);
        console.log('Description:', card.description);
        console.log('Image URL:', card.imageURL);
        console.log('Price:', card.price);
        console.log('Category:', card.category);
        console.log('Link: ', hrefValue);

        // 返回主页,确保页面重新加载完成
        await page.goto('https://www.discudemy.com/all', { waitUntil: 'networkidle2' });
        await page.waitForSelector('body > article section.card');
      } catch (error) {
        console.error('Error occurred while processing card:', error);
        continue;
      }
    }

    await browser.close();
  } catch (err) {
    console.error(err);
  }
})();

关键改动说明

  1. 提前提取数据:用page.evaluate一次性把所有符合条件的卡片数据(标题、描述、链接等)提取到内存数组,彻底摆脱对元素句柄的依赖
  2. 直接链接跳转:不再点击旧元素句柄,而是用提前提取的链接跳转详情页,避免上下文失效
  3. 稳定导航:所有页面跳转都加上waitUntil: 'networkidle2',确保页面资源加载完成后再进行下一步操作
  4. 返回主页优化:直接重新访问主页URL,而不是依赖goBack(),避免历史记录异常导致的页面状态问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 09:53:22