L’extraction de données web, ou « web scraping », est une technique permettant de récupérer des informations spécifiques à partir d’un site internet. Les pages web sont structurées à l’aide du langage HTML. Un code HTML clair et bien structuré facilite grandement l’identification et l’extraction des données pertinentes.
Un extracteur web est souvent utilisé pour collecter et suivre des informations, ainsi que pour observer les modifications qui y sont apportées au fil du temps.
Notions jQuery essentielles pour l’utilisation de Cheerio
jQuery est une bibliothèque JavaScript très répandue, qui simplifie la manipulation du Document Object Model (DOM), la gestion d’événements, la création d’animations, et bien plus encore. Cheerio, un outil de « web scraping », se base sur jQuery, en adoptant sa syntaxe et son API, tout en facilitant l’analyse de documents HTML ou XML.
Avant de commencer à utiliser Cheerio, il est crucial de comprendre comment sélectionner des éléments HTML avec jQuery. Heureusement, jQuery est compatible avec la plupart des sélecteurs CSS3, ce qui rend l’identification des éléments DOM très aisée. Prenons l’exemple de ce code:
$("#container");
Ici, jQuery sélectionne l’élément ayant l’identifiant « container ». La même opération avec du JavaScript standard serait plus complexe :
document.querySelectorAll("#container");
La comparaison de ces deux extraits révèle que le premier est beaucoup plus lisible. C’est l’un des avantages majeurs de jQuery.
jQuery met aussi à disposition des fonctions très pratiques comme text(), html(), etc., pour manipuler les éléments HTML. Plusieurs méthodes permettent de naviguer à travers le DOM, comme parent(), siblings(), prev() et next().
La méthode each() de jQuery est particulièrement populaire dans de nombreux projets Cheerio. Elle permet d’itérer sur des tableaux ou des objets. Sa syntaxe est la suivante :
$(<element>).each(<array or object>, callback)
Le « callback » est exécuté pour chaque élément du tableau ou de l’objet spécifié.
Chargement de code HTML avec Cheerio
Pour analyser des données HTML ou XML avec Cheerio, on utilise la fonction cheerio.load(). Voici un exemple d’utilisation:
const $ = cheerio.load('<html><body><h1>Bonjour le monde!</h1></body></html>');
console.log($('h1').text())
Ce code utilise la fonction text() de jQuery pour extraire le contenu textuel de l’élément h1. La syntaxe complète de la fonction load() est la suivante:
load(content, options, mode)
Le paramètre « content » représente les données HTML ou XML à analyser. Le paramètre « options » est un objet optionnel qui permet de modifier le comportement de la fonction. Par défaut, la fonction load() ajoute les éléments html, head et body s’ils sont absents. Pour désactiver ce comportement, il faut définir « mode » sur « false ».
Extraction de données de Hacker News avec Cheerio
Le code source de ce projet est accessible sur un dépôt GitHub et peut être utilisé librement sous licence MIT.
Il est maintenant temps de mettre en pratique ce qui a été appris en créant un « scraper » simple. Hacker News est un site web populaire auprès des entrepreneurs et des innovateurs. C’est également un excellent terrain d’entraînement pour le « web scraping », car le site est rapide, doté d’une interface simple et ne contient aucune publicité.
Assurez-vous que Node.js et son gestionnaire de paquets sont installés sur votre machine. Créez un dossier vide, puis un fichier package.json, et copiez le JSON suivant dans ce fichier :
{
"name": "web-scraper",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"cheerio": "^1.0.0-rc.12",
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Ensuite, ouvrez le terminal et tapez :
npm i
Cela installera les paquets nécessaires pour le « scraper ». Ces paquets incluent Cheerio pour analyser le HTML, ExpressJS pour créer un serveur, et Nodemon, un outil de développement qui surveille les changements dans le projet et redémarre automatiquement le serveur.
Mise en place et création des fonctions nécessaires
Créez un fichier index.js et, dans ce fichier, déclarez une variable constante appelée « PORT ». Attribuez à PORT la valeur 5500 (ou le port de votre choix), puis importez les paquets Cheerio et Express.
const PORT = 5500;
const cheerio = require("cheerio");
const express = require("express");
const app = express();
Déclarez ensuite trois variables: url, html et finishPage. Définissez l’URL comme celle de Hacker News.
const url="https://news.ycombinator.com";
let html;
let finishedPage;
Créez une fonction appelée getHeader() qui renvoie du code HTML qui sera affiché dans le navigateur.
function getHeader(){
return `
<div style="display:flex; flex-direction:column; align-items:center;">
<h2 style="text-transform:capitalize">Scraper d'Actualités</h2>
<div style="display:flex; gap:10px; align-items:center;">
<a href="https://www.makeuseof.com/" id="news" onClick='showLoading()'>Accueil</a>
<a href="https://wilku.top/best" id="best" onClick='showLoading()'>Meilleurs</a>
<a href="https://wilku.top/newest" id="newest" onClick='showLoading()'>Nouveaux</a>
<a href="https://wilku.top/ask" id="ask" onClick='showLoading()'>Questions</a>
<a href="https://wilku.top/jobs" id="jobs" onClick='showLoading()'>Emplois</a>
</div>
<p class="loading" style="display:none;">Chargement...</p>
</div>
`}
Créez également une fonction getScript() qui renvoie du code JavaScript pour le navigateur. N’oubliez pas de passer le type de variable en argument lors de son appel.
function getScript(type){
return `
<script>
document.title = "${type.substring(1)}"window.addEventListener("DOMContentLoaded", (e) => {
let navLinks = [...document.querySelectorAll("a")];
let current = document.querySelector("#${type.substring(1)}");
document.body.style = "margin:0 auto; max-width:600px;";
navLinks.forEach(x => x.style = "color:black; text-decoration:none;");
current.style.textDecoration = "underline";
current.style.color = "black";
current.style.padding = "3px";
current.style.pointerEvents = "none";
})function showLoading(e){
document.querySelector(".loading").style.display = "block";
document.querySelector(".loading").style.textAlign = "center";
}
</script>`
}
Enfin, créez une fonction asynchrone appelée fetchAndRenderPage(). Cette fonction effectue une tâche simple : elle récupère une page de Hacker News, l’analyse et la formate avec Cheerio, puis renvoie le code HTML au client pour l’affichage.
async function fetchAndRenderPage(type, res) {
const response = await fetch(`${url}${type}`)
html = await response.text();
}
Sur Hacker News, il existe différents types de publications. Il y a les « actualités », c’est-à-dire les publications de la page d’accueil. Les questions des utilisateurs sont étiquetées « ask ». Les publications les plus appréciées sont regroupées sous l’étiquette « meilleur », les plus récentes sous « nouveaux » et les offres d’emploi sous « emplois ».
La fonction fetchAndRenderPage() extrait la liste des publications en fonction du type transmis en argument. Si l’extraction réussit, la variable html est mise à jour avec le texte de la réponse.
Ajoutez les lignes de code suivantes à la fonction:
res.set('Content-Type', 'text/html');
res.write(getHeader());const $ = cheerio.load(html);
const articles = [];
let i = 1;
Dans ce bloc de code, la fonction set() définit l’en-tête HTTP. La fonction write() envoie une partie du corps de la réponse. La fonction load() prend html en argument.
Ajoutez ensuite le code suivant pour sélectionner les éléments enfants de tous les éléments avec la classe « titleline » :
$('.titleline').children('a').each(function(){
let title = $(this).text();
articles.push(`<h4>${i}. ${title}</h4>`);
i++;
})
Dans ce bloc, à chaque itération, le contenu textuel de l’élément HTML ciblé est extrait et stocké dans la variable « title ».
Ensuite, ajoutez la réponse de la fonction getScript() dans le tableau « articles ». Créez ensuite une variable, finishPage, qui contiendra le code HTML final à envoyer au navigateur. Enfin, utilisez la fonction write() pour envoyer la page finie sous forme de bloc et terminez la réponse avec la fonction end().
articles.push(getScript(type))
finishedPage = articles.reduce((c, n) => c + n);
res.write(finishedPage);
res.end();
Définition des routes pour gérer les requêtes GET
Juste après la fonction fetchAndRenderPage, utilisez la méthode get() d’Express pour définir les routes pour les différents types de publications. Puis utilisez la méthode listen pour écouter les connexions sur le port spécifié de votre réseau local.
app.get("https://www.makeuseof.com/", (req, res) => {
fetchAndRenderPage('/news', res);
})app.get("https://wilku.top/best", (req, res) => {
fetchAndRenderPage("https://wilku.top/best", res);
})app.get("https://wilku.top/newest", (req, res) => {
fetchAndRenderPage("https://wilku.top/newest", res);
})app.get("https://wilku.top/ask", (req, res) => {
fetchAndRenderPage("https://wilku.top/ask", res);
})app.get("https://wilku.top/jobs", (req, res) => {
fetchAndRenderPage("https://wilku.top/jobs", res);
})app.listen(PORT)
Dans ce bloc, chaque fonction get possède une fonction de rappel qui appelle la fonction fetchAndRenderPage en transmettant le type et l’objet res.
Quand vous ouvrez votre terminal et exécutez npm run start, le serveur démarre. Vous pouvez alors accéder à localhost:5500 dans votre navigateur pour voir les résultats.
Félicitations, vous avez réussi à « scraper » Hacker News et à récupérer les titres des publications sans utiliser d’API externe !
Aller plus loin avec l’extraction de données web
Avec les données récupérées de Hacker News, vous pouvez créer des visualisations comme des tableaux, des graphiques ou des nuages de mots pour présenter les informations et les tendances de manière plus accessible.
Vous pouvez également extraire les profils utilisateurs pour analyser leur réputation en fonction de facteurs comme les votes positifs reçus, les commentaires laissés, etc.