La plupart des données sur internet résident dans du HTML non structuré, dispersé sur des pages conçues pour les yeux humains plutôt que pour vos scripts. Le web scraping vous permet de les transformer en enregistrements structurés que vous pouvez sauvegarder, interroger et analyser, sans copier-coller manuellement ni attendre qu'un site propose une API qu'il ne construira peut-être jamais. Le suivi des prix, la veille concurrentielle, les listes de prospects et la collecte de données d'entraînement commencent tous de la même façon : récupérer une page, l'analyser, et stocker les champs qui vous intéressent.

Ce guide parcourt l'intégralité du flux de travail Python de bout en bout. Vous configurerez la boîte à outils, enverrez une requête, analyserez le HTML avec des sélecteurs CSS, gérerez les pages rendues par JavaScript, suivrez la pagination, stockerez les résultats en CSV ou JSON, et ferez face aux blocages qui surviennent dès que vous passez à l'échelle. Chaque extrait de code est réel et prêt à être copié, et l'ensemble du tutoriel reste limité aux données publiques d'un site de pratique conçu pour l'apprentissage.

Ce que vous allez construire

Un petit scraper Python complet qui lit une liste paginée de livres sur un site de pratique public, extrait un enregistrement propre de chacun d'eux, parcourt toutes les pages jusqu'à la fin, et écrit tout sur le disque. Ce schéma, récupérer puis analyser puis boucler puis stocker, constitue la colonne vertébrale de presque tous les scrapers que vous écrirez.

  • Titre. Le nom du produit sur chaque carte de listing.
  • Prix. La chaîne de prix affichée, prête à convertir en nombre.
  • Disponibilité. Si l'article est en stock.
  • Note. La note en étoiles attachée à chaque carte.
  • URL. Le lien absolu vers la page de détail.

Nous ciblons books.toscrape.com, un bac à sable conçu spécifiquement pour pratiquer le scraping. Il est statique, bien structuré et librement utilisable, ce qui vous permet de vous concentrer sur la technique sans avoir à combattre les blocages dès votre première tentative.

Comment fonctionne le web scraping

Un scraper n'est qu'un client HTTP associé à un analyseur syntaxique. Le client demande une URL et le serveur retourne du HTML ; l'analyseur charge ce HTML dans un arbre que vous pouvez interroger par balise, classe ou sélecteur CSS, et vous copiez les valeurs souhaitées dans une liste d'enregistrements. Les moteurs de recherche fonctionnent ainsi depuis les premiers crawlers de 1993, et la mécanique n'a guère changé : découvrir des URLs, récupérer chacune d'elles, extraire des champs structurés, et passer à la suite.

Ce qui a changé, c'est le web moderne. De nombreux sites envoient aujourd'hui un shell HTML quasi vide et rendent le contenu visible dans le navigateur avec JavaScript, et la plupart des cibles sérieuses se défendent contre le trafic automatisé. Ces deux réalités, le rendu côté client et la défense anti-bot, expliquent pourquoi un guide « complet » ne peut pas s'arrêter à requests et BeautifulSoup. Nous commencerons par la pile simple car elle enseigne les fondamentaux, puis nous montrerons où elle atteint ses limites et ce qui la remplace.

Configurer la boîte à outils Python

L'écosystème Python de scraping est vaste, mais une poignée d'outils suffit pour couvrir presque tous les cas. Voici la boîte à outils moderne et le moment où chaque élément mérite sa place.

  • requests envoie des requêtes HTTP et retourne la réponse. C'est la valeur par défaut appropriée pour les pages statiques.
  • BeautifulSoup analyse le HTML en un arbre navigable et tolère le balisage désordonné des vraies pages.
  • lxml est un analyseur rapide que BeautifulSoup peut utiliser comme backend, et il apporte la prise en charge complète de XPath quand vous en avez besoin.
  • Selenium ou Playwright pilotent un vrai navigateur pour pouvoir rendre JavaScript et interagir avec une page en cliquant et en tapant.
  • Scrapy est un framework de crawling complet avec concurrence intégrée, nouvelles tentatives et pipelines, pour quand un script grossit jusqu'à devenir un vrai projet.

Pour un panorama plus large de ce qui est disponible, voir les meilleures bibliothèques Python de web scraping. Pour ce tutoriel, commencez par un environnement virtuel propre et les deux bibliothèques qui font l'essentiel du travail.

bash
python --version

python -m venv scraper_env
source scraper_env/bin/activate

pip install requests beautifulsoup4 lxml

Sur Windows, activez l'environnement avec scraper_env\Scripts\activate au lieu de la ligne source. Vous avez besoin de Python 3.8 ou supérieur ; vérifiez avec python --version et installez depuis python.org si nécessaire. L'environnement actif, vous êtes prêt à envoyer votre première requête.

Étape 1 : Envoyer une requête et lire la réponse

Chaque scrape commence par une requête HTTP. Envoyez un GET vers l'URL, confirmez que le code de statut est 200 avant de faire quoi que ce soit d'autre, et le HTML de la page est entre vos mains.

python
import requests

url = "https://books.toscrape.com/catalogue/page-1.html"
headers = {"User-Agent": "Mozilla/5.0 (scraper tutorial)"}

response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
    print(response.text[:500])
else:
    print(f"Request failed: {response.status_code}")

Deux petites habitudes portent leurs fruits immédiatement. Un en-tête User-Agent fait ressembler votre requête à celle d'un navigateur plutôt qu'à un script anonyme, ce que de nombreux sites préfèrent. Un timeout empêche le scraper de rester bloqué indéfiniment lorsqu'un serveur répond lentement. Exécutez ceci et vous devriez voir les 500 premiers caractères du HTML réel s'afficher dans votre terminal, ce qui confirme que la récupération fonctionne avant d'écrire un seul sélecteur.

Crawlbase Crawling API

Ce simple requests.get fonctionne sur une page de pratique statique, mais sur une vraie cible il échoue face au JavaScript et se fait bloquer à grande échelle. La Crawling API prend la même URL, rend la page dans un vrai navigateur derrière une IP résidentielle rotative, et retourne le HTML fini, de sorte que le code d'analyse des étapes suivantes reste identique et vous évite de gérer vous-même une flotte de navigateurs headless et un pool de proxies.

Étape 2 : Analyser le HTML avec des sélecteurs

Le HTML brut n'est qu'une chaîne de caractères. Pour sélectionner des éléments, vous le chargez dans BeautifulSoup, qui transforme le balisage en un arbre interrogeable par nom de balise et classe CSS. Ouvrez la page dans votre navigateur, cliquez droit sur une carte de livre et choisissez Inspecter pour lire la structure : sur ce site, chaque livre se trouve dans un article.product_pod, avec le titre dans l'attribut title de h3 a, le prix dans p.price_color, la disponibilité dans p.instock, et la note encodée comme classe sur p.star-rating.

python
from bs4 import BeautifulSoup

soup = BeautifulSoup(response.text, "lxml")
books = soup.select("article.product_pod")

print(f"Found {len(books)} books on this page")

L'argument "lxml" indique à BeautifulSoup d'analyser avec le backend lxml rapide que vous avez installé ; si vous sautez l'installation, passez "html.parser" à la place, qui est inclus avec Python. La méthode select prend un sélecteur CSS et retourne toutes les correspondances sous forme de liste, donc article.product_pod vous donne les vingt cartes de livres de la page. Si vous préférez find et find_all, ils font le même travail avec un style d'appel de méthode. Pour un tour plus complet des deux styles, voir comment utiliser BeautifulSoup en Python, et pour la différence entre sélecteurs CSS et XPath, voir web scraping avec XPath et sélecteurs CSS.

Étape 3 : Extraire des champs propres

Extrayez maintenant les données de chaque carte. Parcourez les éléments en boucle, lisez la valeur de chaque enfant, et collectez un dictionnaire ordonné par livre. Envelopper les sélecteurs dans un petit helper évite qu'un champ manquant fasse planter toute la récupération.

python
from urllib.parse import urljoin

BASE = "https://books.toscrape.com/catalogue/"

def text_of(element, selector):
    el = element.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_books(soup):
    rows = []
    for card in soup.select("article.product_pod"):
        link = card.select_one("h3 a")
        rating = card.select_one("p.star-rating")
        rows.append({
            "title": link["title"] if link else None,
            "price": text_of(card, "p.price_color"),
            "availability": text_of(card, "p.instock"),
            "rating": rating["class"][1] if rating else None,
            "url": urljoin(BASE, link["href"]) if link else None,
        })
    return rows

Le helper text_of interroge un seul élément et retourne None s'il est absent, au lieu de lever une exception sur un appel .get_text() sur rien. Le titre et l'URL viennent des attributs plutôt que du texte, donc on les lit directement sur la balise <a>. La note est stockée comme deuxième classe sur p.star-rating (par exemple class="star-rating Three"), donc on prend le deuxième nom de classe. urljoin transforme le href relatif en URL absolue. Appelez parse_books(soup) et vous obtenez une liste propre de dictionnaires, un par livre.

Étape 4 : Gérer les pages rendues par JavaScript

Le site de pratique est statique, ce qui en fait précisément une bonne première cible. De nombreux vrais sites ne le sont pas : ils envoient un shell quasi vide et construisent le contenu dans le navigateur avec JavaScript. requests ne récupère que ce shell initial et n'exécute jamais les scripts, donc quand vous analysez la réponse, les champs que vous voyiez dans votre navigateur sont tout simplement absents.

La solution classique est un vrai navigateur. Playwright (ou Selenium) lance Chromium, laisse le JavaScript de la page s'exécuter, puis vous transmet le HTML entièrement rendu, qui s'écoule dans le même analyseur BeautifulSoup que vous avez déjà écrit.

python
# pip install playwright && playwright install chromium
from playwright.sync_api import sync_playwright

def render(url):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until="networkidle")
        html = page.content()
        browser.close()
    return html

soup = BeautifulSoup(render(url), "lxml")

L'option wait_until="networkidle" attend jusqu'à ce que la page cesse d'effectuer des requêtes réseau, ce qui est généralement suffisant pour que le contenu rendu côté client apparaisse. Cela fonctionne, mais un navigateur headless est lourd : il est lent en volume, gourmand en mémoire, et fragile quand un site détecte l'automatisation. Pour un traitement complet de ce problème, voir comment scraper des pages JavaScript avec Python et le guide dédié au web scraping avec Playwright.

Étape 5 : Suivre la pagination

Une page, c'est une démonstration ; le vrai catalogue s'étend sur de nombreuses pages. Ce site relie la page suivante avec un élément li.next a, et quand il disparaît vous avez atteint la fin. La boucle est donc simple : récupérer la page actuelle, l'analyser, trouver le lien suivant, et répéter jusqu'à l'absence de lien suivant.

python
import time

def scrape_all():
    all_rows = []
    next_url = BASE + "page-1.html"
    while next_url:
        response = requests.get(next_url, headers=headers, timeout=10)
        if response.status_code != 200:
            print(f"Stopped at {next_url}: {response.status_code}")
            break
        soup = BeautifulSoup(response.text, "lxml")
        all_rows.extend(parse_books(soup))

        next_link = soup.select_one("li.next a")
        next_url = urljoin(next_url, next_link["href"]) if next_link else None
        time.sleep(1)
    return all_rows

La boucle while next_url s'exécute jusqu'à ce que le sélecteur du lien suivant ne retourne rien, auquel point next_url devient None et la boucle se termine naturellement. Le href du site est relatif, donc urljoin le résout par rapport à la page actuelle. Le time.sleep(1) entre les pages n'est pas une politesse optionnelle sur une vraie cible : espacer vos requêtes est le moyen le plus simple de rester sous les limites de débit d'un site.

Étape 6 : Stocker les données en CSV ou JSON

Les données qui vivent uniquement en mémoire disparaissent à la fin du script. Écrivez-les sur le disque pour pouvoir les ouvrir dans un tableur, les charger dans pandas, ou les fournir à ce qui vient ensuite. Les modules intégrés csv et json de Python gèrent les deux formats sans dépendances supplémentaires. CSV est idéal pour les enregistrements plats et tabulaires ; JSON préserve la structure imbriquée et est plus compatible avec d'autres programmes. Si vous ne savez pas lequel choisir, voir les différences principales entre JSON et CSV.

python
import csv, json

def save_csv(rows, filename="books.csv"):
    if not rows:
        return
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=rows[0].keys())
        writer.writeheader()
        writer.writerows(rows)

def save_json(rows, filename="books.json"):
    with open(filename, "w", encoding="utf-8") as f:
        json.dump(rows, f, indent=2, ensure_ascii=False)

if __name__ == "__main__":
    data = scrape_all()
    save_csv(data)
    save_json(data)
    print(f"Saved {len(data)} books")

DictWriter fait correspondre les clés de chaque dictionnaire aux colonnes CSV, de sorte que la ligne d'en-tête s'écrit elle-même à partir des noms de champs que vous avez déjà choisis. newline="" évite les lignes vides entre les enregistrements sous Windows, et encoding="utf-8" préserve les caractères accentués. Pour les projets plus importants, vous écririez dans une base de données plutôt que dans un fichier, mais les enregistrements sont identiques : une liste de dictionnaires se mappe proprement sur des lignes SQL ou un magasin de documents. Exécutez le script et vous avez un export complet de tous les livres sur toutes les pages. C'est un scraper complet et fonctionnel.

À quoi ressemble le résultat

Chaque enregistrement est un dictionnaire plat, qui se sérialise proprement en JSON. Une seule entrée de books.json ressemble à ceci.

json
{
  "title": "A Light in the Attic",
  "price": "£51.77",
  "availability": "In stock",
  "rating": "Three",
  "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
}

Le prix porte encore son symbole monétaire et la note est un mot plutôt qu'un nombre, ce qui est normal : les scrapers capturent ce que la page affiche, et une passe de nettoyage distincte convertit "£51.77" en 51.77 et "Three" en 3 avant l'analyse. Garder l'extraction et le nettoyage comme étapes distinctes rend les deux plus faciles à déboguer.

Pourquoi les scrapers se font bloquer, et comment rester débloqué

Le site de pratique ne résiste jamais, mais les vraies cibles si. Deux obstacles surgissent dès que vous passez à l'échelle, et aucun ne se résout en modifiant les sélecteurs.

Le premier est la défense anti-bot. Les IPs de datacenter, les schémas de requêtes répétitifs, et le trafic qui ne ressemble pas à un vrai navigateur se voient opposer des CAPTCHAs ou sont bloqués directement. Votre scraper peut fonctionner pendant dix requêtes puis commencer à retourner des 403 ou des pages vides. Le second est le rendu côté client, abordé à l'étape 4 : une simple récupération ne peut pas voir le contenu que le navigateur construit avec JavaScript. Vous pouvez combattre les deux vous-même en maintenant un pool d'IPs résidentielles rotatives et en faisant tourner une flotte de navigateurs headless, mais assembler tout cela et le maintenir en bonne santé représente l'essentiel de l'effort d'ingénierie, et rien de tout cela n'est la donnée que vous voulez réellement.

Une API de crawling gérée plie les deux en une seule requête. Vous lui envoyez l'URL, elle rend la page dans un vrai navigateur derrière une IP rotative de confiance, et elle retourne le HTML fini pour le même analyseur que vous avez déjà écrit. Installez le client officiel aux côtés de vos bibliothèques existantes.

bash
pip install crawlbase

Gardez votre token Crawlbase sous la main ; c'est la clé d'authentification pour chaque appel. L'échange est d'une ligne : là où vous appeliez requests.get, vous appelez l'API à la place, et le HTML retourné s'écoule dans la même fonction parse_books.

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def fetch(url):
    options = {"ajax_wait": "true", "page_wait": 2000}
    result = api.get(url, options)
    if result["status_code"] == 200:
        return result["body"].decode("utf-8")
    return None

html = fetch(url)
soup = BeautifulSoup(html, "lxml")  # same parser, unchanged
rows = parse_books(soup)

Les options ajax_wait et page_wait importent sur une cible rendue côté client : ajax_wait attend que le contenu asynchrone soit terminé, et page_wait attend un nombre fixe de millisecondes pour que les éléments tardifs apparaissent avant la capture. Parce que l'API retourne du HTML, l'intégrer est une modification d'une seule ligne plutôt qu'une réécriture. Pour le guide complet anti-blocage, incluant la stratégie d'en-têtes et la rotation de proxies, voir comment scraper des sites sans se faire bloquer.

Deux types de tokens

Crawlbase propose un token normal pour récupérer du HTML statique et un token JavaScript qui rend d'abord la page dans un vrai navigateur. Utilisez le token normal pour les pages statiques comme le catalogue de pratique ; passez au token JavaScript pour tout site qui construit son contenu côté client. Si vos champs analysés reviennent vides sur une vraie cible, le token JavaScript est généralement la solution.

Passer à l'échelle au-delà d'un seul script

Le schéma récupérer-analyser-boucler-stocker vous mène loin, mais deux besoins vous poussent éventuellement au-delà d'un seul script. Le premier est la concurrence : scraper des pages une par une est lent quand vous avez des milliers d'URLs. Le second est la structure : les nouvelles tentatives, la déduplication et les pipelines de données n'ont pas leur place dans une boucle ad hoc. C'est là que Scrapy mérite sa place. Il vous offre des requêtes parallèles, des nouvelles tentatives automatiques, la planification des requêtes et des pipelines d'éléments prêts à l'emploi, de sorte que vous décrivez ce qu'il faut extraire et le framework gère l'orchestration.

Même avec Scrapy, les deux obstacles de la section précédente ne disparaissent pas : vous avez toujours besoin de rendu pour les pages JavaScript et d'IPs de confiance pour éviter les blocages en volume. La séparation propre consiste à laisser le framework gérer la concurrence et les pipelines tandis qu'une API gérée gère le rendu et la rotation, de sorte que le téléchargeur d'un spider Scrapy route simplement chaque requête via la Crawling API. Votre code reste centré sur les données et délègue l'infrastructure qui n'a rien à voir avec elles.

Scraper de façon responsable et légale

Scraper des données publiques est généralement permis, mais la façon dont vous le faites et ce que vous collectez importent plus que l'acte lui-même. Avant de pointer un scraper sur un site, lisez son robots.txt et ses conditions d'utilisation : le premier signale les chemins que le site demande aux clients automatisés d'éviter, et le second définit les règles que vous acceptez en l'utilisant. Espacez vos requêtes pour ne jamais surcharger le serveur, identifiez votre client honnêtement, et préférez l'API officielle d'un site quand elle existe, car une API est le chemin d'accès que le propriétaire a réellement conçu pour une utilisation programmatique et vous épargne la fragilité de l'analyse HTML.

Restez du bon côté de la ligne en limitant la collecte aux données publiques non personnelles. Évitez tout ce qui se trouve derrière une connexion, tout ce qui nécessite d'accepter des conditions que vous contourneriez, et les données personnelles couvertes par des régimes de confidentialité comme le RGPD et le CCPA, où la collecte peut nécessiter un consentement et une base légale. Ne redistribuez pas les médias protégés par le droit d'auteur que vous scrapez, et quand un projet est commercial ou touche à des données réglementées, obtenez la validation juridique que vous demanderiez pour toute autre source de données. Le scraping responsable relève surtout du bon sens : ne prenez que ce qui est public, prenez-le doucement, et respectez les souhaits que le site a déjà publiés.

Récapitulatif

Points clés

  • La boucle fondamentale est : récupérer, analyser, boucler, stocker. requests obtient le HTML, BeautifulSoup extrait les champs, la pagination parcourt les pages, et le module csv ou json sauvegarde le résultat.
  • Adaptez l'outil à la page. requests et BeautifulSoup couvrent les sites statiques ; Playwright ou Selenium rendent JavaScript ; Scrapy ajoute la concurrence et des pipelines à grande échelle.
  • Inspectez avant de sélectionner. Ouvrez les outils de développement de la page pour trouver les balises et classes qui contiennent vos données, puis mappez chaque champ sur un sélecteur CSS.
  • requests simple a deux limites. Il ne peut pas exécuter JavaScript et se fait bloquer à grande échelle, ce qu'aucun sélecteur ne peut corriger.
  • Une API gérée résout les deux en un seul appel. La Crawling API rend la page derrière une IP rotative de confiance et retourne le HTML fini, de sorte que votre analyseur existant continue de fonctionner sans changement.

Foire aux questions

Qu'est-ce que le web scraping ?

Le web scraping est l'extraction automatisée de données depuis des pages web. Un script demande une URL, le serveur retourne du HTML, et un analyseur extrait les champs spécifiques que vous voulez et les sauvegarde dans un format structuré comme CSV, JSON, ou une base de données. C'est ainsi que vous transformez des pages conçues pour la lecture humaine en données que vous pouvez interroger et analyser à grande échelle.

De quelles bibliothèques Python ai-je besoin pour commencer ?

Pour un site statique typique, requests et BeautifulSoup suffisent : requests télécharge la page et BeautifulSoup extrait les champs par balise et classe CSS. Ajoutez lxml pour une analyse plus rapide et la prise en charge de XPath, Playwright ou Selenium quand un site se rend avec JavaScript, et Scrapy quand vous avez besoin de concurrence et de pipelines pour un projet plus important.

Pourquoi mes données scrapées sont-elles vides alors que la page contient clairement du contenu ?

Presque toujours parce que le site rend son contenu avec JavaScript. requests ne récupère que le shell HTML initial et n'exécute pas les scripts, donc les données que vous voyez dans votre navigateur ne sont pas présentes dans ce que vous analysez. Rendez d'abord la page, soit avec un navigateur headless soit avec le token JavaScript de la Crawling API, avant que BeautifulSoup puisse trouver les champs.

Comment scraper plusieurs pages ?

Trouvez le lien ou le schéma que le site utilise pour sa page suivante, puis faites une boucle. S'il y a un bouton "suivant", suivez son href jusqu'à ce qu'il disparaisse, comme montré à l'étape 5. Si les URLs suivent un schéma numérique comme page-2.html, vous pouvez les construire dans une boucle range à la place. Dans tous les cas, ajoutez un court délai entre les pages pour rester poli et débloqué.

Comment éviter d'être bloqué pendant le scraping ?

Espacez vos requêtes avec un délai, envoyez un en-tête User-Agent réaliste, et évitez de marteler un seul chemin. À grande échelle, vous avez également besoin d'IPs qui ressemblent à de vrais visiteurs, ce qu'une seule machine ne peut pas fournir. Router via des IPs résidentielles rotatives, que ce soit via la Crawling API ou le Smart AI Proxy, est ce qui permet aux runs à fort volume d'éviter les limites de débit et les CAPTCHAs.

Le web scraping est-il légal ?

Scraper des données publiques est généralement permis, mais cela dépend des conditions d'utilisation du site, de votre juridiction, et de ce que vous faites des données. Vérifiez le robots.txt et les conditions avant de commencer, évitez les données personnelles couvertes par des lois sur la confidentialité comme le RGPD et le CCPA, ne scrapez jamais de contenu derrière une connexion, et préférez une API officielle quand elle existe. En cas de doute, collectez uniquement des données publiques et gardez votre volume assez bas pour ne pas surcharger le serveur.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles