【问题标题】:In puppeteer, how I get content of nextSibling of element found by innerText?在 puppeteer 中,如何获取由 innerText 找到的元素的 nextSibling 的内容?
【发布时间】:2021-07-13 14:25:14
【问题描述】:

我有以下 html:

<article data-tid="product-detail"> <!-- I page.$eval on this element. -->
  <h1 itemprop="name">Product name</h1> <!-- This I can query by itemprop -->
  
  <h2>Some other topic</h2>
  <p>I don't want this text.</p>

  <h2>Unique topic</h2> <!-- I can find this innerText === "Unique topic". -->
  <p>Text I want real' bad.</p> <!-- I want this innerText. -->

  <h2>Some other topic</h2>
  <p>I don't want this text.</p>
</article>

如何获得“我想要真正的'不好的文字”。从页面,知道“独特的主题”?

我从节点脚本运行 puppeteer。


这是我目前所拥有的:

async function puppeteerProductDataExtractor($product) {
  // This works like charm. 
  const productName = $product.querySelector('[itemprop=name]')?.innerText;

  // Now I have to find the right h2 and get it's next element sibling. 
  const h2 = $product.querySelectorAll('h2');
  
  // 1. If I try to get innerText of all h2, it fails with getProperty function not defined.
  console.log(
    await Promise.all([...h2].map(async $el => await (await element.getProperty('innerText')).jsonValue()))
  );

  // 2. This returns empty array
  console.log([...h2].filter($el => $el.innerText.startsWith('Unique topic')));
  // This prints JSHandle@array - innerText is not a string. 
  console.log([...h2].map($el => $el.innerText));
  // This also prints JSHandle@array which is just insane.
  console.log([...h2].map($el => Object.keys($el)));
  // This fails with "property is not a function" error. 
  console.log([...h2].map(el => el.property('innerText')));
  // So does this. 
  console.log([...h2].map(el => el.getProperty('innerText')));
}

page.on('console', consoleObj => console.log('xxxx', consoleObj.text()));

const product = await page.$eval('article[data-tid=product-detail]', puppeteerProductDataExtractor);

第一次尝试来自这里:https://stackoverflow.com/a/52828950/336753

其他一切都只是沮丧的盲目射击。必须承认我很困惑。有些东西应该根据文档工作,但它只是失败了。像 JSHandle should have the property function,但是当我调用它时它失败了(不是函数)。

我什至没有进入 nextSibling 部分。

我尝试了很多代码,但大部分都失败了,不想用它污染问题。感觉这应该很简单,我只是错过了一些东西。希望初衷清晰。

我确信有一个简单的解决方案,但尝试和失败似乎不是解决问题的方法。


经过更多挖掘,事实证明我的初衷是正确的。 filter() 不起作用不是因为 innerText 将是 JSHandle 的实例(如它所显示的那样),而是因为大写的第一个字母是由 CSS 完成的(在比较之前必须小写以统一字符串)。有点惭愧……抱歉,感谢@ggorlen 的帮助。

/* WE'RE INSIDE $eval FUNCTION */
// This returns JSHandle@array which is just weird...
console.log([...h2].map(el => el.innerText));
// But this returns the joined string correctly. Huh... 
console.log([...h2].map(el => el.innerText).join(';'));
// So this eventually works
console.log([...h2]
  .filter(el => el.textContent.toLowerCase().startsWith('unique topic'))[0]?
  .nextElementSibling.textContent);

【问题讨论】:

    标签: puppeteer


    【解决方案1】:

    您可以尝试nextElementSibling 而不是nextSibling,它可以返回一个空白文本节点:

    const text = document
      .querySelector("h2")
      .nextElementSibling
      .textContent
    ;
    console.log(text);
    <h2>Unique topic</h2>
    <p>Text I want real' bad.</p>

    虽然您尚未共享您的标记,但您可能拥有多个 &lt;h2&gt;/&lt;p&gt; 组合,并且您希望从每个组合中提取文本。这可能有助于您入门:

    const text = [...document.querySelectorAll("h2")]
      .map(e => e.nextElementSibling.textContent)
    ;
    console.log(text);
    <h2>Unique topic 1</h2>
    <p>Text I want real' bad. 1</p>
    <h2>Unique topic 2</h2>
    <p>Text I want real' bad. 2</p>
    <h2>Unique topic 3</h2>
    <p>Text I want real' bad. 3</p>

    如果不明显,上述代码必须在$eval$$evalevaluate 内运行。例如,您可以使用:

    const puppeteer = require("puppeteer");
    
    let browser;
    (async () => {
      const html = `
        <h2>Unique topic 1</h2>
        <p>Text I want real' bad. 1</p>
        <h2>Unique topic 2</h2>
        <p>Text I want real' bad. 2</p>
        <h2>Unique topic 3</h2>
        <p>Text I want real' bad. 3</p>
      `;
      browser = await puppeteer.launch();
      const [page] = await browser.pages();
      await page.setContent(html);
      const contents = await page.$$eval(
        "h2",
        els => els.map(e => e.nextElementSibling.textContent)
      ); 
      console.log(contents);
    })()
      .catch(err => console.error(err))
      .finally(async () => await browser.close())
    ;
    

    你的线路

    await (await element.getProperty('innerText')).jsonValue()
    

    是纯 Node Puppeteer,但您正试图在浏览器控制台中运行它。这是一个常见的错误——elementHandles 只能在 Puppeteer 中工作。 thread you linked 提供了一个底部示例,该示例显示了 evaluate 方法,该方法使用仅浏览器的代码。

    对于调试浏览器代码(在evaluate$eval$$eval 等的回调中执行的东西),我建议将侦听器附加到控制台,如How do print the console output of the page in puppeter as it would appear in the browser? 所示,或者直接运行以便您可以看到错误信息。

    另一个技巧是在浏览器中手动计算您的选择器,然后将它们添加到 Puppeteer 的 evaluate 仅在您使用它们之后。 evaluate 是通用的,因此您可以使用简写 Puppeteer page 和 elementHandle 便捷方法(如 .click().getProperty().$eval.$x 等)进行的所有 DOM 操作都可以直接在 evaluate 中完成。

    【讨论】:

    • 感谢您的回答。我确实在节点中运行我的代码。我不知道有人会在浏览器中运行 puppeteer,所以我没有指定它。这也是为什么我对没有在 JSHandle 上定义 getProperty 感到困惑的原因。现在我正在考虑它,它可能是在由节点实例化的浏览器模拟器中运行的代码,所以也许这就是为什么没有在那里定义函数的原因。我确实连接了控制台(这就是我获得输出的方式),但你也可以为其他人提及它。我会根据您的笔记做更多尝试,也许会编辑问题以使其更清楚。谢谢!
    猜你喜欢
    • 2020-07-07
    • 2021-05-17
    • 2022-09-23
    • 2019-03-20
    • 1970-01-01
    • 2023-03-22
    • 2011-04-18
    • 2011-05-06
    • 2014-01-14
    相关资源
    最近更新 更多