使用木偶和无头铬的dom节点文本



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

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

作为一个完整的示例,我尝试将其转换为脚本,该脚本将从堆栈溢出上的用户配置文件中提取我的名字。我们的项目使用节点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()。我可以传递字符串和数字,但不能传递元素手柄。示例是错误的,还是节点6的问题?如何提取DOM节点的文本?

我找到了三个解决方案,具体取决于您的提取的复杂性。最简单的选项是我没有注意到的相关功能: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();
            });
        });
    });
});

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

使用 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)

i使用页面。$ eval

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

我使用以下内容成功:

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

希望它有帮助。

最新更新