Puppeteer - 如何 select 基于其内部文本的元素?
Puppeteer - how to select an element based on its inner text?
我正在使用 Puppeteer 抓取一堆页面。内容不以classes/ids/etc区分。并且在页面之间以不同的顺序呈现。因此,我将需要 select 基于其内部文本的元素。我在下面包含了一个简化示例 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>
在这个例子中,我需要抓取制造商名称和制造商地址。所以我想我需要 select 基于嵌套 th 的内部文本的适当 tr,并在同一 tr 中抓取关联的 td。请注意,此 table 的行顺序并不总是相同,并且 table 包含的行数比这个简化示例多得多,所以我不能只 select 第 3 行和第 4 行td.
我已尝试使用 XPATH select 基于内部文本的元素,但它似乎不起作用:
var manufacturerName = document.evaluate("//th[text()='Manufacturer name']", document, null, XPathResult.ANY_TYPE, null)
这甚至不是我需要的数据(它将是与此相关的 td),但我认为这至少是第 1 步。如果有人可以通过内部文本向 select 或 select 与此关联的 td 提供有关策略的输入,我将不胜感激。
您可以像这样获取数据:
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);
根据上述答案中的用例说明,以下是用例的逻辑:
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);
这确实是一个 xpath 问题,并不特定于 puppeteer,所以这个问题可能也会有所帮助,因为您需要找到 <th>
之后的 <td>
你找到了:XPath:: Get following Sibling
但是您的 xpath 确实 适合我。在 Chrome DevTools 页面上 HTML 在你的问题中,运行 这行查询文档:
$x('//th[text()="Manufacturer name"]')
注意:$x()
是一个仅在 Chrome DevTools 中有效的辅助函数,尽管 Puppeteer 具有类似的 Page.$x
函数。
该表达式应该 return 一个包含一个元素的数组,<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();
一次获得所有这些的简单方法:
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
}, {})
})
我正在使用 Puppeteer 抓取一堆页面。内容不以classes/ids/etc区分。并且在页面之间以不同的顺序呈现。因此,我将需要 select 基于其内部文本的元素。我在下面包含了一个简化示例 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>
在这个例子中,我需要抓取制造商名称和制造商地址。所以我想我需要 select 基于嵌套 th 的内部文本的适当 tr,并在同一 tr 中抓取关联的 td。请注意,此 table 的行顺序并不总是相同,并且 table 包含的行数比这个简化示例多得多,所以我不能只 select 第 3 行和第 4 行td.
我已尝试使用 XPATH select 基于内部文本的元素,但它似乎不起作用:
var manufacturerName = document.evaluate("//th[text()='Manufacturer name']", document, null, XPathResult.ANY_TYPE, null)
这甚至不是我需要的数据(它将是与此相关的 td),但我认为这至少是第 1 步。如果有人可以通过内部文本向 select 或 select 与此关联的 td 提供有关策略的输入,我将不胜感激。
您可以像这样获取数据:
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);
根据上述答案中的用例说明,以下是用例的逻辑:
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);
这确实是一个 xpath 问题,并不特定于 puppeteer,所以这个问题可能也会有所帮助,因为您需要找到 <th>
之后的 <td>
你找到了:XPath:: Get following Sibling
但是您的 xpath 确实 适合我。在 Chrome DevTools 页面上 HTML 在你的问题中,运行 这行查询文档:
$x('//th[text()="Manufacturer name"]')
注意:$x()
是一个仅在 Chrome DevTools 中有效的辅助函数,尽管 Puppeteer 具有类似的 Page.$x
函数。
该表达式应该 return 一个包含一个元素的数组,<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();
一次获得所有这些的简单方法:
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
}, {})
})