javascript Puppeteer的行为与开发人员控制台中的不同

ilmyapht  于 2023-01-01  发布在  Java
关注(0)|答案(3)|浏览(131)

我正在尝试使用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

编辑:进步!!谢谢大卫巴顿。看来无头必须是假的才能起作用?有人知道为什么吗?

dced5bon

dced5bon1#

如果你只需要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包动态填充的,因此,当<head>的内容简单地为:""(空字符串)。
通常情况下,您应该在SPA的情况下使用waitUntil: 'networkidle0',以确保DOM由实际的JS框架正确填充,并且功能齐全:

await page.goto('https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106', {
    waitUntil: 'networkidle0'
  })

不幸的是,这个特定的网站,它抛出了一个超时错误,因为网络连接没有关闭,直到30000毫秒默认超时,网页的前端似乎有什么不好(网络工作者处理?).
作为一个变通方案,你可以强制 puppet 师睡眠8秒:await page.waitFor(8000),然后再尝试检索title:* * 到那时,它将被正确填充。实际上,当您在DevTools控制台中运行脚本时,它会工作,因为您不会立即运行脚本:当页面已经完全加载时,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 })也会影响结果。

6ljaweal

6ljaweal2#

导航到该页时,请等待该页加载完毕

await page.goto(req.params[0], { waitUntil: "networkidle2" }); //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,"isTitleThere" :isTitleThere }
    })

} catch (error) {
    console.log(error, 'There was an error');

}
  • 或是这个 *
try {
title = await page.evaluate(() => {
    const title = document.querySelector('meta[property="og: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,"isTitleThere" :isTitleThere }
   })

   } catch (error) {
   console.log(error, 'There was an error');

   }
3pmvbmvn

3pmvbmvn3#

作为一个规范的回答,Puppeteer在Puppeteer中的行为可能不同于在浏览器开发工具中的行为,原因有很多:

  • 当你开始向开发工具中输入查询时,页面通常已经完全加载了。在Puppeteer中不一定是这样,“页面完全加载”的概念是模糊的。通常,page.waitForSelector是解决方案,但有时more drastic measures是必要的。page.waitForTimeout是一个糟糕的解决方案,因为它会导致竞争条件,并不必要地减慢脚本。但是它对于在收紧 predicate 之前的初始调试是有帮助的。
  • 在浏览器开发工具中,iframe和shadow根会自动展开,允许您选择Puppeteer默认情况下无法选择的内容。
  • 服务器具有检测机器人的方法,防止您访问站点或以意外方式更改页面的行为。
  • 服务器具有检测运行Puppeteer headlessly, but not headfully的机器人的方法。如果您找不到选择器,请尝试使用puppeteer.launch({headless: false});启动Puppeteer。
  • 元素可能具有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元素。

相关问题