Fermer

mars 11, 2021

Le guide ultime du raclage éthique de sites Web dynamiques avec NodeJS et Puppeteer


Pour de nombreuses tâches de scraping Web, un client HTTP suffit pour extraire les données d'une page. Cependant, lorsqu'il s'agit de sites Web dynamiques, un navigateur sans tête devient parfois indispensable. Dans ce didacticiel, nous allons créer un racleur Web capable de capturer des sites Web dynamiques basés sur NodeJS et Puppeteer.

Commençons par une petite section sur ce que signifie réellement le scraping Web. Nous utilisons tous le web scraping dans notre vie de tous les jours. Il décrit simplement le processus d'extraction d'informations à partir d'un site Web. Par conséquent, si vous copiez et collez une recette de votre plat de nouilles préféré depuis Internet vers votre cahier personnel, vous effectuez un «web scraping». Lors de l'utilisation de ce terme dans l'industrie du logiciel, nous nous référons généralement à l'automatisation de cette tâche manuelle en utilisant un logiciel. Pour reprendre notre exemple précédent de «plat de nouilles», ce processus comporte généralement deux étapes:

  • Récupération de la page
    Nous devons d'abord télécharger la page dans son ensemble. Cette étape revient à ouvrir la page dans votre navigateur Web lors du scraping manuellement.
  • Analyse des données
    Maintenant, nous devons extraire la recette dans le HTML du site Web et la convertir en un format lisible par machine comme JSON ou XML.

Dans le passé, j'ai travaillé pour de nombreuses entreprises en tant que consultant en données. J'ai été étonné de voir combien de tâches d'extraction de données, d'agrégation et d'enrichissement sont encore effectuées manuellement et pourraient être automatisées avec seulement quelques lignes de code. C'est exactement ce que représente pour moi le web scraping: extraire et normaliser des informations précieuses d'un site Web pour alimenter un autre processus commercial générateur de valeur.

Pendant ce temps, j'ai vu des entreprises utiliser le web scraping pour toutes sortes de cas d'utilisation. Les entreprises d'investissement se sont principalement concentrées sur la collecte de données alternatives, telles que des critiques de produits, des informations sur les prix ou des publications sur les réseaux sociaux pour soutenir leurs investissements financiers. Un exemple: un client m'a contacté pour collecter les données d'évaluation des produits pour une liste complète de produits de plusieurs sites Web de commerce électronique, y compris la note, l'emplacement de l'évaluateur et le texte de l'avis pour chaque avis soumis. Les données de résultat ont permis au client d'identifier les tendances concernant la popularité du produit sur différents marchés. Ceci est un excellent exemple de la façon dont une seule information apparemment «inutile» peut devenir précieuse par rapport à une plus grande quantité.

D'autres entreprises accélèrent leur processus de vente en utilisant le web scraping pour générer des leads. Ce processus implique généralement l'extraction d'informations de contact telles que le numéro de téléphone, l'adresse e-mail et le nom du contact pour une liste donnée de sites Web. L'automatisation de cette tâche donne aux équipes commerciales plus de temps pour approcher les prospects. Par conséquent, l'efficacité du processus de vente augmente.

Stick To The Rules

En général, le web scraping de données accessibles au public est légal, comme le confirme la compétence de l'affaire Linkedin vs. HiQ . Cependant, je me suis fixé un ensemble de règles éthiques que j'aime respecter lors du démarrage d'un nouveau projet de web scraping. Cela inclut:

  • Vérification du fichier robots.txt.
    Il contient généralement des informations claires sur les parties du site auxquelles le propriétaire de la page peut accéder et met en évidence les sections qui ne doivent pas être consultées. [19659005] Lecture des termes et conditions.
    Par rapport au robots.txt, cette information n'est pas disponible moins souvent, mais indique généralement comment ils traitent les grattoirs de données.
  • Grattage à vitesse modérée.
    Le grattage crée charge du serveur sur l'infrastructure du site cible. En fonction de ce que vous récupérez et du niveau de concurrence auquel votre scraper fonctionne, le trafic peut entraîner des problèmes pour l’infrastructure serveur du site cible. Bien sûr, la capacité du serveur joue un grand rôle dans cette équation. Par conséquent, la vitesse de mon grattoir est toujours un équilibre entre la quantité de données que je cherche à gratter et la popularité du site cible. Pour trouver cet équilibre, il suffit de répondre à une seule question: "La vitesse prévue va-t-elle modifier de manière significative le trafic organique du site?". Dans les cas où je ne suis pas sûr de la quantité de trafic naturel d'un site, j'utilise des outils comme ahrefs pour avoir une idée approximative.

Sélection de la bonne technologie

En fait, gratter avec un headless Le navigateur est l'une des technologies les moins performantes que vous puissiez utiliser, car elle a un impact important sur votre infrastructure. Un cœur du processeur de votre machine peut gérer approximativement une instance de chrome.

Faisons un rapide exemple de calcul pour voir ce que cela signifie pour un projet de scraping Web réel.

Szenario

  • Vous voulez supprimer 20 000 URL.
  • Le temps de réponse moyen du site cible est de 6 secondes.
  • Votre serveur dispose de 2 cœurs de processeur.

Le projet prendra 16 heures pour se terminer.

Par conséquent, j'essaie toujours d'éviter d'utiliser un navigateur lors d'un test de faisabilité de grattage pour un site Web dynamique.

Voici une petite liste de contrôle que je passe toujours en revue:

☐ Puis-je forcer l'état de la page requis via les paramètres GET dans l'URL? Si oui, nous pouvons simplement exécuter une requête HTTP avec les paramètres ajoutés.

☐ Les informations dynamiques font-elles partie de la source de la page et sont-elles disponibles via un objet javascript quelque part dans le DOM? Si oui, nous pouvons à nouveau utiliser une requête HTTP normale et analyser les données de l'objet stringifié.

☐ Les données sont-elles récupérées via une requête XHR? Si tel est le cas, puis-je accéder directement au point de terminaison avec un client HTTP? Si oui, nous pouvons envoyer une requête HTTP directement au point de terminaison. Souvent, la réponse est même formatée en JSON, ce qui nous facilite beaucoup la vie.

Si toutes les questions sont répondues par un «NON» définitif, nous manquons officiellement d'options possibles pour utiliser un client HTTP. Bien sûr, il peut y avoir plus de réglages spécifiques au site que nous pourrions essayer, mais généralement, le temps nécessaire pour les comprendre est trop long, par rapport aux performances plus lentes d'un navigateur sans tête. La beauté du grattage avec un navigateur est que vous pouvez gratter tout ce qui est soumis à la règle de base suivante:

Si vous pouvez le voir avec un navigateur, vous pouvez le gratter.

Prenons le site suivant comme exemple pour notre grattoir: https://quotes.toscrape.com/search.aspx . Il présente des citations d'une liste d'auteurs donnés pour une liste de sujets. Toutes les données sont récupérées via XHR.

 Site Web avec des données rendues dynamiquement
Exemple de site Web avec des données rendues dynamiquement. ( Grand aperçu )

Quiconque a examiné de près le fonctionnement du site et a parcouru la liste de contrôle ci-dessus s'est probablement rendu compte que les citations pouvaient en fait être récupérées à l'aide d'un client HTTP, car elles peuvent être récupérées en faisant une POST-request sur le point de terminaison de devis directement. Mais puisque ce didacticiel est censé couvrir comment gratter un site Web à l'aide de Puppeteer, nous allons prétendre que c'était impossible.

Installation des prérequis

Puisque nous allons tout construire en utilisant Node.js, ouvrons d'abord un nouveau dossier et créer un nouveau projet de nœud à l'intérieur, en exécutant la commande suivante:

 mkdir js-webscraper
cd js-webscraper
npm init 

Veuillez vous assurer d'avoir préalablement installé le gestionnaire d'empaquetage npm.

L'installateur nous posera quelques questions sur les méta-informations sur ce projet, que nous pouvons tous ignorer en appuyant sur Entrée.

Installer Puppeteer

Nous avons déjà parlé de scraping avec un navigateur. Puppeteer est une API NodeJS qui nous permet de parler à une instance de chrome headless par programmation.

Installons-le en utilisant npm:

 npm install puppeteer 

Building Our Scraper

Maintenant, commençons à construire notre grattoir en création d'un nouveau fichier, appelé scraper.js.

Tout d'abord, nous importons la bibliothèque précédemment installée, Puppeteer:

 const puppeteer = require ('puppeteer'); 

Comme étape suivante, nous demandons à Puppeteer d'ouvrir une nouvelle instance de navigateur dans une fonction asynchrone et auto-exécutable:

 (async function scrape () {
  navigateur const = attendre puppeteer.launch ({headless: false});
  // La logique de raclage arrive ici…
}) (); 

Remarque : Par défaut, le mode sans tête est désactivé, car cela augmente les performances. Cependant, lors de la construction d'un nouveau grattoir, j'aime désactiver le mode sans tête. Cela nous permet de suivre le processus que traverse le navigateur et de voir tout le contenu rendu. Cela nous aidera à déboguer notre script plus tard.

Dans notre instance de navigateur ouverte, nous ouvrons maintenant une nouvelle page et nous dirigeons vers notre URL cible:

 const page = wait browser.newPage ();
wait page.goto ('https://quotes.toscrape.com/search.aspx'); 

Dans le cadre de la fonction asynchrone, nous utiliserons l'instruction await pour attendre la commande suivante à exécuter avant de passer à la ligne de code suivante.

Maintenant que nous avons réussi à ouvrir une fenêtre de navigateur et à naviguer vers la page, nous devons créer l'état du site Web, de sorte que les informations souhaitées deviennent visibles pour le grattage. [19659008] Les rubriques disponibles sont générées dynamiquement pour un auteur sélectionné. Par conséquent, nous allons d'abord sélectionner «Albert Einstein» et attendre la liste de sujets générée. Une fois la liste entièrement générée, nous sélectionnons «apprentissage» comme sujet et le sélectionnons comme deuxième paramètre de formulaire. Nous cliquons ensuite sur soumettre et extrayons les citations récupérées du conteneur qui contient les résultats.

Comme nous allons maintenant convertir cela en logique javascript, faisons d'abord une liste de tous les sélecteurs d'élément dont nous avons parlé dans le précédent paragraphe:

author select-field #author
tag select-field #tag
submit button input [type=”submit”]
quote-container .quote [19659066] Avant de commencer à interagir avec la page, nous nous assurerons que tous les éléments auxquels nous accéderons sont visibles, en ajoutant les lignes suivantes à notre script:
 await page.waitForSelector ('# author');
wait page.waitForSelector ('# tag'); 

Ensuite, nous allons sélectionner des valeurs pour nos deux champs de sélection:

 wait page.select ('select # author', 'Albert Einstein');
wait page.select ('select # tag', 'learning'); 

Nous sommes maintenant prêts à effectuer notre recherche en cliquant sur le bouton «Rechercher» sur la page et en attendant que les guillemets apparaissent:

 attendez la page .click ('. btn');
wait page.waitForSelector ('. quote'); 

Puisque nous allons maintenant accéder à la structure HTML DOM de la page, nous appelons la fonction page.evaluate () fournie, en sélectionnant la conteneur qui contient les guillemets (il n'y en a qu'un dans ce cas). Nous construisons ensuite un objet et définissons null comme valeur de secours pour chaque paramètre d'objet:

 let quotes = wait page.evaluate (() => {
        let quotesElement = document.body.querySelectorAll ('. quote');
  let quotes = Object.values ​​(quotesElement) .map (x => {
              revenir {
                  author: x.querySelector ('. author'). textContent ?? nul,
    quote: x.querySelector ('. content'). textContent ?? nul,
    tag: x.querySelector ('. tag'). textContent ?? nul,
  };
});
 renvoyer des citations;
}); 

Nous pouvons rendre tous les résultats visibles dans notre console en les enregistrant:

 console.log (quotes); 

Enfin, fermons notre navigateur et ajoutons une instruction catch:

 wait browser. close (); 

Le grattoir complet ressemble à ceci:

 const puppeteer = require ('puppeteer');

(fonction async scrape () {
    navigateur const = attendre puppeteer.launch ({headless: false});

    page const = attendre browser.newPage ();
    attendre page.goto ('https://quotes.toscrape.com/search.aspx');

    attendre page.waitForSelector ('# author');
    attendre page.select ('# author', 'Albert Einstein');

    attendre page.waitForSelector ('# tag');
    attendre page.select ('# tag', 'apprentissage');

    attendre page.click ('. btn');
    attendre page.waitForSelector ('. quote');

    // extraire des informations du code
    let quotes = attendre page.evaluate (() => {

        let quotesElement = document.body.querySelectorAll ('. quote');
        let quotes = Object.values ​​(quotesElement) .map (x => {
            revenir {
                author: x.querySelector ('. author'). textContent ?? nul,
                quote: x.querySelector ('. content'). textContent ?? nul,
                tag: x.querySelector ('. tag'). textContent ?? nul,

            }
        });

        renvoyer des citations;

    });

    // enregistrement des résultats
    console.log (guillemets);
    attendre browser.close ();

}) ();

Essayons de faire fonctionner notre grattoir avec:

 node scraper.js 

Et voilà! Le grattoir renvoie notre objet de citation comme prévu:

 résultats de notre grattoir Web
Résultats de notre grattoir Web. ( Grand aperçu )

Optimisations avancées

Notre grattoir de base fonctionne maintenant. Ajoutons quelques améliorations pour le préparer à des tâches de scraping plus sérieuses.

Définition d'un user-agent

Par défaut, Puppeteer utilise un user-agent contenant la chaîne "HeadlessChrome". De nombreux sites Web recherchent ce type de signature et bloquent les demandes entrantes avec une signature comme celle-ci. Pour éviter que cela ne soit une raison potentielle de l'échec du grattoir, j'ai toujours défini un user-agent personnalisé en ajoutant la ligne suivante à notre code:

 await page.setUserAgent ('Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit / 537.36 (KHTML, comme Gecko) Chrome / 88.0.4298.0 Safari / 537.36 '); 

Cela pourrait être encore amélioré en choisissant un user-agent aléatoire avec chaque requête d'un tableau des 5 plus courants agents utilisateurs. Une liste des agents utilisateurs les plus courants peut être trouvée ici .

Implémentation d'un proxy

Puppeteer facilite la connexion à un proxy, car l'adresse proxy peut être transmise au marionnettiste au lancement , comme ci-dessous:

 const browser = wait puppeteer.launch ({
  sans tête: faux,
  args: ['--proxy-server=']
}); 

sslproxies fournit une longue liste de proxies gratuits que vous pouvez utiliser. Alternativement, services proxy rotatifs peuvent être utilisés. Comme les proxys sont généralement partagés entre de nombreux clients (ou utilisateurs gratuits dans ce cas), la connexion devient beaucoup moins fiable qu'elle ne l'est déjà dans des circonstances normales. C'est le moment idéal pour parler de la gestion des erreurs et de la gestion des tentatives.

Gestion des erreurs et des tentatives

De nombreux facteurs peuvent entraîner l'échec de votre grattoir. Par conséquent, il est important de gérer les erreurs et de décider de ce qui doit se passer en cas de panne. Puisque nous avons connecté notre racleur à un proxy et que nous nous attendons à ce que la connexion soit instable (surtout parce que nous utilisons des proxies gratuits), nous voulons réessayer quatre fois avant d'abandonner.

De plus, il ne sert à rien de réessayer une requête avec la même adresse IP si elle a déjà échoué. Par conséquent, nous allons construire un petit système de rotation proxy.

Tout d'abord, nous créons deux nouvelles variables:

 let retry = 0;
let maxRetries = 5; 

Chaque fois que nous exécutons notre fonction scrape () nous augmenterons notre variable de relance de 1. Nous encapsulons ensuite notre logique de grattage complète avec une instruction try and catch afin de pouvoir gérer les erreurs. La gestion des tentatives se produit à l'intérieur de notre fonction catch:

L'instance de navigateur précédente sera fermée, et si notre variable retry est plus petite que notre variable maxRetries la fonction scrape est appelée récursivement.

Notre grattoir ressemblera maintenant à ceci:

 const browser = await puppeteer.launch ({
  sans tête: faux,
  args: ['--proxy-server=' + proxy]
});
essayer {
  page const = attendre browser.newPage ();
  … // notre logique de grattage
} catch (e) {
  console.log (e);
  attendre browser.close ();
  if (réessayer <maxRetries) {
    rayer();
  }
}; 

Maintenant, ajoutons le rotateur de proxy mentionné précédemment:

Créons d'abord un tableau contenant une liste de proxies:

 let proxyList = [
  '202.131.234.142:39330', 
  '45.235.216.112:8080',
  '129.146.249.135:80', 
  '148.251.20.79'
]; 

Maintenant, choisissez une valeur aléatoire dans le tableau:

 var proxy = proxyList [Math.floor(Math.random() * proxyList.length)]; 

Nous pouvons maintenant exécuter le proxy généré dynamiquement avec notre instance Puppeteer:

 const browser = await puppeteer.launch ({
  sans tête: faux,
  args: ['--proxy-server=' + proxy]
}); 

Bien sûr, ce rotateur de proxy pourrait être encore optimisé pour signaler les proxies morts, et ainsi de suite, mais cela irait certainement au-delà de la portée de ce tutoriel.

Ceci est le code de notre grattoir (y compris tous améliorations):

 const marionnettiste = require ('marionnettiste');

// démarrage de Puppeteer

laissez réessayer = 0;
soit maxRetries = 5;

(fonction async scrape () {
    retry ++;

    laissez proxyList = [
        '202.131.234.142:39330',
        '45.235.216.112:8080',
        '129.146.249.135:80',
        '148.251.20.79'
    ];

    var proxy = proxyList [Math.floor(Math.random() * proxyList.length)];

    console.log ('proxy:' + proxy);

    navigateur const = attendre puppeteer.launch ({
        sans tête: faux,
        args: ['--proxy-server=' + proxy]
    });

    essayer {
        page const = attendre browser.newPage ();
        attendre page.setUserAgent ('Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit / 537.36 (KHTML, comme Gecko) Chrome / 88.0.4298.0 Safari / 537.36');

        attendre page.goto ('https://quotes.toscrape.com/search.aspx');

        attendre page.waitForSelector ('select # author');
        attendre page.select ('select # author', 'Albert Einstein');

        attendre page.waitForSelector ('# tag');
        attendre page.select ('select # tag', 'learning');

        attendre page.click ('. btn');
        attendre page.waitForSelector ('. quote');

        // extraire des informations du code
        let quotes = attendre page.evaluate (() => {

            let quotesElement = document.body.querySelectorAll ('. quote');
            let quotes = Object.values ​​(quotesElement) .map (x => {
                revenir {
                    author: x.querySelector ('. author'). textContent ?? nul,
                    quote: x.querySelector ('. content'). textContent ?? nul,
                    tag: x.querySelector ('. tag'). textContent ?? nul,

                }
            });

            renvoyer des citations;

        });

        console.log (guillemets);

        attendre browser.close ();
    } catch (e) {

        attendre browser.close ();

        if (réessayer 

L'exécution du grattoir dans notre terminal renverra les citations, comme ci-dessus.

Le dramaturge comme alternative au marionnettiste

Puppeteer a été développé par Google. Début 2020, Microsoft a publié une alternative appelée Playwright . Microsoft a chassé de nombreux ingénieurs de l'équipe Puppeteer. Par conséquent, Playwright a été développé par de nombreux ingénieurs qui ont déjà mis la main à la pâte sur Puppeteer. En plus d'être le petit nouveau sur le blog, le plus grand point de différenciation de Playwright est le multi-navigateur support, car il prend en charge Chromium, Firefox et WebKit (Safari).

Les tests de performance ( comme celui-ci mené par Checkly) montrent que le marionnettiste offre généralement des performances supérieures d'environ 30% par rapport à Playwright, qui correspond à ma propre expérience.

D'autres différences, comme le fait que vous pouvez exécuter plusieurs appareils avec une seule instance de navigateur, ne sont pas vraiment utiles dans le contexte du scraping Web.

 Smashing Editorial "width =" 35 "height =" 46 "loading =" lazy "decoding =" async (vf, yk, il)




Source link