我正在尝试使用 Puppeteer 提取此页面的标题:https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106
我有以下代码,
(async () => {
const browser = await puppet.launch({ headless: true });
const page = await browser.newPage();
await page.goto(req.params[0]); //this is the url
title = await page.evaluate(() => {
Array.from(document.querySelectorAll("meta")).filter(function (
el
) {
return (
(el.attributes.name !== null &&
el.attributes.name !== undefined &&
el.attributes.name.value.endsWith("title")) ||
(el.attributes.property !== null &&
el.attributes.property !== undefined &&
el.attributes.property.value.endsWith("title"))
);
})[0].attributes.content.value ||
document.querySelector("title").innerText;
});
我已经使用浏览器控制台甚至使用 Puppeteer 的 { headless: false } 选项进行了测试。它在浏览器中按预期工作,但是当我实际使用 Node 运行它时,它给出了以下错误。
10:54:21 AM web.1 | (node:10288) UnhandledPromiseRejectionWarning: Error: Evaluation failed: TypeError: Cannot read property 'attributes' of undefined
10:54:21 AM web.1 | at __puppeteer_evaluation_script__:14:20
因此,当我在浏览器中运行相同的 Array.from ...querySelectorAll("meta")...
查询时,我得到了预期的字符串:
"Zella High Waist Studio Pocket 7/8 Leggings | Nordstrom"
我开始认为我在异步 promise 方面做错了什么,因为这是不同的部分。谁能指出我正确的方向?
编辑:按照建议,我使用 document.title 进行了测试,它应该在那里,但它也返回 null。请参阅下面的代码和日志:
console.log(
"testing the return",
(async () => {
const browser = await puppet.launch({ headless: true });
const page = await browser.newPage();
await page.goto(req.params[0]); //this is the url
try {
title = await page.evaluate(() => {
const title = document.title;
const isTitleThere = title == null ? false : true;
//recently read that this checks for undefined as well as null but not an
//undeclared var
return {
title: title,
titleTitle: title.title,
isTitleThere: isTitleThere,
};
});
} catch (error) {
console.log(error, "There was an error");
}
11:54:11 AM web.1 | testing the return Promise { <pending> }
11:54:13 AM web.1 | { title: '', isTitleThere: true }
这与单页应用程序有关系吗?我认为 puppeteer 处理了这个问题,因为它首先加载所有内容。
编辑:我已按照建议添加了网络空闲线并等待 8000 毫秒。标题仍然是空的。下面的代码和日志:
await page.goto(req.params[0], { waitUntil: "networkidle2" });
await page.waitFor(8000);
console.log("done waiting");
title = await page.$eval("title", (el) => el.innerText);
console.log("title: ", title);
console.log("done retrieving");
12:36:39 PM web.1 | done waiting
12:36:39 PM web.1 | title:
12:36:39 PM web.1 | done retreiving
编辑:进展!! 谢谢大卫·巴顿。看来 headless 必须是假的才能起作用?有谁知道为什么吗?
最佳答案
如果你只需要title
的innerText你可以用 page.$eval
来做到这一点puppeteer 方法可以达到相同的结果:
const title = await page.$eval('title', el => el.innerText)
console.log(title)
输出:
Zella High Waist Studio Pocket 7/8 Leggings | Nordstrom
page.$$eval(selector, pageFunction[, ...args])
page.$eval 方法运行 Array.from(document.querySelectorAll(selector))
在页面内并将其作为第一个参数传递给 pageFunction。
但是:您的主要问题是您正在访问的页面是用 React.Js 制作的单页应用程序(SPA),其 title
由 JavaScript 包动态填充。所以你的 puppeteer 操作者找到了一个有效的 title
<head>
中的元素当其内容很简单: ""
(空字符串)。
通常您应该使用 waitUntil: 'networkidle0'
在 SPA 的情况下,确保 DOM 由实际 JS 框架正确填充并且功能齐全:
await page.goto('https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106', {
waitUntil: 'networkidle0'
})
不幸的是,对于这个特定的网站,它会抛出超时错误,因为网络连接在 30000 毫秒默认超时之前不会关闭,网页的前端似乎有些问题(Webworker 处理?)。
作为解决方法,您可以使用以下命令强制 puppeteer sleep 8 秒:await page.waitFor(8000)
在尝试检索 title
之前:到那时它将被正确填充。实际上,当您在 DevTools Console 中运行脚本时,它会起作用,因为您没有立即运行脚本:此时页面已经完全加载,DOM 已填充。
此脚本将返回预期的标题:
async function fn() {
const browser = await puppeteer.launch({ headless: false })
const page = await browser.newPage()
await page.goto('https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106', {
waitUntil: 'networkidle2'
})
await page.waitFor(8000)
const title = await page.$eval('title', el => el.innerText)
console.log(title)
await browser.close()
}
fn()
也许const browser = await puppeteer.launch({ headless: false })
也会影响结果。
关于javascript - Puppeteer 的行为与开发者控制台不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63817148/