web-dev-qa-db-fra.com

Obtention de texte de nœud DOM avec Puppeteer et Chrome sans tête

J'essaie d'utiliser Chrome et Puppeteer sans tête pour exécuter nos tests Javascript, mais je ne peux pas extraire les résultats de la page. Basé sur cette réponse , il semble que je devrais utiliser page.evaluate() . Cette section contient même un exemple qui ressemble à ce dont j'ai besoin.

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

À titre d'exemple complet, j'ai essayé de convertir cela en un script qui extraire mon nom de mon profil utilisateur sur Stack Overflow. Notre projet utilise le nœud 6, j'ai donc converti les expressions await pour utiliser .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();
                });
            });
        });
    });
});

Quand je lance ça, j'obtiens cette erreur:

$ 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)

Le problème semble être lié à la sérialisation du paramètre d'entrée sur page.evaluate(). Je peux passer des chaînes et des nombres, mais pas les poignées d'élément. L'exemple est-il faux ou s'agit-il d'un problème avec le noeud 6? Comment puis-je extraire le texte d'un nœud DOM?

8
Don Kirkby

J'ai trouvé trois solutions à ce problème, en fonction de la complexité de votre extraction. L'option la plus simple est une fonction connexe que je n'avais pas remarquée: page.$eval() . Il fait fondamentalement ce que j'essayais de faire: combine page.$() et page.evaluate(). Voici un exemple qui fonctionne:

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

Cela me donne le résultat attendu:

$ node get_user.js 
Don Kirkby top 2% overall

Je voulais extraire quelque chose de plus compliqué, mais j'ai finalement compris que la fonction d'évaluation est exécutée dans le contexte de la page. Cela signifie que vous pouvez utiliser tous les outils chargés dans la page, puis simplement envoyer des chaînes et des nombres dans les deux sens. Dans cet exemple, j'utilise jQuery dans une chaîne pour extraire ce que je veux:

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

Cela me donne un résultat avec les espaces blancs intacts:

$ node get_user.js 

                            Don Kirkby

                                top 2% overall

Dans mon vrai script, je veux extraire le texte de plusieurs nœuds, j'ai donc besoin d'une fonction au lieu d'une simple chaîne:

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

Cela donne exactement le même résultat. Maintenant, je dois ajouter le traitement des erreurs et peut-être réduire les niveaux d'indentation.

9
Don Kirkby

En utilisant await/async et $eval , la syntaxe est la suivante:

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

J'ai eu du succès en utilisant les éléments suivants:

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

J'espère que ça aide.

0
Darren Hall