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

如何在点击加载更多后等待网站新组件加载完成(Puppeteer)

解决思路与修改方案

你的核心问题是同步循环点击加载按钮时,没有等待每次点击后的内容加载完成,直接触发后续抓取,导致数据不全。DOMContentLoaded和window.onload只在初始页面加载时生效,对后续动态加载的内容无效,确实需要等待每次点击后的API请求完成或DOM更新。

关键问题分析

  1. 原代码中val()是同步循环点击,浏览器根本来不及处理每次点击后的加载请求,就已经执行完所有点击,此时大部分内容还没加载。
  2. page.evaluate里的函数没有异步等待逻辑,无法感知动态加载的完成状态。
  3. 硬编码setTimeout等待10分钟完全不可靠,既浪费时间又可能在网络慢时仍未加载完成。

修改后的代码

const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--window-size=1600,1200',
    ],
    defaultViewport: null,
  });
  const page = await browser.newPage();
  await page.goto(
    'https://www.gelbeseiten.de/suche/friseur/bundesweit?umkreis=4000',
    { waitUntil: 'networkidle2' }
  );

  // 异步点击加载更多,直到按钮消失或无法点击
  async function loadAllContent() {
    while (true) {
      try {
        // 等待加载按钮出现,最多等5秒
        await page.waitForSelector('#mod-LoadMore--button', { timeout: 5000 });
        // 点击按钮,同时等待网络空闲(等待API请求完成)+ DOM更新
        const prevItemCount = await page.evaluate(() => 
          document.querySelectorAll('.mod-AdresseKompakt__phoneNumber').length
        );
        await Promise.all([
          page.click('#mod-LoadMore--button'),
          page.waitForNetworkIdle({ idleTime: 1000 }), // 等待1秒无网络请求
          // 等待列表项数量增加,确保新内容已渲染
          page.waitForFunction(
            prevCount => document.querySelectorAll('.mod-AdresseKompakt__phoneNumber').length > prevCount,
            {},
            prevItemCount
          )
        ]);
      } catch (err) {
        // 按钮消失或超时,说明加载完成
        break;
      }
    }
  }

  // 抓取数据
  async function getData() {
    return await page.evaluate(() => {
      const liTags = document.querySelectorAll('.mod-AdresseKompakt__phoneNumber');
      return Array.from(liTags).map(tag => tag.innerText.trim());
    });
  }

  // 执行流程:先加载所有内容,再抓取数据
  await loadAllContent();
  const phoneNumbers = await getData();

  // 写入CSV
  const writeStream = fs.createWriteStream('data.csv');
  writeStream.write(`id, phone number\n`);
  phoneNumbers.forEach((number, index) => {
    writeStream.write(`${index},${number}\n`);
  });
  writeStream.end();

  await browser.close();
})();

核心改进点

  • 异步循环加载:用while(true)循环替代同步for循环,每次点击后等待三个条件:
    1. 网络空闲waitForNetworkIdle:确保加载新内容的API请求完成。
    2. 列表项数量增加:确保新内容已经渲染到DOM中。
    3. 等待按钮存在:如果按钮消失,说明没有更多内容可加载。
  • 分离加载与抓取逻辑:把加载所有内容和抓取数据分成两个独立函数,逻辑更清晰。
  • 移除硬编码超时:不再依赖setTimeout,而是根据实际加载状态判断是否完成。

额外提示

  • 如果网站有反爬机制,可适当增加点击间隔(比如await page.waitForTimeout(500)),避免被封。
  • waitForNetworkIdle的idleTime可根据网站加载速度调整,确保所有请求都已完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 05:37:44