Formation Python | Web Scraping avec Beautiful Soup

Apprenez à utiliser Beautiful Soup pour extraire des données efficacement sur le web avec Python. Guide complet pour le scraping HTML et la manipulation DOM.

Icône de calendrier
Intermédiaire
8 chapitres

Qu’est-ce que le Web Scraping ?

Le scraping de données est une technique essentielle pour extraire des informations de sites web en utilisant Python.

On distingue aujourd’hui 2 principaux types de scraping sur le web :

  1. Le scraping de SERP (Search Engine Result Page) : visant à collecter des informations sur les résultats de recherche, comme les titres, descriptions, URLs ou autres métadonnées. Ce type de scraping est largement exploité par des outils SEO tels que SEMrush ou Ahrefs, qui analysent les performances des mots-clés, suivent les classements, et surveillent la concurrence sur les moteurs de recherche.
  2. Le scraping de sites web : visant à collecter des données spécifiques sur une ou plusieurs pages web, telles que des prix de produits, des avis clients, des articles de blog, ou toute autre information accessible publiquement.

Qu’il soit sur une SERP ou directement sur un site web, les cas d’usage du web scraping sont nombreux : veille concurrentielle, collecte de données, curation de contenus, alimentation pour machine learning, etc.

Je vous invite à lire notre guide du web scraping si vous souhaitez en apprendre plus sur cet univers passionnant.

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

Vous l’aurez compris, le web scraping consiste à analyser le contenu d’une page web. Il nous faut donc être capable de récupérer le contenu d’une page web depuis son URL ! Et ça, c’est le rôle de notre premier module Python indispensable : requests.

La bibliothèque requests est une bibliothèque Python très populaire pour effectuer des requêtes HTTP. Elle est utile pour interagir avec des API ou télécharger des données depuis des sites web.

Installation de requests

Pour installer requests, taper la commande suivante :

copié !
pip install requests

Récupération avec la méthode get

Pour récupérer le contenu d’une page web, on utilise la méthode get de la bibliothèque requests. Cette méthode permet d’envoyer une requête HTTP GET à une URL et d’en récupérer la réponse.

Dans le cas du scraping de pages web, ce retour HTTP se présentera généralement sous la forme de document HTML (ou parfois XML) mais gardez en tête que la bibliothèque requests est tout aussi centrale pour manipuler des retours API, bien souvent au format JSON.

main.py
copié !
import requests

response = requests.get("https://laconsole.dev/blog")

Si on print(response), on obtiendra alors le retour suivant : <Response [200]>

Extraction du résultat

Une bonne habitude est de s’assurer que requests.get() nous retourne bien un code HTTP de succès.

Pour cela on vérifie ce que contient la propriété response.status_code :

main.py
copié !
import requests

response = requests.get("https://laconsole.dev/blog")

if response.status_code == 200:
	# ✅ CODE 200
else:
	# ❌ CODES 4xx/5xx

Un code HTTP en retour c’est pas mal, mais on ne va pas aller bien loin avec ça. Nous ce qu’on veut, c’est des données !

Pour extraire le contenu brut de la réponse HTTP, on fait appel à la propriété response.text.

main.py
copié !
import requests

response = requests.get("https://laconsole.dev/blog")

if response.status_code == 200:
	print(response.text)
else:
	print(f"Erreur lors de la requête : {response.status_code}")

Avec cette méthode, vous êtes maintenant capable de récupérer du contenu HTML ou JSON depuis une page web, une première étape clé pour le scraping !

Analyser le contenu d’une page web avec BeautifulSoup

Il est temps d’analyser le contenu du code HTML retourné. Et ça, c’est le rôle de la très célèbre librairie Python BeautifulSoup.

Installation de beautifulsoup4

Commençons par installer BeautifulSoup. Pour cela, taper la commande suivante :

copié !
pip install beautifulsoup4

Création de la soupe !

La soupe c’est tout simplement le nom donné à l’objet qui va stocker la structure du DOM de la page que nous souhaitons scraper.

main.py
copié !
import requests
from bs4 import BeautifulSoup

response = requests.get("https://laconsole.dev/blog")

if response.status_code == 200:
	soup = BeautifulSoup(response.text, 'html.parser')
else:
	print(f"Erreur lors de la requête : {response.status_code}")

La classe BeautifulSoup attend 2 arguments :

  • Le code HTML brut (contenu dans response.text) que nous souhaitons scraper
  • Un parser pour analyser ce contenu HTML. Par défaut, on utilisera 'html.parser'.

BeautifulSoup propose 5 librairies pour analyser le code HTML : html.parser, xml, lxml, lxml-xml et html5lib.

Le choix d’un parseur plutôt qu’un autre sera défini par le type de code à parser (XML ou HTML), les performances qu’il propose ou encore pour des raisons de compatibilité.

Une fois que nous avons créé notre objet BeautifulSoup (généralement nommé soup), il est possible d’explorer l’arbre du DOM (Document Object Model) de la page HTML.

Formater sa soupe pour un bel output !

L’utilisation de la méthode prettify() permet de rendre notre soupe plus… digeste ! 🍜 Concrètement, celle-ci va formater le code HTML de manière plus lisible, en ajoutant des indentations et des retours à la ligne.

Idéal pour un affichage lisible.

main.py
copié !
import requests
from bs4 import BeautifulSoup

response = requests.get("https://laconsole.dev/blog")

if response.status_code == 200:
	soup = BeautifulSoup(response.text, 'html.parser')
	formatted_html = soup.prettify()
else:
	print(f"Erreur lors de la requête : {response.status_code}")

Exploration de la soupe

Une soupe d’objets

BeautifulSoup nous permet de naviguer facilement dans cette structure HTML, qui se présente sous la forme d’une soupe d’objets.

Ces objets nous aident à récupérer, analyser et manipuler le contenu HTML de manière très flexible.

Voici les 3 principaux types d’objets que nous rencontrons :

  • Tag : représente une balise HTML, comme <div>, <p> ou <a>. Il permet d’accéder au contenu de la balise, à ses enfants ou à ses attributs.
  • NavigableString : représente le contenu textuel à l’intérieur d’une balise HTML
  • Comment : représente un commentaire HTML.

Les Tag constituent la grande majorité des objets que nous manipulons.

Ici, soup.h1 retourne un Tag :

html = "<body><h1 class='text-lg font-bold'>Je suis le titre principal</h1></body>"
soup = BeautifulSoup(response.text, 'html.parser')
print(soup.h1) # Affiche "<h1 class='text-lg font-bold'>Je suis le titre principal</h1>"

Depuis un Tag, on peut aisément extraire son nom et son contenu textuel :

# ...

print(soup.h1.name) # Affiche "h1"
print(soup.h1.string) # Affiche "Je suis le titre principal"

Pour accéder, ajouter, modifier ou supprimer des attributs, on travaillera comme on le ferait avec une liste en python :

# ...

print(soup.h1['class']) # Affiche ["text-lg", "font-bold"]
soup.h1['id'] = 'main-title' # Ajoute un id => <h1 class="..." id="main-title">...</h1>
del soup.h1['id'] # Supprime l'id

Maintenant que nous visualisons bien de quels objets est constituée notre soupe HTML, il est temps de l’explorer !

Recherche dans l’arbre

BeautifulSoup propose 2 grandes approches lorsqu’il s’agit de rechercher dans l’arbre du DOM.

Les méthodes find() et find_all()

Ces méthodes sont historiques et permettent de rechercher des éléments en spécifiant des tags, des classes ou des attributs. Elles sont pratiques pour des recherches simples ou ciblées.

copié !
h1 = soup.find('h1')
avatar = soup.find('img', id='avatar')
links = soup.find_all('a', href=True)

find() retourne le premier élément qui matche avec le sélecteur, tandis que find_all() retourne une liste.

Les méthodes select_one() et select()

Ces méthodes utilisent des sélecteurs CSS, ce qui les rend intuitives pour les développeurs habitués à écrire des styles CSS ou manipuler le DOM en JavaScript.

Elles sont particulièrement adaptées pour des recherches complexes combinant classes, IDs, et relations hiérarchiques entre les éléments.

copié !
first_h2 = soup.select_one('div.content > h2')
secured_links = soup.select('a[href^="https://"]')

select_one() retourne le premier élément qui matche avec le sélecteur, tandis que select() retourne une liste.

Dans un objectif d’initiation au scraping web avec Python, il est donc important d’être en mesure de « faire beaucoup avec peu ». Je vous invite donc à privilégier les méthodes select_one() et select(), pour leur simplicité et leur universalité.

L’un des principaux avantages de BeautifulSoup est la facilité avec laquelle on peut naviguer dans la structure hiérarchique d’une page HTML. Cette navigation permet de se déplacer entre les différents éléments, qu’il s’agisse de parents, d’enfants ou de frères et sœurs.

Il existe 2 manières de naviguer dans le DOM :

  1. En profondeur (decendants et ancêtres)
  2. Au même niveau (frères et soeurs)
En profondeur

BeautifulSoup nous permet de naviguer dans la profondeur des balises du DOM.

Descendants

Pour accéder aux balises enfants d’une autre, on utilise la propriété children.

children renvoie les enfants directs (balises, texte, espaces) d’un élément sous forme d’itérateur (iterable) à parcourir via une boucle.

Considérons le code HTML suivant :

copié !
<div id="demo">
	<p>
		Texte 1 
		<span>Texte 2</span>
	</p>
	<a>Texte 3</a>
</div>
copié !
div = soup.select_one("#demo")

for child in div.children:
	print(child)

On obtient en sortie :

<p>Texte 1 <span>Texte 2</span></p>
<a>Texte 3</a>

Pour accéder directement à l’ensemble des éléments sous forme de liste, il existe la propriété alternative contents.

copié !
div = soup.select_one("#demo")
div.contents

On obtient en sortie :

[<p>Texte 1 <span>Texte 2</span></p>, <a>Texte 3</a>]

La propriété descendants permet quant à elle de parcourir tous les descendants d’un élément, à n’importe quel niveau de profondeur, plutôt que juste les enfants directs.

Le résultat sera également disponible sous la forme d’un itérateur (iterable) :

div = soup.select_one("#demo")

for descendant in div.descendants:
	print(descendant)

On obtient en sortie :

<p>Texte 1 <span>Texte 2</span></p>
Texte 1 
<span>Texte 2</span>
Texte 2
<a>Texte 3</a>
Texte 3
Ancêtres

Il est possible d’effectuer les opérations inverses avec les propriétés parent et parents.

Considérons le code HTML suivant :

copié !
<div id="demo">
	<p>
		Texte
		<span>1</span>
	</p>
</div>

parent renvoie l’élément parent direct de l’élément actuel.

copié !
p = soup.select_one("p")
print(p.parent)

On obtient en sortie :

<div id="demo"><p>Texte <span>1</span></p></div>

parents renvoie un itérateur sur tous les ancêtres de l’élément, du plus proche au plus éloigné (jusqu’à la racine [document] du document).

copié !
span = soup.select_one("span")

for parent in span.parents:
	print(parent)

On obtient en sortie :

<p>Texte <span>1</span></p>
<div id="demo"><p>Texte <span>1</span></p></div>
<div id="demo"><p>Texte <span>1</span></p></div>

Utilisez name dans la boucle for pour visualiser les noms des ancêtres :

copié !
span = soup.select_one("span")

for parent in span.parents:
	print(parent.name)

Cela affichera en sortie :

p
div
[document]

On remarque bien que le contenu de [document] est identique à la <div id="demo">, encapsulant tout le contenu de la page.

Au même niveau

Les propriétés next_sibling, previous_sibling, next_siblings et previous_siblings permettent de naviguer entre les éléments frères (siblings) d’un élément, c’est-à-dire les éléments qui partagent le même parent.

Considérons le code HTML suivant :

<p id="p1">Premier</p>
<p id="p2">Deuxième</p>
<p id="p3">Troisième</p>
  1. previous_sibling : Renvoie l’élément précédent au même niveau que l’élément actuel dans le DOM.
  2. next_sibling : Renvoie l’élément suivant au même niveau que l’élément actuel dans le DOM.
copié !
p2 = soup.select_one("#p2")
print(p2.previous_sibling)
print(p2.next_sibling)

Cela affichera en sortie :

<p id="p1">Premier</p>
<p id="p3">Troisième</p>

Les versions plurielles next_siblings et previous_siblings de ces attributs renvoient respectivement un itérateur de tous les frères suivants ou précédents d’un élément.

copié !
p1 = soup.select_one("#p1")
for next in p1.next_siblings:
	print(next)

Cela affichera en sortie :

<p id="p2">Deuxième</p>
<p id="p3">Troisième</p>
copié !
p3 = soup.select_one("#p3")
for previous in p3.previous_siblings:
	print(previous)

Cela affichera en sortie :

<p id="p2">Deuxième</p>
<p id="p1">Premier</p>

Modifier l’arbre

Si les propriétés vues précédemment s’avèrent très utiles pour analyser du code HTML/XML, il peut parfois être intéressant d’aller modifier cette structure.

C’est par exemple le cas si l’on cherche à :

  • Ajouter de nouvelles balises pour enrichir le contenu
  • Nettoyer le DOM en supprimant des éléments inutiles ou gênants (<script>, <style>, espaces et retours à la ligne…)
  • Restructurer l’arbre pour faciliter l’analyse ou l’exploitation
  • Corriger une erreur dans le DOM source
  • Insérer des données dynamiques récupérées depuis une autre source
  • Etc.

S’agissant d’un besoin à mon sens plus secondaire dans cette initiation au scraping web avec BeautifulSoup, je me contenterai de lister sans rentrer dans les détails quelques propriétés et méthodes utiles pour modifier le DOM :

Méthode/propriétéRôle
append()Ajoute un nouveau nœud comme dernier enfant d’une balise existante.
insert()Insère un nœud enfant à une position spécifique dans une balise.
new_tag()Crée une nouvelle balise HTML ou XML que vous pouvez ajouter dans l’arbre DOM.
decompose()Supprime complètement un nœud de l’arbre DOM.
extract()Supprime un nœud de l’arbre DOM et le retourne (utile pour réutilisation).
clear()Supprime tout le contenu enfant d’une balise sans supprimer la balise elle-même.
replace_with()Remplace un nœud existant par un autre nœud.
wrap()Enveloppe un nœud existant dans une nouvelle balise.
unwrap()Supprime une balise tout en conservant son contenu enfant.
stringDéfinit ou modifie le texte à l’intérieur d’une balise.