【问题标题】:Getting DOM node text with Puppeteer and headless Chrome使用 Puppeteer 和无头 Chrome 获取 DOM 节点文本
【发布时间】:2020-01-01 02:58:15
【问题描述】:

我正在尝试使用无头 Chrome 和 Puppeteer 来运行我们的 Javascript 测试,但我无法从页面中提取结果。基于this answer,看来我应该使用page.evaluate()。该部分甚至有一个看起来像我需要的示例。

const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);
await bodyHandle.dispose();

作为一个完整的示例,我尝试将其转换为一个脚本,该脚本将从我在 Stack Overflow 上的用户个人资料中提取我的姓名。我们的项目使用的是节点 6,所以我将 await 表达式转换为使用 .then()

const puppeteer = require('puppeteer');

puppeteer.launch().then(function(browser) {
    browser.newPage().then(function(page) {
        page.goto('https://stackoverflow.com/users/4794').then(function() {
            page.$('h2.user-card-name').then(function(heading_handle) {
                page.evaluate(function(heading) {
                    return heading.innerText;
                }, heading_handle).then(function(result) {
                    console.info(result);
                    browser.close();
                }, function(error) {
                    console.error(error);
                    browser.close();
                });
            });
        });
    });
});

当我运行它时,我得到了这个错误:

$ node get_user.js 
TypeError: Converting circular structure to JSON
    at Object.stringify (native)
    at args.map.x (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/helper.js:30:43)
    at Array.map (native)
    at Function.evaluationString (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/helper.js:30:29)
    at Frame.<anonymous> (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:376:31)
    at next (native)
    at step (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:355:24)
    at Promise (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:373:12)
    at fn (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:351:10)
    at Frame._rawEvaluate (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:375:3)

问题似乎在于将输入参数序列化为page.evaluate()。我可以传入字符串和数字,但不能传入元素句柄。示例是错误的,还是 Node 6 的问题?如何提取 DOM 节点的文本?

【问题讨论】:

  • 将 ElementHandle 作为参数传递给 page.evaluate 是最近才在 github.com/GoogleChrome/puppeteer/commit/… 中添加的。您的代码将在 Puppeteer 的下一个版本中运行。
  • 我猜我的时机不对。感谢@Joel 的解释。

标签: node.js google-chrome-headless puppeteer


【解决方案1】:

我找到了三个解决此问题的方法,具体取决于您提取的复杂程度。最简单的选项是一个我没有注意到的相关函数:page.$eval()。它基本上做了我想做的事情:结合page.$()page.evaluate()。这是一个有效的示例:

const puppeteer = require('puppeteer');

puppeteer.launch().then(function(browser) {
    browser.newPage().then(function(page) {
        page.goto('https://stackoverflow.com/users/4794').then(function() {
            page.$eval('h2.user-card-name', function(heading) {
                return heading.innerText;
            }).then(function(result) {
                console.info(result);
                browser.close();
            });
        });
    });
});

这给了我预期的结果:

$ node get_user.js 
Don Kirkby top 2% overall

我想提取一些更复杂的东西,但我终于意识到评估函数是在页面的上下文中运行的。这意味着您可以使用页面中加载的任何工具,然后来回发送字符串和数字。在这个例子中,我在一个字符串中使用 jQuery 来提取我想要的内容:

const puppeteer = require('puppeteer');

puppeteer.launch().then(function(browser) {
    browser.newPage().then(function(page) {
        page.goto('https://stackoverflow.com/users/4794').then(function() {
            page.evaluate("$('h2.user-card-name').text()").then(function(result) {
                console.info(result);
                browser.close();
            });
        });
    });
});

这给了我一个空白完整的结果:

$ node get_user.js 

                            Don Kirkby

                                top 2% overall

在我的真实脚本中,我想提取几个节点的文本,所以我需要一个函数而不是一个简单的字符串:

const puppeteer = require('puppeteer');

puppeteer.launch().then(function(browser) {
    browser.newPage().then(function(page) {
        page.goto('https://stackoverflow.com/users/4794').then(function() {
            page.evaluate(function() {
                return $('h2.user-card-name').text();
            }).then(function(result) {
                console.info(result);
                browser.close();
            });
        });
    });
});

这给出了完全相同的结果。现在我需要添加错误处理,并可能降低缩进级别。

【讨论】:

    【解决方案2】:

    使用await/async$eval,语法如下:

    await page.goto('https://stackoverflow.com/users/4794')
    const nameElement = await context.page.$eval('h2.user-card-name', el => el.text())
    console.log(nameElement)
    

    【讨论】:

    • 谢谢,但我试图在没有异步和等待的节点 6 中执行此操作。
    • 发生这种情况时将其放入 babel 中。
    • 我认为返回的 DOM 元素不会包含 text() 方法;你可能想使用 innerText
    • 这里的context 怎么样了?
    • context 通常在创建 puppeteer 实例时提供给您。不过,API 可能在编写后发生了变化。
    【解决方案3】:

    我使用 page.$eval

    const text = await page.$eval('h2.user-card-name', el => el.innerText );
    console.log(text);
    
    

    【讨论】:

      【解决方案4】:

      我使用以下方法取得了成功:

      const browser = await puppeteer.launch();
      try {
        const page = await browser.newPage();
        await page.goto(url);
        await page.waitFor(2000);
        let html_content = await page.evaluate(el => el.innerHTML, await page.$('.element-class-name'));
        console.log(html_content);
      } catch (err) {
        console.log(err);
      }
      

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 2019-03-30
        • 2010-10-20
        • 1970-01-01
        • 2010-11-30
        • 1970-01-01
        • 1970-01-01
        • 2018-02-28
        • 1970-01-01
        相关资源
        最近更新 更多