javascript - Puppeteer 的行为与开发者控制台不同

标签 javascript node.js web-scraping puppeteer

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

相关文章:

node.js - Socket.IO 客户端安全

node.js - 向特定 channel 发送消息,不起作用

python - 使用 beautifulsoup 抓取动态网站

node.js - 如何在 Node.js 中仅在所有网页抓取请求完成后才渲染页面?

javascript - 多点触控桌面浏览器

Javascript:将字典添加到列表中

node.js - 向 API 发出获取请求时出现用户代理丢失错误

python - Scrapy:从相对路径构造绝对路径的非重复列表

javascript - 如何将 ng-model 添加到运行时创建的 html 对象

javascript - 使用 jQuery 更快地搜索表