Scraper un site web avec Python et BeautifulSoup : guide complet pour extraire des données

Récupérer les titres d’une catégorie blog pour vérifier une migration SEO, aspirer une SERP publique pour un audit, lister les fiches produits d’un concurrent : tu ouvres ton terminal, tu installes requests et beautifulsoup4, tu parses le HTML. Le workflow tient en dix lignes ; les pièges sont dans les détails.

Qu’est-ce que scraper un site web avec Python et BeautifulSoup ?

Trois briques : requests fait la requête HTTP, BeautifulSoup parse le HTML en un objet soup navigable, toi tu extrais avec find, find_all, select ou get_text.

Pourquoi BeautifulSoup reste un choix pertinent pour scraper une page web

Syntaxe lisible, tolérance au HTML malformé, choix du parser (lxml ou html.parser), intégration naturelle avec requests. Pour une page statique ou semi-dynamique, dix lignes suffisent à extraire ce qu’il faut. La limite arrive quand tu veux avaler des milliers de pages en parallèle : là, on bascule sur Scrapy ou on streame en écriture disque pour ne pas noyer la RAM.

Installer requests et beautifulsoup4 et démarrer le script

pip, puis l’import :

pip install requests beautifulsoup4 lxml

import requests
from bs4 import BeautifulSoup

lxml comme parser par défaut : plus rapide et plus tolérant que html.parser sur du HTML cassé.

Récupérer le contenu d’une page web avec requests

requests.get(url) te rend un objet response avec un status_code et un body. Sur un 200, le HTML est dans response.text ; sur une page lourde ou aux encodages exotiques, response.content brut est plus fiable.

response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200:
    raise RuntimeError("Erreur lors de la récupération de la page")

Un 403 dit presque toujours qu’un anti-bot a repéré l’User-Agent par défaut (python-requests/X.Y) ; passer un User-Agent de navigateur résout la plupart des cas triviaux. Un 429 signale un rythme trop élevé. Les redirections 3xx sont suivies automatiquement sauf si tu passes allow_redirects=False, ce qui sert quand tu veux inspecter une chaîne de redirects pendant un audit de migration.

Analyser le HTML avec BeautifulSoup et créer l’objet soup

soup = BeautifulSoup(response.text, "lxml")

BeautifulSoup représente les balises comme des éléments accessibles via find, find_all et select. Tu accèdes à soup.title, tu parcours soup.find_all(“a”), tu récupères un attribut avec element.get(“href”). Pour les sélecteurs CSS, select_one(“h1”) ou select_one(“meta[property=‘og:title’]”) sont plus robustes qu’une recherche par texte libre : le HTML bouge, un sélecteur ciblé tient mieux que trois find imbriqués.

Extraire des données avec BeautifulSoup : find, find_all, select et get_text

element.get_text(strip=True) renvoie le texte nettoyé d’une balise, enfants compris. find_all retourne une liste ; find retourne le premier match ou None. Tu filtres par balise, par classe avec find_all(“div”, class_=“article”), par id, ou par attribut arbitraire.

Dans une boucle sur les éléments, tu récupères le titre avec el.select_one(“h2”), le lien avec el.find(“a”).get(“href”), le paragraphe avec el.select_one(“p”).get_text(strip=True). Les sélecteurs CSS de select_one et select sont souvent plus maintenables qu’une imbrication de find : si le site change une classe, tu modifies un seul sélecteur au lieu de refactorer toute la boucle.

url = "https://example.com/mes-articles"
response = requests.get(url)
soup = BeautifulSoup(response.text, "lxml")
articles = soup.find_all("article")
data = []
for a in articles:
    titre = a.select_one("h2").get_text(strip=True) if a.select_one("h2") else None
    lien = a.find("a").get("href") if a.find("a") else None
    extrait = a.select_one("p").get_text(strip=True) if a.select_one("p") else None
    data.append({"titre": titre, "lien": lien, "extrait": extrait})

Le pitfall qu’on a tous rencontré une fois : ton sélecteur marche parfaitement dans la console DevTools, et renvoie None dans ton script. La plupart du temps, le contenu est injecté par JavaScript après le chargement initial ; l’inspecteur affiche le DOM final, requests te donne le HTML source brut. Ouvre l’onglet Network des DevTools, cherche une requête XHR ou fetch qui porte la donnée, attaque l’endpoint JSON directement. Sinon, Playwright. Ce diagnostic réflexe t’évite des heures à bricoler un sélecteur qui ne matchera jamais, parce que la balise que tu cherches n’existe pas encore au moment où requests lit la page. On a nous-mêmes passé une demi-journée à debugger un sélecteur qui ne matchait pas, avant de se rendre compte que la donnée arrivait par une XHR qu’on n’avait jamais ouverte.

Exemple complet : scraper un site web Python BeautifulSoup pas à pas

Le squelette ne change plus d’un projet à l’autre : imports, get, soup, find_all sur le conteneur parent, boucle, append. Ce qui varie, c’est le sélecteur qui cible tes éléments et le nettoyage du texte (espaces insécables, accents mal encodés, retours chariot). Sortie : une liste de dictionnaires, prête pour pandas, csv.DictWriter ou json.dump.

Scraper plusieurs pages web : pagination et itération

Deux patterns dominent. Le paramètre page dans l’URL (?page=2, /page/2/) : tu boucles sur un range, tu formates l’URL, tu parses. Ou le lien « page suivante » dans une balise rel=“next” ou un bouton : tu suis le lien jusqu’au None final, plus robuste quand le site change de schéma d’URL.

Pour un gros volume, tu écris le CSV en mode append à chaque page plutôt que d’accumuler en mémoire. Un time.sleep(1) entre les requêtes et un try/except autour de get : quand le site renvoie un 503 ponctuel à la page 47, le script continue au lieu de s’arrêter.

Quand utiliser BeautifulSoup, requests, une API ou un autre scraper ?

Pages statiques, contenu dans le HTML initial, volume modeste : requests + BeautifulSoup, point. Endpoint JSON disponible ou API officielle : requests seul, tu zappes le parsing et tu gagnes en stabilité (les balises changent, les schémas d’API versionnés bien moins). Contenu rendu côté client par React, Vue ou Next.js en CSR : Playwright ou Selenium pour exécuter le JS avant de parser. Crawling à grande échelle avec pipelines, middlewares et gestion de la concurrence : Scrapy. Réflexe utile : ouvrir l’onglet Network avant de coder. Si l’API XHR est là, tu l’utilises directement et tu sautes BeautifulSoup entièrement.

Gérer les limites du scraping : contenu dynamique, erreurs et anti-bot

Trois murs. Rendu JavaScript : tu cherches l’endpoint XHR, sinon Playwright. Rate limit : 429, captcha, blacklist IP ; time.sleep, backoff exponentiel, User-Agent identifiable. Anti-bot sérieux (Cloudflare challenge, DataDome, PerimeterX) : si le site a investi là-dedans, il ne veut pas de toi. Une API payante coûtera moins cher en temps qu’un jeu du chat et de la souris perdu d’avance.

Nettoyer, structurer et exporter les données collectées

get_text(strip=True) absorbe le plus gros du ménage. Pour le reste, re.sub sur les caractères indésirables et unicodedata.normalize si les accents arrivent cassés. Liste de dictionnaires en sortie, csv.DictWriter ou json.dump pour l’export, pandas.DataFrame(data) si tu enchaînes sur de l’analyse.

Bonnes pratiques, conformité et FAQ sur scraper site web python beautifulsoup

Légalité : ça dépend des CGU du site et du type de données. Les contenus publics non personnels passent en général, le RGPD verrouille dès que tu touches des données personnelles, et contourner une protection technique bascule dans le gris foncé. Le robots.txt n’a pas force de loi, mais il signale une intention ; l’ignorer massivement aggrave la posture en cas de litige.

Un scraper propre annonce qui il est dans son User-Agent, respecte un rythme lisible, extrait ce qui sert au projet et stocke sans rediffuser la donnée brute.

Questions fréquentes

Comment scraper une page web avec Python si elle charge du contenu en JavaScript ?

Si le HTML initial ne contient pas les données visibles, on cherche d’abord un endpoint réseau qui renvoie du JSON, sinon on utilise Playwright ou Selenium pour rendre la page et obtenir le HTML final avant d’utiliser BeautifulSoup pour parser.

BeautifulSoup suffit-il pour tous les sites ?

Non. BeautifulSoup convient aux pages statiques. Pour des pages fortement dynamiques, ou pour contourner des protections anti-bot, il faut d’autres outils ou une API officielle.

Pourquoi mon code ne récupère pas le contenu attendu malgré un response 200 ?

Plusieurs causes possibles : contenu injecté en JavaScript, sélecteurs CSS incorrects, ou changement de structure des balises. Vérifier la source HTML dans response.text et adapter les find_all ou select en conséquence.

Comment structurer les données pour un usage backend ?

Organiser chaque élément comme un dictionnaire avec des clés stables, puis regrouper en liste ou transformer en dataframe pour ingestion dans un backend Python ou une API.

Quiz personnalisé

Votre recommandation sur scraper un site web avec python & beautifulsoup

Quelques questions rapides pour adapter la recommandation à votre cas.

Q1 Votre situation sur scraper un site web avec python & beautifulsoup ?
Q2 Votre priorité ?
Q3 Votre horizon ?