我正在尝试使用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;
});
我已经使用浏览器控制台甚至使用{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"
我开始认为我在异步承诺上做错了什么,因为这是不同的部分。有人能给我指出正确的方向吗?
EDIT:正如建议的那样,我使用document.title进行了测试,它应该在那里,但是它也返回了null。请参见下面的代码和日志:
一个三个三个一个
这与单页应用程序bs有关吗?我认为puppeteer处理这个问题是因为它首先加载所有内容。
编辑:我已经添加了networkidle行,并按照建议等待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
编辑:进步!!谢谢大卫巴顿。看来无头必须是假的才能起作用?有人知道为什么吗?
3条答案
按热度按时间dced5bon1#
如果你只需要
title
的innerText,你可以用page.$eval
puppeteer方法来实现同样的结果:输出:
page.$$eval(selector, pageFunction[, ...args])
page. $eval方法在页面内运行
Array.from(document.querySelectorAll(selector))
,并将其作为第一个参数传递给pageFunction。但是,主要的问题是,您访问的页面是一个用React.Js制作的单页应用程序(SPA),它的
title
是由JavaScript包动态填充的,因此,当<head>
的内容简单地为:""
(空字符串)。通常情况下,您应该在SPA的情况下使用
waitUntil: 'networkidle0'
,以确保DOM由实际的JS框架正确填充,并且功能齐全:不幸的是,这个特定的网站,它抛出了一个超时错误,因为网络连接没有关闭,直到30000毫秒默认超时,网页的前端似乎有什么不好(网络工作者处理?).
作为一个变通方案,你可以强制 puppet 师睡眠8秒:
await page.waitFor(8000)
,然后再尝试检索title
:* * 到那时,它将被正确填充。实际上,当您在DevTools控制台中运行脚本时,它会工作,因为您不会立即运行脚本:当页面已经完全加载时,DOM被填充。**此脚本将返回预期的标题:
也许
const browser = await puppeteer.launch({ headless: false })
也会影响结果。6ljaweal2#
导航到该页时,请等待该页加载完毕
你能试试这个吗
3pmvbmvn3#
作为一个规范的回答,Puppeteer在Puppeteer中的行为可能不同于在浏览器开发工具中的行为,原因有很多:
page.waitForSelector
是解决方案,但有时more drastic measures是必要的。page.waitForTimeout
是一个糟糕的解决方案,因为它会导致竞争条件,并不必要地减慢脚本。但是它对于在收紧 predicate 之前的初始调试是有帮助的。puppeteer.launch({headless: false});
启动Puppeteer。.click()
调用可以处理滚动到视图外或没有宽度和高度的内容。但Puppeteer的page.click()
可能无法单击元素。page.click()
发出一系列鼠标命令,尝试以可信的方式单击元素。这适用于page.type
和其他Puppeteer API方法。"networkidle0"
永远无法解析,从而导致导航超时,而导航超时可能不会导致浏览器出现问题。许多这样的问题可以通过在
await page.goto(url, {waitUntil: "domcontentloaded"})
之后记录console.log(await page.content())
来调试。这通常可以告诉你网站是否屏蔽了你,或者选择器是否还没有显示。如果你需要在这个静态HTML字符串中搜索你的选择器,Cheerio可能是一个有用的选择,尽管我不建议在一般情况下在Puppeteer中使用它。检查iframe和shadow根可以在dev工具中完成,但是如果你放大了一个特定的深度嵌套元素,很容易漏掉。向上遍历父节点,确保它们都是普通的HTML元素。