Le scraping par langage 6 min de lecture

Web scraping en Python avec lxml : vitesse et XPath

Pourquoi lxml est l'un des parseurs HTML/XML les plus rapides pour Python : requêtes XPath, comparaison avec BeautifulSoup et techniques pour traiter les documents volumineux.

ÉW
Équipe Web-Scraping.fr
Collecte de données pour votre activité
Publié le: 21 janvier 2025

lxml est la bibliothèque d'analyse HTML et XML la plus rapide de l'écosystème Python. Construite sur les bibliothèques C libxml2 et libxslt, elle surpasse largement les parseurs en pur Python et offre en prime un XPath complet — plus puissant que les sélecteurs CSS. Si vous avez atteint les limites de performance de BeautifulSoup sur de gros volumes ou si vous avez besoin de sélections complexes, lxml est le bon choix.

Cet article approfondit le sujet abordé dans le guide de référence « Web scraping en Python », où lxml est mentionné parmi d'autres parseurs. Ici, nous l'analysons en détail.

Sommaire

  1. Installation et les deux modules de lxml
  2. Analyse du HTML
  3. XPath : le cœur de la puissance de lxml
  4. Sélecteurs CSS via cssselect
  5. Extraction de données : texte, attributs, liens
  6. Le cyrillique dans lxml
  7. Documents volumineux : iterparse
  8. lxml vs BeautifulSoup
  9. Avantages et inconvénients

1. Installation et les deux modules de lxml

bash
pip install lxml

lxml comporte deux modules clés :

  • lxml.html — pour analyser le HTML (tolère le balisage « sale », comme un navigateur).
  • lxml.etree — pour le XML strict (voir l'article dédié « Parser du XML en Python »).

Pour le web scraping, dans 95 % des cas, c'est lxml.html qu'il vous faut.


2. Analyse du HTML

python
import requests
from lxml import html

resp = requests.get("https://example.com", timeout=10)
tree = html.fromstring(resp.content)   # on passe des octets — lxml détecte l'encodage lui-même

fromstring renvoie l'élément racine de l'arbre. On lui applique ensuite des requêtes XPath ou des sélecteurs CSS. On peut aussi parser depuis un fichier :

python
tree = html.parse("page.html").getroot()

Important : passez resp.content (des octets) et non resp.text (une chaîne) — lxml lira alors correctement la déclaration d'encodage contenue dans le HTML lui-même. Cela élimine la plupart des problèmes liés au cyrillique.


3. XPath : le cœur de la puissance de lxml

XPath est un langage d'adressage des nœuds dans l'arbre du document, et c'est là que lxml révèle tout son potentiel.

python
# texte de tous les titres h2 portant la classe title
titles = tree.xpath('//h2[@class="title"]/text()')

# attribut href de tous les liens
links = tree.xpath('//a/@href')

# texte à l'intérieur d'un bloc précis
price = tree.xpath('//div[@class="price"]/text()')[0]

Constructions XPath utiles

python
# correspondance partielle de classe (quand class="title big featured")
tree.xpath('//h2[contains(@class, "title")]/text()')

# par le texte de l'élément
tree.xpath('//a[text()="Подробнее"]/@href')

# n-ième élément (la numérotation commence à 1 !)
tree.xpath('(//div[@class="item"])[3]')

# chemin relatif à partir du nœud trouvé
for card in tree.xpath('//div[@class="card"]'):
    name = card.xpath('.//h3/text()')      # le point = « à partir du nœud courant »
    link = card.xpath('.//a/@href')

Le point initial (.//) dans un XPath relatif est crucial : sans lui, la recherche part de la racine du document et non de la carte courante. C'est l'erreur la plus fréquente chez les débutants.

Les axes XPath

XPath sait parcourir l'arbre dans toutes les directions — chose impossible en CSS :

python
# parent d'un élément
tree.xpath('//span[@class="price"]/parent::div')

# élément suivant du même niveau
tree.xpath('//h2/following-sibling::p[1]/text()')

# ancêtre portant une classe donnée
tree.xpath('//a[@id="buy"]/ancestor::div[@class="product"]')

4. Sélecteurs CSS via cssselect

Si XPath vous semble lourd, lxml prend en charge les sélecteurs CSS (le paquet cssselect est requis) :

bash
pip install cssselect
python
# la méthode .cssselect() renvoie une liste d'éléments
cards = tree.cssselect("div.product-card")
title = tree.cssselect("h1.title")[0].text_content()
links = [a.get("href") for a in tree.cssselect("a.product-link")]

Le CSS est plus familier pour ceux qui viennent du front-end. Sous le capot, cssselect traduit le CSS en XPath, la vitesse est donc préservée. Pour les sélections complexes (par texte, par ancêtres), XPath restera néanmoins nécessaire.


5. Extraction de données : texte, attributs, liens

python
el = tree.cssselect(".product")[0]

el.text_content()          # tout le texte interne, balises imbriquées comprises
el.get("href")             # valeur d'un attribut
el.attrib                  # dictionnaire de tous les attributs
el.tag                     # nom de la balise
el.text                    # seulement le texte direct du nœud (sans les imbriqués)

La différence entre .text et .text_content() est importante :

python
# <p>Привет, <b>мир</b>!</p>
p.text              # "Привет, "         (seulement jusqu'à la balise imbriquée)
p.text_content()    # "Привет, мир!"     (tout le texte, récursivement)

Convertir les liens relatifs en liens absolus :

python
tree.make_links_absolute("https://example.com")
links = tree.xpath('//a/@href')   # tous les liens sont désormais absolus

make_links_absolute évite la concaténation manuelle d'URL — pratique lors du parcours d'un site.


6. Encodages et Unicode dans lxml

lxml gère parfaitement le cyrillique, à condition de lui fournir des octets et non une chaîne déjà décodée :

python
# CORRECT : des octets — lxml lira l'encodage depuis <meta charset>
tree = html.fromstring(resp.content)

# RISQUÉ : une chaîne déjà décodée par requests (peut-être incorrectement)
tree = html.fromstring(resp.text)

S'il faut imposer l'encodage explicitement (par exemple quand le serveur ment dans ses en-têtes) :

python
from lxml import html

parser = html.HTMLParser(encoding="windows-1251")
tree = html.fromstring(resp.content, parser=parser)

La théorie complète des problèmes d'encodage se trouve dans le guide central, section « Encodages ».


7. Documents volumineux : iterparse

Quand un document est énorme (un flux XML de plusieurs centaines de mégaoctets), le charger entièrement en mémoire est du gaspillage. iterparse lit le flux au fil de l'eau, en traitant les éléments à mesure qu'ils apparaissent et en libérant la mémoire :

python
from lxml import etree

for event, element in etree.iterparse("huge.xml", tag="item"):
    title = element.findtext("title")
    process(title)
    element.clear()                       # on libère la mémoire
    while element.getprevious() is not None:
        del element.getparent()[0]        # on supprime les nœuds déjà traités

Cette technique (lecture + clear()) permet d'analyser des fichiers qui ne tiennent pas entièrement en RAM. Pour en savoir plus sur l'analyse XML en streaming, consultez « Parser du XML en Python ».


8. lxml vs BeautifulSoup

Critère lxml BeautifulSoup
Vitesse très élevée (C) plus faible (sans le moteur lxml)
API plus stricte, exige de connaître XPath conviviale, se lit comme du texte
XPath complet absent (recherche CSS uniquement)
HTML « sale » bien géré très bien géré, très tolérant
Mémoire plus économe, avec iterparse consommation supérieure
Courbe d'apprentissage plus raide plus douce

En pratique, on les combine souvent : BeautifulSoup peut utiliser lxml comme moteur (BeautifulSoup(html, "lxml")) — vous obtenez l'API confortable de bs4 et la vitesse de lxml. Si vous avez besoin de XPath ou d'une analyse en streaming de fichiers de plusieurs gigaoctets, utilisez lxml directement. La comparaison des différents parseurs se trouve dans le guide central, et l'extraction de tableaux dans un article dédié.


9. Avantages et inconvénients de lxml

Avantages :

  • L'un des parseurs les plus rapides de l'écosystème Python (cœur en C).
  • XPath complet avec les axes (parent, sibling, ancestor) — inaccessible en CSS.
  • Prise en charge du CSS via cssselect — le meilleur des deux mondes.
  • iterparse pour le traitement en streaming de documents gigantesques.
  • Consommation mémoire réduite.

Inconvénients :

  • Courbe d'apprentissage plus raide, surtout pour XPath.
  • API plus stricte et moins « tolérante » que celle de BeautifulSoup.
  • Messages d'erreur parfois obscurs.
  • L'installation exige parfois des bibliothèques système (sur certains OS).

Conclusion : lxml est le choix de la performance et des sélections complexes. Pour les tâches ponctuelles et une lisibilité maximale, BeautifulSoup est plus simple ; pour des volumes gigantesques avec des sélections CSS, jetez aussi un œil à selectolax. C'est précisément pour sa vitesse que lxml est le plus souvent associé au scraping asynchrone : quand des centaines de pages se téléchargent simultanément, un parser lent devient le goulot d'étranglement, et le moteur C de lxml le supprime.