Puppeteer爬取新闻文章时无法稳定获取全部页面数据的问题求助
Puppeteer爬取新闻文章时无法稳定获取全部页面数据的问题求助
看起来你遇到的是Puppeteer爬取时常见的不稳定加载问题,这种情况一般和页面渲染时机、元素句柄有效性、错误处理逻辑有关,我来帮你拆解问题并给出修复方案:
问题分析
你的代码核心问题集中在这几个方面:
- 分页循环依赖动态变化的元素句柄数组,容易因为DOM更新导致循环提前终止
- 分页点击后的等待逻辑不够严谨,可能页面还未完全渲染就开始提取数据
- 新页面爬取的错误处理有漏洞,可能导致页面未关闭、数据丢失
- 元素提取前未确保元素完全加载,导致获取到的链接数量不足
修复方案
1. 改用固定页数循环,避免依赖元素句柄
先获取总页数,再按页数循环,这样不管DOM怎么变化,循环次数都是固定的:
// 先获取总页数,替代直接拿元素句柄数组 const totalPages = await page.$$eval("div.gsc-cursor-page", els => els.length); const result: Result[] = []; for(let i = 0; i < totalPages; i++){ // 获取当前页的分页按钮元素(每次循环重新获取,避免句柄失效) const pageElements = await page.$$("div.gsc-cursor-page"); const pageElement = pageElements[i]; if(i !== 0){ await page.evaluate((el) => el.click(), pageElement); // 同时等待页面导航完成和结果区域加载,确保页面切换完成 await Promise.all([ page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 90000 }), page.waitForSelector("div.gsc-resultsbox-visible", { timeout: 90000 }) ]); } // ... 后续提取链接的逻辑 }
2. 优化新页面的错误处理与资源释放
确保不管爬取成功还是失败,都关闭新页面,避免浏览器资源耗尽:
for (const element of elements) { const link = await page.evaluate((el: HTMLAnchorElement) => el.href, element); const articlePage = await browser.newPage(); try { await articlePage.goto(link, { waitUntil: 'networkidle2', timeout: 90000 }); // 等待标题元素,失败则直接进入catch await articlePage.waitForSelector("h1.title", { timeout: 90000 }); const title = await articlePage.$eval("h1.title", (element) => element.textContent.trim()); const body = await articlePage.$$eval("div.articlebodycontent p", (elements) => elements.map((p) => p.textContent.replace(/\n/g, " ").replace(/\s+/g, " ")) ); result.push({ title, content: body.join(" ") }); } catch (error) { console.log(`提取链接 ${link} 时出错:`, error); } finally { // 无论成功失败,都关闭页面释放资源 await articlePage.close(); } }
3. 确保元素完全加载后再提取
在获取文章链接前,先等待链接元素加载完成:
// 先等待链接元素渲染完成 await page.waitForSelector("div.gsc-resultsbox-visible > div > div div > div.gsc-thumbnail-inside > div > a", { timeout: 90000 }); const elements: ElementHandle<HTMLAnchorElement>[] = await page.$$("div.gsc-resultsbox-visible > div > div div > div.gsc-thumbnail-inside > div > a");
完整修改后的代码片段
await page.goto(url, { timeout: 90000, waitUntil: 'networkidle2' }); const result: Result[] = []; // 获取总页数 const totalPages = await page.$$eval("div.gsc-cursor-page", els => els.length); await page.waitForSelector('div.gsc-cursor-page', { timeout: 90000 }); await page.waitForSelector("div.gsc-resultsbox-visible", { timeout: 90000 }); for(let i = 0; i < totalPages; i++){ // 每次循环重新获取分页按钮,避免句柄失效 const pageElements = await page.$$("div.gsc-cursor-page"); const pageElement = pageElements[i]; if(i !== 0){ await page.evaluate((el) => el.click(), pageElement); // 并行等待导航和结果区域加载 await Promise.all([ page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 90000 }), page.waitForSelector("div.gsc-resultsbox-visible", { timeout: 90000 }) ]); } // 等待链接元素加载完成 await page.waitForSelector("div.gsc-resultsbox-visible > div > div div > div.gsc-thumbnail-inside > div > a", { timeout: 90000 }); const elements: ElementHandle<HTMLAnchorElement>[] = await page.$$("div.gsc-resultsbox-visible > div > div div > div.gsc-thumbnail-inside > div > a"); for (const element of elements) { try { const link = await page.evaluate((el: HTMLAnchorElement) => el.href, element); const articlePage = await browser.newPage(); try { await articlePage.goto(link, { waitUntil: 'networkidle2', timeout: 90000 }); await articlePage.waitForSelector("h1.title", { timeout: 90000 }); const title = await articlePage.$eval("h1.title", (element) => element.textContent.trim()); const body = await articlePage.$$eval("div.articlebodycontent p", (elements) => elements.map((p) => p.textContent.replace(/\n/g, " ").replace(/\s+/g, " ")) ); result.push({ title, content: body.join(" ") }); } catch (error) { console.log(`提取链接 ${link} 时出错:`, error); } finally { await articlePage.close(); } } catch (error) { console.log("获取链接时出错:", error); } } } return { searchQuery, length: result.length, result };
额外建议
- 优先用
waitForNavigation和waitForSelector确保页面加载,必要时可以加500-1000ms的微小延迟兜底 - 如果网站有反爬机制,可以模拟人类行为,比如随机滚动页面、调整点击间隔
- 增加日志记录每一页爬取的链接数量,方便快速定位哪一页出现异常
备注:内容来源于stack exchange,提问作者Preethi
相关产品推荐
相关产品推荐

