Javascript 木偶演员的行为与开发人员控制台中的不同

Javascript 木偶演员的行为与开发人员控制台中的不同,javascript,node.js,web-scraping,puppeteer,Javascript,Node.js,Web Scraping,Puppeteer,我正在尝试使用Puppeter提取此页面的标题: 我有下面的代码 (async () => { const browser = await puppet.launch({ headless: true }); const page = await browser.newPage(); await page.goto(req.params[0]); //this is the url

我正在尝试使用Puppeter提取此页面的标题:

我有下面的代码

          (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;
            });
我已经使用浏览器控制台测试过了,甚至使用了Puppeter的{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"
我开始认为我在异步承诺方面做错了什么,因为这是不同的部分。谁能给我指出正确的方向吗

编辑:正如建议的那样,我使用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");
              }
编辑:进步!!
感谢大卫伯顿。看来无头是假的吗?有人知道原因吗?

导航到页面时,请等待页面加载

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');

}
还是这个


如果您只需要
title
的内部文本,您可以使用Puppeter方法来实现相同的结果:

const title = await page.$eval('title', el => el.innerText)
console.log(title)
输出:

Zella High Waist Studio Pocket 7/8 Leggings | Nordstrom
page.$$eval(选择器,pageFunction[,…args])

page.$eval方法在页面内运行
Array.from(document.querySelectorAll(selector))
,并将其作为第一个参数传递给pageFunction


但是:您的主要问题是,您正在访问的页面是React.Js中制作的单页应用程序(SPA),其
标题
由JavaScript包动态填充。因此,当
的内容仅为:
“”
(空字符串)时,您的木偶演员会在
中找到一个有效的
title
元素

通常,您应该在SPA的情况下使用,以确保DOM由实际的JS框架正确填充,并且功能齐全:

await page.goto('https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106', {
    waitUntil: 'networkidle0'
  })
不幸的是,对于这个特定的网站,它抛出了一个超时错误,因为网络连接直到30000毫秒的默认超时才会关闭,网页前端似乎有点不正常(webworker处理?)

作为一种解决方法,在尝试检索
标题之前,您可以使用:
等待页面。waitFor(8000)
强制木偶演员睡眠8秒钟,届时将正确填充该标题。实际上,当您在DevTools控制台中运行脚本时,它会工作,因为您没有立即运行脚本:此时页面已完全加载,DOM已填充。

此脚本将返回预期的标题:

异步函数fn(){ const browser=wait puppeter.launch({headless:false}) const page=wait browser.newPage() 等待页面。转到('https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106', { waitUntil:'networkidle2' }) 等待页面。等待(8000) const title=等待页面。$eval('title',el=>el.innerText) console.log(标题) 等待浏览器关闭() } fn()

可能
const browser=wait puppeter.launch({headless:false})
也会影响结果。

我尝试了第一个。结果是真的:(但我正在查看的页面中肯定有一个文档标题。你可以像这样访问标题。标题我不是。我应该是吗?:0我只希望这一个函数是异步的。我想,它可以在等待时完成其余的工作。这错了吗?我应该将我的整个代码包装在一个异步函数中吗?为什么networkidle2 specifically,而不是networkidle0或1?当我遇到该问题时,我使用了此url的解决方案。即使使用networkidle和8000,它仍然返回空。是否可能在这些等待之后它仍未完全加载?或者我是否做了其他错误?如何使用networkidle?如果使用networkidle0,整个脚本可能会失败。我的脚本仅为这3行(在page.goto之后)现在它返回了标题。我尝试了networkidle2和networkidle0。请参见编辑。相同的结果。如果你说你的代码正在返回标题,那么可能是我的代码的其他部分搞乱了,因为我们有相同的东西。我将清除它们,看看它是否仍然会导致问题。谢谢你的帮助!@QrowSaki我已添加我的整个脚本最后都很清晰。我认为游戏规则的改变者是
{headless:true}
变为
{headless:false}
。值得调查一下为什么它会产生不同的结果。很高兴我能帮上一点忙。是的,这是“令人头痛的”chrome。问题是:只有在浏览器不是无头的情况下(至少到目前为止,这似乎是限制因素)。你可以尝试使用Puppeter extra和其他名为隐形的插件来假装你的chrome是一个有头的实例,而不启动UI:如果你值得这么做的话(以及对项目的其他依赖项)。
 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');

   }
const title = await page.$eval('title', el => el.innerText)
console.log(title)
Zella High Waist Studio Pocket 7/8 Leggings | Nordstrom
await page.goto('https://www.nordstrom.com/s/zella-high-waist-studio-pocket-7-8-leggings/5460106', {
    waitUntil: 'networkidle0'
  })