Node、Puppeteer、Cheerio爬取动态页面时每页结果重复问题
问题分析与修复方案
你的代码核心问题是静态DOM复用,加上几处异步逻辑错误,导致每次循环都读取第一页的数据:
核心错误:复用了初始页面的cheerio DOM
你在循环外通过page.evaluate获取了第一次页面的HTML,并用cheerio加载成$,之后每次循环都用这个静态的$查询元素——它不会同步页面后续的动态更新,所以不管页面怎么变化,你读取的始终是第一页的内容。
其他问题
- cheerio的
each方法不支持async/await,你写的async回调不会被等待,会导致还没读完当前页数据就跳转到下一页。 Promise.all里的await page.waitForTimeout(5000)写法错误,会先阻塞5秒再执行点击,失去了并行等待的作用。- 固定等待5秒不可靠,应该等待页面内容实际更新,而不是硬等时间。
修复后的代码
try { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto(url, { waitUntil: "networkidle0" }).catch(err => console.log("error loading url", err)); // 这里假设你已正确获取总页数pageCount,比如从分页栏提取 // 示例:const pageCount = await page.evaluate(() => { /* 提取总页数的逻辑 */ }); for (let i = 0; i < pageCount; i++) { // 等待当前页商品列表加载完成 await page.waitForSelector('.itemDivWrapper > div'); // 直接在浏览器上下文采集最新页面数据 const itemNames = await page.evaluate(() => { const items = Array.from(document.querySelectorAll('.itemDivWrapper > div')); return items.map(item => { const h3 = item.querySelector('a > div > h3'); return h3 ? h3.textContent.trim() : ''; }); }); // 输出当前页商品名 itemNames.forEach(name => console.log(name)); // 最后一页无需点击下一页 if (i < pageCount - 1) { await Promise.all([ page.click('div.itemList > nav > ul > li.nextButton > a'), // 根据页面加载方式选择等待逻辑: // 若点击触发页面跳转,用waitForNavigation page.waitForNavigation({ waitUntil: "networkidle0" }), // 若为AJAX无刷新加载,改用等待商品列表更新 // page.waitForSelector('.itemDivWrapper > div', { timeout: 10000 }) ]); } } await browser.close(); } catch (error) { console.log(error); }
关键修改说明
- 直接在页面上下文采集数据:用
page.evaluate在浏览器里直接查询当前最新的DOM,代替cheerio的静态解析,确保每次循环读取的是当前页面的内容。 - 正确处理异步逻辑:通过
await等待数据采集完成后,再执行下一页操作,避免逻辑混乱。 - 可靠的页面更新等待:根据页面加载方式,选择
waitForNavigation(页面跳转场景)或waitForSelector(AJAX无刷新场景)代替固定等待,确保页面内容更新后再继续。 - 避免多余操作:循环到最后一页时,不再执行下一页点击。
内容的提问来源于stack exchange,提问作者StichDisney
相关产品推荐
相关产品推荐

