【问题标题】:NodeJS Puppeteer Get InnerText of Child Elements from XPathNodeJS Puppeteer 从 XPath 获取子元素的 InnerText
【发布时间】:2020-07-07 10:29:49
【问题描述】:

我有一个项目要从内部 CRM 中抓取某些客户购买的产品。这个 CRM 使用了很多动态加载的磁贴,所以没有多少一致的类名(很多在每次页面加载时随机附加一个 ID),而且在同一个类名的页面上也有很多不同的报表/元素,所以我无法在整个页面中查询元素选择器。

我已经通过 xpath 确定了我想要的“父”元素。然后我想向下钻取并仅获取与查询选择器匹配的子项的 innerText(我看到的大多数线程都有人在整个页面上执行查询选择器,这将从我不想要的菜单中获得结果)。

我可以在浏览器控制台中使用常规 Javascript 来执行此操作,但我不知道如何在 Node/Puppeteer 中执行此操作。这是我目前所拥有的:

//Getting xpath of the "box" that contains all of the product tiles that a customer has
const productsBox = await page.$x("/html/body/blah/blah/blah");

这就是它崩溃的地方。我不太熟悉某些语法或理解 Puppeteer 的文档,但我尝试了几种不同的方法(我对使用 => 格式的函数也不太满意。Puppeteer 文档有一个 example我正在尝试做的事情,但我尝试了相同的结构,但它也没有返回任何内容):

//Tried using the elementHandle.$$eval approach on the zero index of my xpath results, 
//but doesn't return anything when I console.log(productsList)
    const productsList = await productsBox[0].$$eval('.title-heading', function parseAndText (products) {
      productsList=[];
      for (i=0; i<products.length; i++) {
        productsList.push(products[i].innerText.trim());
      }
      return productsList;
      }
    );

//Tried doing the page.$$eval approach with selector, passing in the zero index of my xpath
      const productsList = await page.$$eval('.title-heading', function parseAndText (products) {
      productsList=[];
      for (i=0; i<products.length; i++) {
        productsList.push(products[i].innerText.trim());
      }
      return productsList;
      }, productsBox[0]

//Tried the page.evaluate and then page.evaluateHandle approach on the zero index of my xpath, 
//doing the query selection inside the evaluation and then doing something with that.
    let productsList= await page.evaluateHandle(function parseAndText(productsBoxZero) {
      productsInnerList = productsBoxZero.querySelectorAll(".title-heading");
      productsList=[];
      for (i=0; i<productsInnerList.length; i++) {
        productsList.push(productsInnerList[i].innerText.trim());
        //Threw a console log here to see if it does anything, 
        //But nothing is logged
        console.log("Pushed product " + i + " into the product list");
      }
      return productsList;
    }, productsBox[0]);

在输出方面,我已经在控制台记录了一些变量,我得到了这个:

productsBox is JSHandle@node
productsBox[0] is JSHandle@node
productList is

为了比较,我在控制台中通过 Javascript 并行执行此操作,以确保我正确地单步执行逻辑并得到我期望的结果:

>productsBox=$x("/html/body/blah/blah/blah");
>productsInnerList=productsBox[0].querySelectorAll(".title-heading");
>productsInnerList.length;
//2, and this customer has 2 products
>productsList=[];
>for (i=0; i<productsInnerList.length; i++) {
    productsList.push(productsInnerList[i].innerText.trim());
};
>console.log(productsList)
>["Product 1", "Product 2"]

感谢您阅读本文,感谢您的帮助!

[编辑]

对于一些额外的研究,我尝试使用 page.evaluateHandle 并尝试记录我的变量到目前为止:

productsBox is JSHandle@node
productsBox[0] is JSHandle@node
productList is JSHandle@array

这是进步。我试着做: let productsText=await productsList.jsonValue();

但是当我尝试输出时,我什么也得不到:

await console.log("productsText is " + productsText);

productsBox is JSHandle@node
productsBox[0] is JSHandle@node
productList is JSHandle@array
productsText is

【问题讨论】:

    标签: javascript node.js xpath puppeteer innertext


    【解决方案1】:

    我建议在尝试每个功能之前仔细阅读文档。 $$eval 在选择器上进行评估,在这种情况下传递元素是没有意义的。 evaluateHandle 用于返回页内元素,因为您要返回一个文本数组并且它是可序列化的,所以您不需要它。您只需将元素传递给 page.evaluate 或在 puppeteer 上下文中执行所有操作。

    为了能够看到页面内的 console.log,您需要:

    page.on('console', msg => console.log(msg.text()));
    
    1. 使用page.evaluate
    let productsList= await page.evaluate((element) => {
        const productsInnerList = element.querySelectorAll(".title-heading");
        const productsList=[];
        for (const el of productsInnerList) {
            productsList.push(el.innerText.trim());
            console.log("Pushed product " + el.innerText.trim() + " into the product list");
        }
        return productsList;
    }, productsBox[0]);
    
    1. 使用elementHandle.$$
    const productList = [];
    const productsInnerList = await productsBox[0].$$('.title-heading');
    for (const element of productsInnerList){
        const innerText = await (await element.getProperty('innerText')).jsonValue();
        productList.push(innerText);
    }
    

    【讨论】:

    • 非常感谢!我仍然处于学习 Node 和 Javascript 的早期阶段,所以我一直在反复阅读文档。有些东西我理解,有些东西对我来说还是有点难。我花了一个晚上在其他几个网站上玩你的代码示例。我终于用这两种方法让它在另一个网站上工作了。它仍然无法在我的原始网站上运行。为子 xpath 添加了另一个等待行,它开始工作了!再次感谢您的帮助,我将继续学习和努力。
    • @NotGoodAtThis 你是对的,我错过了选择孩子之前的等待。编辑了答案。
    • 您的解决方案完全有效,我在与我的目标站点具有相似页面结构的其他站点上对其进行了验证。我正在为我的项目抓取的网站加载速度有点慢,所以我不得不进行额外检查以确保父元素和至少一个目标子元素都存在。再次感谢您的帮助,根据您的反馈,我能够对这两种格式进行大量试验!
    【解决方案2】:

    根据@mbit 的回答,我能够让它工作。我首先在另一个与我的结构相似的站点上进行了测试。将代码复制到我的原始站点,它仍然无法正常工作,只有一个空输出。事实证明,虽然我有一个 await page.$x(full/xpath) 用于父元素,但包含 innerText 的子元素仍未加载。所以我做了两件事:

    1) 为列表中的第一个元素添加了另一个等待页面。$x(full/xpath) 是我的目标之一 2)实现了mbit提供的page.evaluate方法。 2a)显式写出函数(仍然围绕=>结构包裹)

    下面的最终代码(一些变量名称因测试而改变):

    let productsTextList= await page.evaluate(function list(list) {
      const productsInnerList = list.querySelectorAll(".title-heading");
      productsTextList =[];
      for (n=0; n<productsInnerList.length; n++) {
          product=productsInnerList[n].innerText.trim();
          productsTextList.push(product);
      }
      return productsTextList;
    }, productsBox[0]);
    
    console.log(productsTextList);
    

    我选择了 page.evaluate 方法,因为它更符合我在浏览器控制台中所做的工作,因此很容易测试。 elementHandle.$$ 方法的诀窍是,正如 mbit 所提到的,使用await element.getProperty('innerText') 而不是.innerText。在故障排除和学习过程中,我还偶然发现了this thread on GitHub,它还谈到了如何提取它(与上述 mbit 的方法相同)。对于遇到类似问题的任何人,您并不孤单!

    【讨论】:

      猜你喜欢
      • 2021-02-13
      • 2023-01-17
      • 2021-05-17
      • 2020-07-17
      • 2023-03-22
      • 2012-03-09
      • 2019-04-01
      • 1970-01-01
      • 2021-05-20
      相关资源
      最近更新 更多