在 puppeteer 中,如何获取 innerText 找到的元素的 nextSibling 的内容?

In puppeteer, how I get content of nextSibling of element found by innerText?

我关注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>

如何获得“我想要真实的糟糕文本”。从页面,知道“独特的话题”?

我 运行 从节点脚本操纵木偶。


这是我目前所拥有的:

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

第一次尝试来自这里:

其他一切都只是沮丧的盲目射击。必须承认我很困惑。有些东西应该根据文档工作,但它只是失败了。像 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);

您可以尝试 nextElementSibling rather than nextSibling,它可以 return 空白文本节点:

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

虽然您没有共享您的标记,但看起来您可能有多个 <h2>/<p> 组合,并且您想从每个组合中提取文本。这可能有助于您入门:

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 中工作。 提供了一个显示 evaluate 方法的底部示例,该方法使用仅浏览器代码。

为了调试浏览器代码(在 evaluate$eval$$eval 等的回调中执行的内容),我建议将侦听器附加到控制台,如 或 运行 以便您可以看到错误消息。

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

中完成