Puppeteer - 如何根据元素的内部文本选择元素?

4

我正在使用 Puppeteer 抓取一堆页面的内容。这些内容没有用类/ID等方式区分,不同页面之间呈现的顺序也不一样。因此,我需要根据它们的内部文本来选择元素。下面是一个简化的 HTML 示例:

<table>
<tr>
    <th>Product name</th>
    <td>Shakeweight</td>
</tr>
<tr>
    <th>Product category</th>
    <td>Exercise equipment</td>
</tr>
<tr>
    <th>Manufacturer name</th>
    <td>The Shakeweight Company</td>
</tr>
<tr>
    <th>Manufacturer address</th>
    <td>
        <table>
            <tr><td>123 Fake Street</td></tr>
            <tr><td>Springfield, MO</td></tr>
        </table>
    </td>
</tr>

在这个例子中,我需要爬取制造商的名称和地址。因此,我想必须根据嵌套th的内部文本选择适当的tr,并抓取在同一tr内的相关td。请注意,该表格的行顺序并不总是相同的,并且该表格包含的行比此简化示例要多得多,因此我不能仅选择第3个和第4个td。
我尝试使用以下XPATH根据内部文本选择元素,但似乎没有起作用:
var manufacturerName = document.evaluate("//th[text()='Manufacturer name']", document, null, XPathResult.ANY_TYPE, null)

这甚至不是我需要的数据(它应该是与这个th相关的td),但我想这至少是第一步。如果有人可以提供关于通过内部文本选择或选择与此th相关联的td的策略,我将不胜感激。

4个回答

3

这实际上是一个xpath问题,与puppeteer无关,因此这个问题也可能会有所帮助,因为您需要找到在找到的<th>之后出现的<td>标签: XPath:: Get following Sibling

但是您提供的xpath对我来说确实有效。在具有您问题中HTML的页面上的Chrome DevTools中运行以下代码以查询文档:

$x('//th[text()="Manufacturer name"]')

注意:$x()是一个只在Chrome DevTools中起作用的辅助函数,尽管Puppeteer有类似的Page.$x函数。

该表达式应返回一个包含一个元素的数组,即具有该文本查询的<th>。要获取其旁边的<td>

$x('//th[text()="Manufacturer name"]/following-sibling::td')

获取其内部文本方法如下:
$x('//th[text()="Manufacturer name"]/following-sibling::td')[0].innerText

一旦你能够遵循该模式,你就能够使用类似的策略在puppeteer中获取你想要的数据,类似于这样:

const puppeteer = require('puppeteer');

const main = async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('http://127.0.0.1:8080/');  // <-- EDIT THIS

  const mfg = await page.$x('//th[text()="Manufacturer name"]/following-sibling::td');
  const prop = await mfg[0].getProperty('innerText');
  const text = await prop.jsonValue();
  console.log(text);

  await browser.close();
}

main();

1
根据您在上面回答中的用例说明,这是该用例的逻辑:
await page.goto(url, { waitUntil: 'networkidle2' }); // Go to webpage url

await page.waitFor('table'); //waitFor an element that contains the text

const textDataArr = await page.evaluate(() => {
    const trArr = Array.from(document.querySelectorAll('table tbody tr'));

    //Find an index of a tr row where th innerText equals 'Manufacturer name'
    let fetchValueRowIndex = trArr.findIndex((v, i) => {
        const element = document.querySelector('table tbody tr:nth-child(i+1) th');
        return element.innerText === 'Manufacturer name';
    });

    //If the findex is found return the innerText of td of the same row else returns undefined
    return (fetchValueRowIndex > -1) ? document.querySelector(`table tbody tr:nth-child(${fetchValueRowIndex}+1) td`).innerText : undefined;
});
console.log(textDataArr);

0
你可以像这样获取数据:
await page.goto(url, { waitUntil: 'networkidle2' }); // Go to webpage url

await page.waitFor('table'); //waitFor an element that contains the text

const textDataArr = await page.evaluate(() => {
    const element = document.querySelector('table tbody tr:nth-child(3) td'); // select thrid row td element like so
    return element && element.innerText; // will return text and undefined if the element is not found
});
console.log(textDataArr);

谢谢回复 - 不幸的是,这个表格的行顺序并不总是相同的,所以我不能只选择第三和第四个td。也没有id或class - 我需要根据同一行的th的内部文本选择td,其中包括“制造商名称”或“制造商地址”。 - MacGruber
我为你在这里澄清的用例发布了一个新答案,请尝试那个逻辑,它会对你有帮助。 - kavigun

0

一种简单的方法是同时获取它们:

let data = await page.evaluate(() => {
  return [...document.querySelectorAll('tr')].reduce((acc, tr, i) => {
    let cells = [...tr.querySelectorAll('th,td')].map(el => el.innerText)
    acc[cells[0]] = cells[1]
    return acc
  }, {})
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接