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

Puppeteer爬取新闻文章时无法稳定获取全部页面数据的问题求助

Puppeteer爬取新闻文章时无法稳定获取全部页面数据的问题求助

看起来你遇到的是Puppeteer爬取时常见的不稳定加载问题,这种情况一般和页面渲染时机、元素句柄有效性、错误处理逻辑有关,我来帮你拆解问题并给出修复方案:

问题分析

你的代码核心问题集中在这几个方面:

  1. 分页循环依赖动态变化的元素句柄数组,容易因为DOM更新导致循环提前终止
  2. 分页点击后的等待逻辑不够严谨,可能页面还未完全渲染就开始提取数据
  3. 新页面爬取的错误处理有漏洞,可能导致页面未关闭、数据丢失
  4. 元素提取前未确保元素完全加载,导致获取到的链接数量不足

修复方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 03:18:13