Получение текста узла DOM с помощью Puppeteer и headless Chrome

Я пытаюсь использовать headless 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?

3 ответов


я нашел три варианта решения этой проблемы, в зависимости от сложности добычи. Самый простой вариант-связанная функция, которую я не заметил: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)

У меня был успех, используя следующее:

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

надеюсь, что это помогает.