Le web scraping permet de transformer des pages web en jeux de données exploitables. Bien mené, il devient un outil de veille, d'analyse et d'automatisation particulièrement utile pour suivre des prix, constituer des bases produits, enrichir des études ou alimenter des tableaux de bord. Encore faut-il savoir récupérer l'information proprement, sans surcharger les sites ni produire des données fragiles.
Le web scraping consiste à récupérer automatiquement des données présentes sur des pages web pour les transformer en informations structurées. Dans les faits, il s'agit souvent de télécharger le code HTML d'une page, d'identifier les éléments utiles, puis de les extraire dans un format plus simple à manipuler, comme un tableau CSV, un fichier JSON ou une base SQLite.
Cette pratique ne relève pas seulement de la programmation. Elle touche aussi à l'observation du web, à la qualité des données, à la logique métier et au respect des conditions d'usage du site exploré. Un bon script n'est pas uniquement un script qui fonctionne une fois. C'est un script qui résiste aux variations mineures de la page, qui échoue proprement lorsqu'un élément manque, et qui limite son impact sur le serveur distant.
Les cas d'usage sont nombreux : veille tarifaire, suivi de publications, récupération d'annonces, constitution d'un corpus textuel, analyse concurrentielle, enrichissement d'un catalogue, surveillance d'indicateurs publics, ou encore collecte de données pour entraîner ou tester des modèles. Il existe toutefois une différence importante entre consulter une page pour un usage humain et en extraire massivement les données pour un usage automatisé. Cette différence impose de la méthode.
Avant même d'écrire une ligne de Python, il convient d'examiner trois dimensions : la structure de la page, l'origine réelle des données et le cadre d'usage. De nombreuses pages affichent du contenu qui semble statique alors qu'il est chargé via une API en arrière-plan. Dans ce cas, parser le HTML n'est pas toujours la meilleure option. Il faut parfois inspecter les requêtes réseau et cibler directement l'endpoint qui fournit les données.
Une autre étape souvent négligée consiste à lire le fichier robots.txt du site, à consulter ses conditions d'utilisation et à vérifier si le contenu est destiné à être réexploité. Tout n'est pas automatiquement libre parce que tout est visible dans un navigateur. Il faut aussi éviter de collecter des données personnelles sans base claire et limiter la fréquence des requêtes.
La préparation méthodologique évite la majorité des scripts jetables. Un scraping efficace n'est pas celui qui enchaîne le plus de requêtes, mais celui qui comprend où se trouvent réellement les données. Une page produit, par exemple, peut afficher un titre, un prix, une disponibilité, des variantes et des avis. Mais ces informations peuvent venir de plusieurs sources différentes. Le HTML initial ne raconte pas toujours toute l'histoire.
Pour démarrer, un environnement léger suffit. Les bibliothèques les plus utiles pour un scraping classique sont requests pour envoyer les requêtes HTTP, beautifulsoup4 pour analyser le HTML et, selon les besoins, lxml pour accélérer le parsing. Pour les pages fortement dépendantes du JavaScript, playwright devient une option pertinente. Quand le périmètre s'élargit à des centaines ou des milliers de pages, scrapy offre un cadre plus robuste.
python -m venv .venv
source .venv/bin/activate ou .venv\Scripts\activate sous Windows
pip install requests beautifulsoup4 lxml pandas
Ajouter pandas n'est pas obligatoire, mais cette bibliothèque simplifie l'export vers CSV et le contrôle rapide des données collectées. Pour un projet plus sérieux, il est également conseillé de figer les dépendances dans un fichier requirements.txt afin de garantir la reproductibilité.
Le scénario le plus simple consiste à récupérer une page HTML puis à sélectionner les balises qui contiennent les données visées. Imaginons un site listant des articles avec un titre, une URL et une date. Le script ci-dessous montre la logique minimale.
import requests
from bs4 import BeautifulSoup
url = "https://example.com/articles"
headers = {
"User-Agent": "Mozilla/5.0 (compatible; DataResearchBot/1.0)"
}
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
soup = BeautifulSoup(response.text, "lxml")
articles = []
for card in soup.select("article.post"):
title_tag = card.select_one("h2 a")
date_tag = card.select_one("time")
title = title_tag.get_text(strip=True) if title_tag else None
link = title_tag["href"] if title_tag and title_tag.has_attr("href") else None
date = date_tag.get_text(strip=True) if date_tag else None
articles.append({
"title": title,
"link": link,
"date": date
})
print(articles)
Trois éléments méritent déjà l'attention. D'abord, raise_for_status() interrompt le script si le serveur répond avec une erreur HTTP. Ensuite, l'usage de select et select_one permet de cibler les éléments avec des sélecteurs CSS, souvent plus lisibles que d'autres approches. Enfin, les tests conditionnels évitent les erreurs quand un bloc HTML ne contient pas exactement la structure attendue.
Dans un vrai projet, il est préférable de séparer les responsabilités : une fonction pour télécharger la page, une autre pour parser le HTML, une troisième pour nettoyer les champs et une dernière pour exporter les données. Cette séparation rend les tests plus faciles et limite les effets de bord lors des modifications.
Extraire une chaîne de caractères n'est que le début. Les données issues du web sont souvent hétérogènes : espaces multiples, caractères invisibles, devises mélangées, dates sous plusieurs formats, URLs relatives, variantes textuelles d'une même catégorie. Sans nettoyage, l'analyse qui suit devient approximative.
Prenons l'exemple d'un prix affiché sous la forme "1 299,00 €". Pour l'exploiter correctement, il faut le convertir en valeur numérique, standardiser la devise et éventuellement conserver la chaîne d'origine pour audit. Même logique pour les dates. Un site peut présenter 12 mars 2026, 2026-03-12 ou 12/03/2026. L'idéal est de ramener ces formats vers une représentation unique.
from urllib.parse import urljoin
from datetime import datetime
base_url = "https://example.com"
def clean_price(raw):
if not raw:
return None
raw = raw.replace("€", "").replace(" ", "").replace(",", ".")
return float(raw)
def clean_link(href):
if not href:
return None
return urljoin(base_url, href)
def clean_date(raw):
if not raw:
return None
return datetime.strptime(raw, "%d/%m/%Y").date().isoformat()
Structurer les données signifie aussi leur attribuer des noms de colonnes cohérents et durables. Une bonne table évite les intitulés flous comme info1 ou div_text. Il faut privilégier des champs explicites : product_name, price_eur, published_at, category, source_url, scraped_at. Le champ scraped_at est précieux : il permet de savoir quand la donnée a été collectée et de reconstituer un historique.
L'efficacité en scraping n'a rien à voir avec la brutalité. Faire cinquante requêtes par seconde sur un petit site n'a rien d'impressionnant ; c'est surtout une manière rapide d'être bloqué. Une stratégie efficace repose sur quelques principes simples : réutiliser une session HTTP, limiter le nombre de pages utiles, temporiser entre les requêtes, prévoir des tentatives de reprise et mettre en cache ce qui peut l'être.
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retries = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"]
)
session.mount("http://", HTTPAdapter(max_retries=retries))
session.mount("https://", HTTPAdapter(max_retries=retries))
headers = {
"User-Agent": "Mozilla/5.0 (compatible; DataResearchBot/1.0)"
}
for url in urls:
response = session.get(url, headers=headers, timeout=20)
response.raise_for_status()
# traitement ici
time.sleep(1.5)
La session conserve certaines informations de connexion et évite de repartir de zéro à chaque requête. Le mécanisme de retry gère mieux les erreurs temporaires, notamment les codes 429 ou 503. Quant à la pause explicite, elle réduit la pression sur le serveur et diminue les risques de blocage.
Une autre dimension de l'efficacité concerne le volume réellement nécessaire. Si l'objectif est d'obtenir les articles publiés cette semaine, il est inutile de parcourir les archives sur dix ans. Si une API fournit les vingt derniers éléments triés par date, il est absurde de parser des dizaines de pages HTML pour reconstituer le même résultat. L'efficacité technique commence souvent par la réduction du périmètre.
De nombreux débutants pensent que le web scraping échoue parce que BeautifulSoup serait limité. En réalité, l'échec vient souvent du fait que le HTML récupéré avec requests ne contient pas encore les données affichées par le navigateur. La page s'appuie sur du JavaScript pour aller chercher les informations après le chargement initial. Dans ce cas, deux approches sont possibles.
C'est généralement la meilleure option. En inspectant l'onglet Réseau du navigateur, il est fréquent de repérer une requête fetch ou XHR qui renvoie directement du JSON. Si cette ressource est accessible, il devient beaucoup plus simple de récupérer les données de manière structurée.
Lorsque les données sont injectées dans le DOM après exécution du JavaScript, Playwright peut piloter un navigateur réel, attendre que les éléments apparaissent puis extraire le contenu final.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com/dashboard", wait_until="networkidle")
page.wait_for_selector(".result-card")
cards = page.query_selector_all(".result-card")
results = []
for card in cards:
title = card.query_selector("h2")
results.append({
"title": title.inner_text().strip() if title else None
})
browser.close()
Cette solution est plus lourde, plus lente et plus coûteuse que l'analyse d'un JSON renvoyé par une API. C'est pourquoi Playwright ne doit pas être le réflexe initial. Il faut d'abord vérifier si les données ne sont pas déjà disponibles d'une manière plus simple.
requests, il ne faut pas conclure trop vite que le scraping est impossible. Il faut vérifier si la donnée est chargée par une API, injectée par JavaScript ou protégée par une logique d'authentification.La sortie du scraping doit être pensée dès le départ. Le format dépend de l'usage final. Le CSV reste pratique pour des tableaux simples et une ouverture rapide dans un tableur. Le JSON convient mieux à des structures imbriquées. SQLite, quant à lui, offre un compromis très intéressant pour historiser les données sans déployer une base de données plus lourde.
import csv
import json
import sqlite3
# CSV
with open("articles.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["title", "link", "date"])
writer.writeheader()
writer.writerows(articles)
# JSON
with open("articles.json", "w", encoding="utf-8") as f:
json.dump(articles, f, ensure_ascii=False, indent=2)
# SQLite
conn = sqlite3.connect("articles.db")
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS articles (
title TEXT,
link TEXT,
date TEXT
)
""")
cur.executemany(
"INSERT INTO articles (title, link, date) VALUES (:title, :link, :date)",
articles
)
conn.commit()
conn.close()
Dans un contexte analytique, SQLite présente un intérêt souvent sous-estimé. Il devient possible de rejouer des requêtes SQL, de dédupliquer, de filtrer par date de collecte et de comparer les changements d'un jour à l'autre. Pour des projets de veille ou de benchmark, cet historique compte souvent autant que la donnée elle-même.
Tous les projets de scraping ne demandent pas la même architecture. Un script court peut suffire pour un site simple. À l'inverse, un projet de collecte régulier, multi-pages, avec pagination, gestion d'erreurs et pipeline de données bénéficiera d'un framework plus complet. Le tableau suivant résume les principaux choix.
| Critère | Requests + BeautifulSoup | Scrapy / Playwright |
|---|---|---|
| Prise en main | Très simple pour démarrer | Plus structurée, apprentissage plus long |
| Pages statiques | Excellente option recommandé | Utile mais parfois surdimensionné |
| Grand volume de pages | Possible, mais demande plus d'organisation | Très adapté avec pipelines et middlewares avantage |
| Pages JavaScript | Souvent insuffisant | Playwright mieux adapté avantage |
| Débogage | Rapide et lisible | Plus riche, mais plus technique |
| Scalabilité | Bonne pour de petits projets | Supérieure pour les collectes récurrentes avantage |
| Choix idéal | Prototype, tutoriel, extraction ciblée | Projet industriel, site dynamique, pipeline robuste |
La première erreur consiste à scraper sans inspecter le HTML réel renvoyé par le serveur. Beaucoup de scripts échouent parce qu'ils ciblent ce que le navigateur affiche visuellement, sans vérifier ce que requests reçoit effectivement. Il faut presque toujours commencer par sauvegarder une réponse dans un fichier local et examiner sa structure.
La deuxième erreur est d'écrire des sélecteurs trop fragiles. Une classe CSS générée automatiquement ou un chemin DOM très précis peuvent disparaître au moindre redesign. Il vaut mieux rechercher une ancre robuste : une balise, un attribut data-*, une section identifiable, ou une combinaison simple de sélecteurs.
La troisième erreur est d'ignorer les cas limites. Une carte peut ne pas avoir de prix, un lien peut être relatif, une date peut manquer, une page peut répondre 403, 404 ou 429. Un script sérieux n'échoue pas silencieusement. Il journalise les problèmes, conserve éventuellement les URLs en erreur et permet une reprise partielle.
Un scraping fiable repose sur un enchaînement assez sobre : observer la page, comprendre la source des données, écrire des sélecteurs stables, gérer les erreurs, ralentir volontairement le rythme et stocker les résultats dans un format exploitable.
requests, puis observer l'onglet Réseau des outils de développement. Si les données arrivent dans une requête XHR ou fetch, la page s'appuie probablement sur une source externe appelée après le chargement initial.Le web scraping avec Python reste l'une des portes d'entrée les plus concrètes vers l'automatisation de la collecte de données. La technique paraît simple au départ : envoyer une requête, récupérer une page, lire son HTML. Pourtant, l'efficacité réelle vient d'autre chose. Elle vient de la précision avec laquelle le besoin est défini, de la compréhension de la page, du soin apporté au nettoyage, et d'une certaine discipline dans la manière d'interroger les serveurs.
Commencer avec requests et BeautifulSoup est souvent la meilleure approche. Cela permet de comprendre le cycle complet, depuis la requête HTTP jusqu'au stockage. Ensuite, selon l'évolution du projet, il devient possible d'introduire Scrapy pour structurer la collecte, ou Playwright pour traiter des interfaces plus dynamiques. Le plus important, au fond, n'est pas d'utiliser l'outil le plus impressionnant, mais celui qui correspond à la réalité de la page ciblée et au volume de données à traiter.
Un bon scraper ne se reconnaît pas à la longueur de son code. Il se reconnaît à la stabilité de ses résultats, à la propreté de ses données et à la modération de son comportement. C'est là que l'extraction devient réellement efficace.