Web scraping avec Java et Jsoup : guide pratique
Extraire des données web avec Java et Jsoup : sélecteurs CSS, pagination, headers HTTP, gestion des erreurs, rate limiting et respect des conditions d'utilisation.
Jsoup est la bibliothèque Java de référence pour le web scraping HTML. Simple pour les cas basiques, suffisamment flexible pour les cas complexes. Ce guide couvre les patterns essentiels pour extraire des données de manière fiable.
Dépendance Maven
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
Extraction basique
public class ScraperBasique {
public static void main(String[] args) throws IOException {
// Connexion avec headers réalistes
Document doc = Jsoup.connect("https://exemple.com/produits")
.userAgent("Mozilla/5.0 (compatible; MonBot/1.0)")
.header("Accept-Language", "fr-FR,fr;q=0.9")
.timeout(10_000)
.get();
// Sélecteurs CSS — même syntaxe que jQuery
Elements produits = doc.select(".product-card");
for (Element produit : produits) {
String nom = produit.select("h2.product-title").text();
String prix = produit.select("[data-price]").attr("data-price");
String lien = produit.select("a.product-link").attr("abs:href");
// abs:href résout les URLs relatives en absolues automatiquement
System.out.printf("Produit: %s | Prix: %s | URL: %s%n", nom, prix, lien);
}
}
}
Gérer la pagination
La plupart des sites ont plusieurs pages. Deux patterns courants : lien “Page suivante” ou numérotation explicite.
public List<ProduitDto> scrapeAllPages(String baseUrl) throws IOException {
List<ProduitDto> tous = new ArrayList<>();
String url = baseUrl;
while (url != null) {
Document doc = fetchWithRetry(url);
// Extraire les données de la page courante
tous.addAll(extractProduits(doc));
// Trouver le lien vers la page suivante
Element nextLink = doc.selectFirst("a[rel=next], .pagination .next a");
url = nextLink != null ? nextLink.attr("abs:href") : null;
// Rate limiting : pause entre les requêtes
if (url != null) {
Thread.sleep(1000 + (long)(Math.random() * 500));
}
}
return tous;
}
Robustesse avec retry et backoff
private Document fetchWithRetry(String url) throws IOException {
int maxRetries = 3;
int delayMs = 2000;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return Jsoup.connect(url)
.userAgent("Mozilla/5.0 (compatible; MonBot/1.0; +https://monsite.com/bot)")
.timeout(15_000)
.followRedirects(true)
.maxBodySize(5 * 1024 * 1024) // 5 MB max
.get();
} catch (HttpStatusException e) {
if (e.getStatusCode() == 429) {
// Rate limited — attendre plus longtemps
log.warn("Rate limited sur {}, attente {}ms", url, delayMs * attempt);
Thread.sleep(delayMs * attempt);
} else if (e.getStatusCode() >= 500) {
log.warn("Erreur serveur {} sur {}", e.getStatusCode(), url);
if (attempt == maxRetries) throw e;
Thread.sleep(delayMs);
} else {
throw e; // 404, 403 etc. → pas de retry
}
} catch (SocketTimeoutException e) {
log.warn("Timeout sur {}, tentative {}/{}", url, attempt, maxRetries);
if (attempt == maxRetries) throw e;
Thread.sleep(delayMs);
}
}
throw new IOException("Échec après " + maxRetries + " tentatives : " + url);
}
Sélecteurs CSS essentiels
// Par classe CSS
doc.select(".ma-classe")
// Par attribut
doc.select("[data-id]") // a cet attribut
doc.select("[data-type=produit]") // attribut avec valeur exacte
doc.select("[href^=https]") // href commence par https
// Combinaisons
doc.select("article.product h2") // h2 dans .product dans article
doc.select("ul.list > li") // li enfant direct de ul.list
doc.select("li:first-child") // premier élément
doc.select("li:not(.disabled)") // li sans la classe disabled
doc.select("li:nth-child(3n)") // un sur trois
// Texte et attributs
element.text() // texte visible (sans HTML)
element.html() // HTML interne
element.attr("href") // valeur d'un attribut
element.attr("abs:href") // URL absolue résolue
element.hasClass("active") // boolean
element.ownText() // texte direct (sans les enfants)
Traiter du JavaScript rendu côté client
Jsoup ne peut pas exécuter JavaScript. Si le contenu est chargé dynamiquement (React, Angular, lazy loading), Jsoup récupère une page vide ou partielle.
Solutions :
1. Chercher l’API JSON : beaucoup de sites chargent leurs données via une API REST en arrière-plan. Ouvrez Chrome DevTools > Réseau > XHR/Fetch, rechargez la page et identifiez les appels API. Appelez directement l’API JSON — plus fiable que scraper le HTML.
2. Playwright ou Selenium : pour les sites vraiment incontournables qui nécessitent l’exécution JavaScript.
// Avec Playwright Java
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(true));
Page page = browser.newPage();
page.navigate("https://exemple.com");
page.waitForSelector(".product-card");
String html = page.content();
Document doc = Jsoup.parse(html);
// Extraire normalement avec Jsoup
}
Exporter les données
// CSV avec OpenCSV
try (CSVWriter writer = new CSVWriter(new FileWriter("produits.csv"))) {
writer.writeNext(new String[]{"Nom", "Prix", "URL"});
for (ProduitDto p : produits) {
writer.writeNext(new String[]{p.nom(), p.prix(), p.url()});
}
}
// JSON avec Jackson
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(new File("produits.json"), produits);
Règles à respecter
robots.txt : vérifiez toujours https://site.com/robots.txt avant de scraper. Respectez les chemins interdits.
CGU : certains sites interdisent explicitement le scraping dans leurs conditions. Vérifiez avant de déployer en production.
Rate limiting : espacez vos requêtes (au minimum 1 seconde entre chaque). Un scraper agressif est bloqué ou provoque des surcharges.
User-Agent honnête : identifiez votre bot avec un User-Agent qui inclut un lien vers votre site de contact.
Conclusion
Jsoup est l’outil idéal pour le scraping HTML Java : API claire, sélecteurs CSS expressifs, gestion HTTP intégrée. La fiabilité vient de la gestion des erreurs et du rate limiting. Pour les sites JavaScript-heavy, Playwright est le complément naturel.
Amine MEGDICHE
Développeur AEM & Java Full Stack — Freelance depuis 2013