【问题标题】:Log that bottom scroll has been reached记录已到达底部滚动条
【发布时间】:2023-01-09 10:59:18
【问题描述】:

我正在使用下面的代码一直滚动到 YouTube 页面的底部并且它有效。我的问题是,在网站向下滚动到底部后,我怎样才能在 console.log 中看到已到达底部?

笔记:该解决方案应该适用于 youtube.com。我已经尝试获取文档高度并将其与滚动高度进行比较,但这没有用!

const puppeteer = require('puppeteer');

let thumbArr = []
const scrapeInfiniteScrollItems = async(page) => {
  while (true) {
    const previousHeight = await page.evaluate(
      "document.querySelector('ytd-app').scrollHeight"
    );
    await page.evaluate(() => {
      const youtubeScrollHeight =
        document.querySelector("ytd-app").scrollHeight;
      window.scrollTo(0, youtubeScrollHeight);
    });
    await page.waitForFunction(
      `document.querySelector('ytd-app').scrollHeight > ${previousHeight}`, {
        timeout: 0
      }
    );

    const thumbnailLength = (await page.$$('ytd-grid-video-renderer')).length
    //this logs the amount of thumbnails every loop but once bottom scroll has        been reached it stops logging (obviously) but the question is how am I supposed to compare the last amount of thumbnail's found with total thumbnails once the loop has stopped running. Take a look below to better understand my question.
    thumbArr.push(thumbnailLength)

    if (thumbnailLength == thumbArr.at(-1)) {
      console.log('bottom has been reached')
    }

    await page.waitForTimeout(1000)
  }
};

(async() => {
  const browser = await puppeteer.launch({
    headless: false
  });
  const page = await browser.newPage();
  await page.goto('https://www.youtube.com', {
    waitUntil: 'networkidle2',
  });

  await scrapeInfiniteScrollItems(page)
})();

更新:

let clientHeightArr = []
let clientHeightArrTracker = []
const scrapeInfiniteScrollItems = async(browser, page) => {
  var infiniteScrollTrackerInterval = setInterval(async() => {
    clientHeightArrTracker.push(clientHeightArr.length)
    if (clientHeightArrTracker.some((e, i, arr) => arr.indexOf(e) !== i) == true) {
      clearInterval(infiniteScrollTrackerInterval)
      console.log('Bottom is reached')
      //causes error "ProtocolError: Protocol error (Runtime.callFunctionOn): Target closed."
      await browser.close()
    }
  }, 2000)
  while (true) {
    const previousHeight = await page.evaluate(
      "document.querySelector('ytd-app').scrollHeight"
    );

    await page.evaluate(() => {
      const youtubeScrollHeight =
        document.querySelector("ytd-app").scrollHeight;
      window.scrollTo(0, youtubeScrollHeight);
    });

    await page.waitForFunction(
      `document.querySelector('ytd-app').scrollHeight > ${previousHeight}`, {
        timeout: 0
      },
    );

    const clientHeight = await page.$$eval("ytd-app", el => el.map(x => x.clientHeight));
    clientHeightArr.push(clientHeight[0])
    await page.waitForTimeout(1000)
  }
};

(async() => {
  const browser = await puppeteer.launch({
    headless: false
  });
  const page = await browser.newPage();
  await page.goto('https://www.youtube.com/c/mkbhd/videos', {
    waitUntil: 'networkidle2',
  });

  await scrapeInfiniteScrollItems(browser, page)
})();

【问题讨论】:

  • 你说的支票在哪里做?它应该可以工作,也许有一个增量,以防万一差异很小。打印这两个值来调试它并调试为什么它没有检测到结束。您还可以计算迭代之间页面上视频元素缩略图(或其他)的数量,如果它停止更改,您就完成了。 await new Promise((resolve) => setTimeout(resolve, 1000)); 应该是 await page.waitForTimeout(1000) 虽然几乎总是有一个 page.waitForFunction 更精确(可能是卡片/缩略图再次计数)。
  • 顺便说一句,根据您要获取的数据,您可能根本不需要滚动,所以整个事情通常是一个 xy problem 如果您首先提供为什么需要滚动的上下文,则可以解决.通常,数据位于网络请求或静态 HTML 中,您可以毫不费力地获取它。
  • @ggorlen 这是我试图获取的数据 const title = await page.$$eval(".ytd-grid-video-renderer #video-title", el => el.map(x => x.getAttribute("title")));
  • 这是在哪个页面?
  • @ggorlen 这个例如https://www.youtube.com/c/mkbhd/videos

标签: javascript puppeteer


【解决方案1】:

这里有很多可能的方法。稍微弄乱之后,我武断地采用了以下策略,并认为这对初学者来说已经足够了:

const puppeteer = require("puppeteer"); // ^16.2.0

let browser;
(async () => {
  browser = await puppeteer.launch({headless: true});
  const [page] = await browser.pages();
  await page.setRequestInterception(true);
  page.on("request", req => {
    if (req.resourceType() === "image") {
      req.abort();
    }
    else {
      req.continue();
    }
  });
  const url = "https://www.youtube.com/c/mkbhd/videos";
  await page.goto(url, {waitUntil: "domcontentloaded"});
  await page.waitForSelector("#video-title");
  let thumbs = [];

  for (;;) {
    try {
      await page.waitForFunction(
        `${thumbs.length} !==
          document.querySelectorAll("#video-title").length`, 
        {timeout: 10000}
      );
    }
    catch (err) {
      break;
    }

    thumbs = await page.$$eval("#video-title", els => {
      els.at(-1).scrollIntoView();
      return els.map(e => e.getAttribute("title"));
    });
  }

  console.log("total length", thumbs.length);
  console.log("first 10 thumbs:", thumbs.slice(0, 10));
  console.log("last 10 thumbs:", thumbs.slice(-10));
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close())
;

输出:

total length 1460
first 10 thumbs: [
  'iPhone 14/Pro Impressions: Welcome to Dynamic Island!',
  'Google Pixel Buds Pro Review: Just Get These!',
  'The Hyundai IONIQ 5: I Get It Now!',
  'Android 13 Hands-On: Top 5 Features!',
  'Dope Tech: The Most Extreme Gaming Monitor!',
  'Samsung Z Fold 4/ Flip 4 Impressions + Watch 5 Pro!',
  'Best Back to School Tech 2022!',
  'OnePlus 10T Impressions: Somebody That You Used to Know',
  'Asus Zenfone 9: The New Compact King!',
  "The iPad's Odd New Feature"
]
last 10 thumbs: [
  'HQ Tutorial: Rocket Dock Application',
  'Camstudio Clarity',
  'Tutorial: Camstudio HQ',
  'HQ Tutorial: Ccleaner',
  '15 Year old Golf Swing Analysis',
  'Fraps HD Test in 1080p (18 WOS)',
  'HP Pavilion dv7t Media Center Remote Overview',
  'High fps LG Voyager footage',
  '14 Year knock-down shot (11 Handicap)',
  '13-Year-Old Golf Swing Analysis'
]

这段代码保留了到目前为止收集的所有拇指的数组。在循环中,我首先检查是否有新的拇指——如果 10 秒左右没有新的拇指出现,则中断并假设我们已经收集了所有的拇指。否则,将最后一个缩略图滚动到视图中并收集所有缩略图,从而扩展结果列表。

“等待 10 秒以获取新拇指,然后退出”在技术上是一种竞争条件。如果有一些滞后,它可能会引发误报,但对于初学者来说似乎已经足够好了。过于慷慨地延长超时会阻止脚本在抓取结束时退出,因此不适合抓取大量视频很少的频道。也许更好的方法是在触发每次滚动后等待 /browse API 响应。此响应对象中包含各种附加数据。

另见Puppeteer - scroll down until you can't anymore

猜你喜欢
  • 2014-12-12
  • 2015-05-30
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 2013-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多