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); } })();
关键改动说明
- 提前提取数据:用
page.evaluate一次性把所有符合条件的卡片数据(标题、描述、链接等)提取到内存数组,彻底摆脱对元素句柄的依赖 - 直接链接跳转:不再点击旧元素句柄,而是用提前提取的链接跳转详情页,避免上下文失效
- 稳定导航:所有页面跳转都加上
waitUntil: 'networkidle2',确保页面资源加载完成后再进行下一步操作 - 返回主页优化:直接重新访问主页URL,而不是依赖
goBack(),避免历史记录异常导致的页面状态问题
内容的提问来源于stack exchange,提问作者mhd abs
相关产品推荐
相关产品推荐

