在 puppeteer 中滚动到 div 的底部不起作用

Scrolling to the bottom of a div in puppeteer not working

所以我正在尝试抓取下图中框出区域中的所有音乐会:

https://i.stack.imgur.com/7QIMM.jpg

问题是列表只显示前 10 个选项,直到您在特定 div 中向下滚动到底部,然后它会动态显示更多选项,直到没有更多结果。我尝试按照下面的 link 回答,但无法向下滚动以显示所有 'concerts':

这是我的基本代码:

const browser = await puppeteerExtra.launch({ args: [                
    '--no-sandbox'                                                  
    ]});

async function functionName() {
    const page = await browser.newPage();
    await preparePageForTests(page);
    page.once('load', () => console.log('Page loaded!'));
    await page.goto(`https://www.google.com/search?q=concerts+near+poughkeepsie&client=safari&rls=en&uact=5&ibp=htl;events&rciv=evn&sa=X&fpstate=tldetail`);   

    const resultList = await page.waitForSelector(".odIJnf"); 
    const scrollableSection = await page.waitForSelector("#Q5Vznb");    //I think this is the div that contains all the concert items.
    const results = await page.$$(".odIJnf");  //this needs to be iterable to be used in the for loop

//this is where I'd like to scroll down the div all the way to the bottom

    for (let i = 0; i < results.length; i++) {
      const result = await (await results[i].getProperty('innerText')).jsonValue();
      console.log(result)
    }
}

试试这个以向下滚动音乐会列表。您可以一直循环直到结果数量停止增加,或者找到您要找的音乐会:

await page.evaluate(()=>{
  document.querySelector("#Q5Vznb").scrollIntoView(false);
});

正如您在问题中提到的,当您 运行 page.$$ 时,您会得到一个 ElementHandle 的数组。来自 Puppeteer's documentation:

ElementHandle represents an in-page DOM element. ElementHandles can be created with the page.$ method.

这意味着您可以遍历它们,但您还必须 运行 evaluate()$eval() 遍历每个元素才能访问 DOM 元素。

我从您的代码片段中看到您正在尝试访问处理列表 scroll 事件的父级 div。问题是这个页面似乎使用了自动生成的 classesids。这可能会使您的代码变得脆弱或无法正常工作。最好尝试直接访问 ullidiv

我创建了这个片段,可以从该站点获取 ITEMS 场音乐会:

const puppeteer = require('puppeteer')

/**
 * Constants
 */
const ITEMS = process.env.ITEMS   || 50
const URL   = process.env.URL     || "https://www.google.com/search?q=concerts+near+poughkeepsie&client=safari&rls=en&uact=5&ibp=htl;events&rciv=evn&sa=X&fpstate=tldetail"

/**
 * Main
 */
main()
  .then( ()    => console.log("Done"))
  .catch((err) => console.error(err))

/**
 * Functions
 */
async function main() {
  const browser = await puppeteer.launch({ args: ["--no-sandbox"] })
  const page = await browser.newPage()
  
  await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36')
  await page.goto(URL)
 
  const results = await getResults(page)
  console.log(results)
  
  await browser.close()
}

async function getResults(page) {
  await page.waitForSelector("ul")
  const ul  = (await page.$$("ul"))[0]
  const div = (await ul.$x("../../.."))[0]
  const results = []
  
  const recurse = async () => {
    // Recurse exit clause
    if (ITEMS <= results.length) {
      return
    }

    const $lis = await page.$$("li")
    // Slicing this way will avoid duplicating the result. It also has
    // the benefit of not having to handle the refresh interval until
    // new concerts are available.
    const lis = $lis.slice(results.length, Math.Infinity)
    for (let li of lis) {
      const result = await li.evaluate(node => node.innerText)
      results.push(result)
    }
    // Move the scroll of the parent-parent-parent div to the bottom
    await div.evaluate(node => node.scrollTo(0, node.scrollHeight))
    await recurse()
  }
  // Start the recursive function
  await recurse()
 
  return results
}

通过研究页面结构,我们看到列表的 ul 嵌套在处理 scrolldiv 深处的三个 div 中。我们也知道页面上只有两个ul,第一个就是我们要的。那是 我们在这些方面做了什么:

  const ul  = (await page.$$("ul"))[0]
  const div = (await ul.$x("../../.."))[0]

$x 函数计算相对于文档的 XPath 表达式作为其上下文节点*。它允许我们遍历 DOM 树,直到找到我们需要的 div。然后我们 运行 一个递归函数,直到我们得到我们想要的项目。