【问题标题】:Puppeteer - scroll down until you can't anymorePuppeteer - 向下滚动,直到你不能再
【发布时间】:2019-01-02 21:20:01
【问题描述】:

我处于向下滚动时创建新内容的情况。新内容具有特定的类名。

如何继续向下滚动直到所有元素都加载完毕?

换句话说,我想达到一个阶段,如果我继续向下滚动,就不会加载任何新内容。

我是用代码向下滚动,加上一个

await page.waitForSelector('.class_name');

这种方法的问题是,在所有元素加载后,代码一直向下滚动,没有创建新元素,最终我得到一个超时错误。

这是代码:

await page.evaluate( () => {
  window.scrollBy(0, window.innerHeight);
});
await page.waitForSelector('.class_name');

【问题讨论】:

  • 听起来您用于向下滚动的代码可能存在问题。您能否将其添加到您的问题中?
  • if i keep scrolling down, nothing new will load 定义“不会加载任何新内容”并在您的代码中检查。也可以重新定义超时。但是,是的,Grant Miller 是对的,请提供您的代码,最好是目标站点 URL。
  • 非常感谢!我更新了代码。因为它是一个本地站点,所以我无法发布 URL……“没有新内容将加载”意味着该网站已加载所有可用元素,因此,当我继续向下滚动并使用 page.waitForSelector() 时,没有新内容元素会出现,我的代码会无限期地等待,直到它抛出一个超时错误。
  • 你可以试试这个await page.evaluate('window.scrollTo(0, document.body.scrollHeight)')

标签: javascript node.js puppeteer


【解决方案1】:

试一试:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: false
    });
    const page = await browser.newPage();
    await page.goto('https://www.yoursite.com');
    await page.setViewport({
        width: 1200,
        height: 800
    });

    await autoScroll(page);

    await page.screenshot({
        path: 'yoursite.png',
        fullPage: true
    });

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

async function autoScroll(page){
    await page.evaluate(async () => {
        await new Promise((resolve, reject) => {
            var totalHeight = 0;
            var distance = 100;
            var timer = setInterval(() => {
                var scrollHeight = document.body.scrollHeight;
                window.scrollBy(0, distance);
                totalHeight += distance;

                if(totalHeight >= scrollHeight){
                    clearInterval(timer);
                    resolve();
                }
            }, 100);
        });
    });
}

来源:https://github.com/chenxiaochun/blog/issues/38

【讨论】:

  • 100);太快了,它只会跳过整个自动滚动,我不得不使用 400 ......无论如何要检测一个类,元素在停止自动滚动之前出现?
  • 当您evaluateing 时,您可以参考文档上下文。因此,您只需使用标准选择器,并使用 getBoundingClientRect 检查它的位置。
  • @CodeGuru 可以使用类名停止自动滚动,但您需要使用scrollIntoView 而不是scrollBy,这意味着您需要对滚动到的元素的引用,这可能会产生更多页面底部的内容。然后,您可以比较滚动到视图之前与滚动到视图之后的类名数。如果滚动到视图后类名的数量增加,则生成了更多内容,因此您可以滚动更多。否则,不再生成内容,因此停止滚动。希望这是有道理的。
  • lqbal:可能和你的xvfb有关。尝试将headless: false 更改为headless: true
  • @JannisIoannou:要在您的 puppeteer 实例上执行 JavaScript 代码,您可以使用 evaluate 方法。想想在评估中运行的代码,就好像你在浏览器控制台中运行它一样。在这种情况下,window 在调用评估时自动创建。请查看evaluate 方法以获取更多上下文。
【解决方案2】:

向下滚动到页面底部有两种方式:

  1. 使用scrollIntoView(滚动到底部可以创建更多内容的页面部分)和选择器(即document.querySelectorAll('.class_name').length检查是否生成了更多内容)
  2. 使用scrollBy(逐步向下滚动页面)和setTimeoutsetInterval(逐步检查我们是否位于页面底部)

这是一个使用scrollIntoView 和选择器(假设.class_name 是我们滚动到更多内容的选择器)的实现,我们可以在浏览器中运行:

方法一:使用scrollIntoView和选择器

const delay = 3000;
const wait = (ms) => new Promise(res => setTimeout(res, ms));
const count = async () => document.querySelectorAll('.class_name').length;
const scrollDown = async () => {
  document.querySelector('.class_name:last-child')
    .scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
}

let preCount = 0;
let postCount = 0;
do {
  preCount = await count();
  await scrollDown();
  await wait(delay);
  postCount = await count();
} while (postCount > preCount);
await wait(delay);

在这种方法中,我们比较滚动前 (preCount) 和滚动后 (postCount) 的 .class_name 选择器的数量,以检查我们是否位于页面底部:

if (postCount > precount) {
  // NOT bottom of page
} else {
  // bottom of page
}

这里有 2 种可能的实现,使用 setTimeoutsetIntervalscrollBy 在纯 JavaScript 中我们可以在浏览器控制台中运行:

方法 2a:使用 setTimeout 和 scrollBy

const distance = 100;
const delay = 100;
while (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
  document.scrollingElement.scrollBy(0, distance);
  await new Promise(resolve => { setTimeout(resolve, delay); });
}

方法 2b:使用 setInterval 和 scrollBy

const distance = 100;
const delay = 100;
const timer = setInterval(() => {
  document.scrollingElement.scrollBy(0, distance);
  if (document.scrollingElement.scrollTop + window.innerHeight >= document.scrollingElement.scrollHeight) {
    clearInterval(timer);
  }
}, delay);

在这个方法中,我们比较document.scrollingElement.scrollTop + window.innerHeightdocument.scrollingElement.scrollHeight来检查我们是否在页面底部:

if (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
  // NOT bottom of page
} else {
  // bottom of page
}

如果上面的任何一个 JavaScript 代码将页面一直滚动到底部,那么我们知道它正在工作,我们可以使用 Puppeteer 自动执行此操作。

这里是示例 Puppeteer Node.js 脚本,它会向下滚动到页面底部并等待几秒钟,然后关闭浏览器。

Puppeteer 方法一:使用 scrollIntoView 和选择器 (.class_name)

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
    args: ['--window-size=800,600']
  });
  const page = await browser.newPage();
  await page.goto('https://example.com');

  const delay = 3000;
  let preCount = 0;
  let postCount = 0;
  do {
    preCount = await getCount(page);
    await scrollDown(page);
    await page.waitFor(delay);
    postCount = await getCount(page);
  } while (postCount > preCount);
  await page.waitFor(delay);

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

async function getCount(page) {
  return await page.$$eval('.class_name', a => a.length);
}

async function scrollDown(page) {
  await page.$eval('.class_name:last-child', e => {
    e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
  });
}

Puppeteer 方法 2a:使用 setTimeout 和 scrollBy

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
    args: ['--window-size=800,600']
  });
  const page = await browser.newPage();
  await page.goto('https://example.com');

  await scrollToBottom(page);
  await page.waitFor(3000);

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

async function scrollToBottom(page) {
  const distance = 100; // should be less than or equal to window.innerHeight
  const delay = 100;
  while (await page.evaluate(() => document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight)) {
    await page.evaluate((y) => { document.scrollingElement.scrollBy(0, y); }, distance);
    await page.waitFor(delay);
  }
}

Puppeteer 方法 2b:使用 setInterval 和 scrollBy

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
    args: ['--window-size=800,600']
  });
  const page = await browser.newPage();
  await page.goto('https://example.com');

  await page.evaluate(scrollToBottom);
  await page.waitFor(3000);

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

async function scrollToBottom() {
  await new Promise(resolve => {
    const distance = 100; // should be less than or equal to window.innerHeight
    const delay = 100;
    const timer = setInterval(() => {
      document.scrollingElement.scrollBy(0, distance);
      if (document.scrollingElement.scrollTop + window.innerHeight >= document.scrollingElement.scrollHeight) {
        clearInterval(timer);
        resolve();
      }
    }, delay);
  });
}

【讨论】:

    【解决方案3】:

    这里的许多解决方案都假定页面高度是恒定的。即使页面高度发生变化(例如,在用户向下滚动时加载新内容),此实现仍然有效。

    await page.evaluate(() => new Promise((resolve) => {
      var scrollTop = -1;
      const interval = setInterval(() => {
        window.scrollBy(0, 100);
        if(document.documentElement.scrollTop !== scrollTop) {
          scrollTop = document.documentElement.scrollTop;
          return;
        }
        clearInterval(interval);
        resolve();
      }, 10);
    }));
    

    【讨论】:

    • 对于有高度变化的页面,这个函数解析更快...
    【解决方案4】:

    基于此url的回答

    await page.evaluate(() => {
      window.scrollBy(0, window.innerHeight);
    });
    

    【讨论】:

    • window.innerHeight 不会一直滚动到底部,但 window.scrollTo(0,window.document.body.scrollHeight) 可以。
    【解决方案5】:

    容易多了:

        await page.evaluate(async () => {
          let scrollPosition = 0
          let documentHeight = document.body.scrollHeight
    
          while (documentHeight > scrollPosition) {
            window.scrollBy(0, documentHeight)
            await new Promise(resolve => {
              setTimeout(resolve, 1000)
            })
            scrollPosition = documentHeight
            documentHeight = document.body.scrollHeight
          }
        })
    

    【讨论】:

      【解决方案6】:

      您可能只使用page.keyboard 对象的以下代码:

      await page.keyboard.press('ArrowDown');
      delay(2000) //wait for 2 seconds
      await page.keyboard.press('ArrowUp');
      function delay(milliseconds) { //function for waiting
              return new Promise(resolve => {
                setTimeout(() => {
                  resolve();
                }, milliseconds);
              });
            }
      

      【讨论】:

      • 只有当我们有这些向上和向下按钮时。
      【解决方案7】:

      相当简单的解决方案

      let lastHeight = await page.evaluate('document.body.scrollHeight');
      
          while (true) {
              await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
              await page.waitForTimeout(2000); // sleep a bit
              let newHeight = await page.evaluate('document.body.scrollHeight');
              if (newHeight === lastHeight) {
                  break;
              }
              lastHeight = newHeight;
          }
      

      【讨论】:

        【解决方案8】:

        与@EdvinTr 类似的解决方案,它给了我很好的结果。 滚动并与页面的 Y Offset 进行比较,非常简单。

        let originalOffset = 0;
        while (true) {
            await page.evaluate('window.scrollBy(0, document.body.scrollHeight)');
            await page.waitForTimeout(200);
            let newOffset = await page.evaluate('window.pageYOffset');
            if (originalOffset === newOffset) {
                break;
            }
            originalOffset = newOffset;
        }
        

        【讨论】:

          猜你喜欢
          • 2023-03-04
          • 2020-06-04
          • 2022-11-16
          • 1970-01-01
          • 2021-09-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-20
          相关资源
          最近更新 更多