无需将 Cheerio 与 Puppeteer 一起使用。 Puppeteer 已经可以使用实时页面,因此将页面快照为字符串,然后将其转储到单独的库中通常没有意义。这是低效的,并且当快照过时时会导致令人困惑的错误。
相反,请使用
page.$$eval(yourSelector, browserCallback)
来完成这项工作:
const puppeteer = require("puppeteer"); // ^21.6.0
const html = `<HTML pasted from your question>`;
let browser;
(async () => {
browser = await puppeteer.launch({headless: "new"});
const [page] = await browser.pages();
await page.setContent(html);
const sel = "table td .people .person";
await page.waitForSelector(sel);
const people = await page.$$eval(
sel,
els => els.map(el => el.textContent.trim())
);
console.log(people);
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
输出:
[
'Richard', 'Linus',
'Brian', 'Alan',
'Bill', 'Ada',
'Steve', 'Ken'
]
上面使用选择器
table td .people .person
解决了连接字符串问题,从技术上讲,这也适用于 Cheerio 方法。
如果你想保持类别不同,你可以使用嵌套查询:
// ...
const people = await page.$$eval("table td", els =>
els.map(el => ({
category: el.className,
people: [...el.querySelectorAll(".person")].map(e =>
e.textContent.trim()
),
}))
);
// ...
给出:
[
{ category: 'hr', people: [ 'Richard', 'Linus', 'Brian' ] },
{
category: 'manufacturing',
people: [ 'Alan', 'Margret', 'Ken', 'Edsger' ]
},
{ category: 'design', people: [ 'Bill', 'Ada', 'Steve', 'Ken' ] }
]
总而言之,如果您正在使用的页面具有您想要的静态数据,那么使用
fetch
和 Cheerio 可能是有意义的。但我假设您正在使用 SPA 或网站,需要一些交互才能到达抓取点,或者有其他一些使用 Puppeteer 的良好动机。
另外,如果您最终坚持使用 Puppeteer 但更喜欢使用 jQuery,您可以添加它,或者如果页面恰好已经包含 jQuery,则使用它。然后,您将在浏览器上下文中运行的
$
系列回调中访问 evaluate
。在大多数情况下,这比使用 Cheerio 更有意义,因为您正在利用 Puppeteer 的实时页面功能,并且不会遇到陈旧数据问题。
为了回答您的其他问题,出于演示和可重复性的目的,我使用
setContent
如上所示,但您可以 运行服务器 并导航到本地主机上的页面。只需确保包含端口即可。