Reading XML python et XPath : extraire exactement les bons éléments

On reçoit un export XML de factures depuis un ERP, et la première requête XPath renvoie une liste vide. Le fichier fait plusieurs mégaoctets, les balises contiennent des namespaces, et le délai de livraison du script est pour demain. C’est exactement dans ce genre de situation que la maîtrise du reading XML python avec XPath fait la différence entre un script qui tourne et une matinée perdue à déboguer des sélecteurs.

Namespaces XML en Python : le piège qui bloque la plupart des requêtes XPath

Quand on travaille sur des fichiers métiers (Factur-X, flux EDI, exports comptables), les balises sont presque toujours préfixées par un namespace. Un élément qui ressemble à <factur:Invoice> dans le fichier source n’est pas trouvé par un simple findall("Invoice"). C’est le premier mur.

Lire également : GIF format for Instagram sur mobile : les bons paramètres à adopter

Avec xml.etree.ElementTree, on doit utiliser la notation complète {namespace}tag dans chaque expression. En pratique, on crée un dictionnaire de préfixes qu’on passe à findall ou find. Oublier ce dictionnaire, c’est garantir un résultat vide sans aucun message d’erreur, ce qui rend le débogage pénible.

Avec lxml, on peut passer un dictionnaire namespaces directement à la méthode xpath(), et utiliser les préfixes courts dans l’expression. C’est plus lisible et plus proche de ce qu’on écrirait dans un outil XPath dédié. Déclarer les namespaces avant toute requête évite la majorité des résultats vides.

A lire aussi : Comment extraire et télécharger l'audio de YouTube en toute simplicité ?

Jeune développeuse travaillant sur du parsing XML en Python avec XPath depuis son canapé, écran de terminal visible

ElementTree ou lxml pour lire du XML en Python : choisir selon le cas

La bibliothèque standard xml.etree.ElementTree est disponible sans installation, et ses méthodes find, findall, findtext couvrent les cas simples : sélectionner des enfants directs, filtrer par attribut, descendre dans l’arbre avec des chemins relatifs. Pour un script rapide sur un petit fichier, c’est suffisant.

En revanche, ElementTree ne supporte qu’un sous-ensemble restreint de XPath 1.0. Pas d’axes avancés (comme following-sibling), pas de fonctions comme contains() ou starts-with(), pas de prédicats complexes combinés. Dès qu’on a besoin de filtrer sur un texte partiel ou de naviguer entre frères, on est bloqué.

C’est là que lxml prend le relais. La bibliothèque expose l’implémentation libxml2, avec un support XPath 1.0 complet et les extensions EXSLT. On peut écrire des expressions comme //facture[contains(@status, 'paid')] sans contournement. Le coût : une dépendance C à installer, ce qui peut poser problème sur certains environnements contraints.

Critères pour trancher entre les deux

  • Le fichier XML fait moins de quelques mégaoctets et les requêtes restent simples (chemins directs, filtres par attribut) : etree de la bibliothèque standard convient.
  • On a besoin de fonctions XPath avancées, de prédicats textuels ou d’axes de navigation : lxml est le bon choix.
  • Le script tourne dans un conteneur Docker ou un environnement sans compilateur C : vérifier que lxml s’installe proprement, sinon rester sur la stdlib avec des contournements en Python pur.

iterparse pour les gros fichiers XML : extraire sans saturer la mémoire

Charger un fichier XML de plusieurs centaines de mégaoctets avec parse() construit l’arbre complet en mémoire. Sur un serveur de production qui traite des flux métiers (logs, exports de données, factures en lot), ça peut faire tomber le process.

iterparse() résout ce problème en traitant le fichier en streaming. On itère sur les événements (start, end) et on extrait les données au fil de la lecture. Le point technique à ne pas rater : libérer les nœuds déjà traités avec elem.clear() après extraction, sinon l’arbre continue de grossir en mémoire malgré le mode incrémental.

Voici le squelette type :

for event, elem in ET.iterparse("export.xml", events=("end",)):
    if elem.tag == "{namespace}Invoice":
        # extraire les données
        elem.clear()

Cette approche fonctionne aussi bien avec xml.etree.ElementTree qu’avec lxml.etree. La version lxml offre quelques options supplémentaires (récupération d’erreurs, gestion des entités), mais le principe reste identique.

Vue aérienne d'un bureau avec cahier annoté de structures XML et requêtes XPath, smartphone et tasse de café pour illustrer Python

Expressions XPath concrètes pour extraire les bons éléments

On va partir de cas qu’on rencontre sur des fichiers réels, pas d’exemples avec des pays et des voisins.

Filtrer des factures par attribut status

Sur un XML de type Factur-X ou assimilé, chaque facture porte un attribut de statut. Pour récupérer uniquement les factures validées :

Avec lxml : root.xpath("//facture[@status='validated']", namespaces=ns)

Avec etree (si le namespace est déclaré) : root.findall(".//{ns}facture[@status='validated']")

Toujours tester l’expression sur un extrait du fichier avant de lancer sur le jeu complet. Un attribut mal orthographié ou un namespace oublié renvoie silencieusement une liste vide.

Récupérer le texte d’un élément imbriqué

Pour extraire le montant d’une ligne de facture enfouie à trois niveaux de profondeur, on utilise un chemin descendant : .//ligne/montant/text() avec lxml, ou findtext(".//ligne/montant") avec etree. La différence : findtext renvoie directement une chaîne (ou None), alors que xpath avec text() renvoie une liste.

Sélectionner le n-ième enfant

XPath utilise des index à base 1. Pour le deuxième élément ligne d’une facture : .//facture/ligne[2]. Avec etree, cette syntaxe fonctionne aussi via findall. C’est un des rares prédicats positionnels supportés par la bibliothèque standard.

Erreurs fréquentes en lecture XML Python et comment les éviter

Après avoir travaillé sur plusieurs intégrations de flux XML, on retrouve toujours les mêmes problèmes.

  • Namespace oublié : la requête renvoie une liste vide sans erreur. Vérifier systématiquement les namespaces déclarés en haut du fichier XML avant d’écrire la moindre expression.
  • Encodage mal déclaré : un fichier annoncé en UTF-8 mais contenant des caractères ISO-8859-1 fait planter le parseur. Ouvrir le fichier en mode binaire (rb) et laisser le parseur détecter l’encodage via la déclaration XML.
  • Confondre find et findall : find renvoie le premier élément trouvé (ou None), findall renvoie une liste. Utiliser l’un à la place de l’autre provoque des AttributeError en cascade.
  • Oublier elem.clear() dans une boucle iterparse : la consommation mémoire explose progressivement, et le script finit par être tué par l’OS sur les gros fichiers.

Le choix entre etree et lxml se fait tôt dans le projet, parce qu’il conditionne la syntaxe des expressions XPath dans tout le code. Migrer de l’un vers l’autre en cours de route reste faisable (les API sont proches), mais les expressions avancées écrites pour lxml ne tourneront pas sur la stdlib sans réécriture. Mieux vaut poser ce choix dès la lecture du format XML cible, en fonction de la complexité des requêtes à écrire.

Quelques actus

Zifub : le Meilleur Site de Streaming Gratuit Disponible

Le streaming est pour l’instant très prisé. Il permet d’économiser de l’espace sur son PC, sur les disques

apprendre à partager vos documents avec d’autres

La recrudescence du travail à distance a su redéfinir les habitudes chez les particuliers et dans le fonctionnement