Puppeteer - 向下滚动直到你不能再滚动

IT技术 javascript node.js puppeteer
2021-02-08 00:19:56

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

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

换句话说,我想达到这样的阶段:如果我继续向下滚动,则不会加载任何新内容。

我正在使用代码向下滚动,再加上

await page.waitForSelector('.class_name');

这种方法的问题在于,在所有元素都加载后,代码继续向下滚动,没有创建新元素,最终出现超时错误。

这是代码:

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

试一试:

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 ......无论如何要检测一个类,在停止自动滚动之前出现的元素?
2021-03-18 00:19:56
@JannisIoannou:要在您的 puppeteer 实例上执行 JavaScript 代码,您可以使用评估方法。将在内部运行的代码视为在浏览器控制台中运行它。在这种情况下,window会在调用评估时自动创建。请查看评估方法以获取其他上下文。
2021-03-18 00:19:56
lqbal:可能和你的xvfb有关。尝试更改headless: falseheadless: true
2021-03-22 00:19:56
@CodeGuru 可以使用 classname 停止自动滚动,但您需要使用scrollIntoView而不是scrollBy,这意味着您需要对要滚动到的元素的引用,这可能会在页面底部生成更多内容。然后,您可以在滚动到视图之前与滚动到视图之后比较类名的数量。如果滚动到视图中后类名的数量增加,则会生成更多内容,因此您可以滚动更多内容。否则,不再生成更多内容,因此停止滚动。希望这是有道理的。
2021-04-05 00:19:56
当你在evaluateing 时,你有一个对文档上下文的引用。因此,您只需使用标准选择器,然后使用getBoundingClientRect.
2021-04-07 00:19:56

向下滚动到页面底部可以通过两种方式完成:

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

这是一个在纯 JavaScript 中使用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);

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

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

这里有两种可能的实现,使用setTimeoutsetInterval使用scrollBy纯 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 方法 1:使用带有选择器 ( .class_name) 的scrollIntoView

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);
  });
}

这里的许多解决方案都假设页面高度是恒定的。即使页面高度发生变化(例如,当用户向下滚动时加载新内容),该实现也能工作。

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);
}));
对于有高度变化的页面,这个功能解决得更快...
2021-04-13 00:19:56

基于这个网址的答案

await page.evaluate(() => {
  window.scrollBy(0, window.innerHeight);
});
window.innerHeight不会一直滚动到底部,但会滚动到底部window.scrollTo(0,window.document.body.scrollHeight)
2021-03-19 00:19:56

容易得多:

    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
      }
    })