【问题标题】:Scrolling to the bottom of a div in puppeteer not working在 puppeteer 中滚动到 div 的底部不起作用
【发布时间】:2021-08-01 01:08:19
【问题描述】:

所以我试图刮掉下图中框出区域的所有音乐会:

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

问题是列表仅显示前 10 个选项,直到您在该特定 div 中向下滚动到底部,然后它会动态显示更多选项,直到没有更多结果。我尝试按照以下链接的答案进行操作,但无法向下滚动以呈现所有“音乐会”:

How to scroll inside a div with Puppeteer?

这是我的基本代码:

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

【问题讨论】:

    标签: javascript node.js web-scraping puppeteer infinite-scroll


    【解决方案1】:

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

    ElementHandle 表示页内 DOM 元素。 ElementHandles 可以使用page.$ 方法创建。

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

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

    我创建了这个可以从网站上获得ITEMS 数量的音乐会的 sn-p:

    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 的三个divs 深处。我们也知道页面上只有两个uls,第一个就是我们想要的。那是 我们在这些方面做了什么:

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

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

    【讨论】:

      【解决方案2】:

      尝试在音乐会列表中向下滚动。您可以继续循环,直到结果数量停止增加,或者您找到了您正在寻找的音乐会:

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

      【讨论】:

      • 嗨,Benny,我认为 div 可能是错误的。到目前为止,我已经尝试过#Q5Vznb、.MZpzq 和 .uAAqtb——到目前为止,没有一个能够获得超过加载的 .odIJnf 元素的原始数量。你对我应该尝试的其他 div 有什么建议吗?谢谢!
      • 我也试过使用 '#immersive_desktop_root > div.drPJve > div.YbRs3e > div:nth-child(2) > div.UbEfxe.uAAqtb > div.MZpzq.gws-horizo​​n-textlists__tl-no -filters.TWKvJb' 和 '#immersive_desktop_root > div.drPJve > div.YbRs3e > div:nth-child(2) > div.UbEfxe.uAAqtb' 作为 querySelector() 的参数。不幸的是,两者都没有工作。
      • 我认为是对的。我刚刚在 Google Chrome 控制台中访问了该网站,这很有效(右键单击,然后单击 Inspect;或使用快捷键 Ctrl+Shift+I):document.querySelectorAll('.odIJnf').length >> 20 document。 querySelector("#Q5Vznb").scrollIntoView(false); >> undefined document.querySelectorAll('.odIJnf').length >> 30 所以滚动命令后音乐会的数量增加了10。
      • 我使用 await page.$$(".odIJnf") 而不是 document.querySelectorAll 有什么不同吗?
      • 等待页面.$$ 在 Node.js 中执行。当您调用 page.evaluate() 时,该函数会在浏览器控制台中执行,因此您可以运行 querySelector。这是对差异的一个很好的解释:stackoverflow.com/questions/55664420/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-10
      • 1970-01-01
      • 2023-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多