La bibliothèque standard de Python est livrée avec un solide parseur XML — aucun pip install requis. xml.etree.ElementTree gère la grande majorité des XML du monde réel : flux RSS, réponses SOAP, fichiers de configuration, fichiers de ressources Android, POMs Maven. Vous n'avez besoin de recourir à lxml que lorsque vous atteignez la validation de schéma XSD, des XPath complexes, ou des fichiers vraiment massifs. Parcourons les deux, avec des exemples concrets.

Bases d'ElementTree — Analyser depuis une chaîne ou un fichier

Le module xml.etree.ElementTree vous offre deux points d'entrée : fromstring() pour analyser des chaînes XML, et parse() pour lire directement depuis un fichier. Voici un exemple pratique utilisant une structure de flux RSS :

python
import xml.etree.ElementTree as ET

rss_xml = """<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Engineering Blog</title>
    <link>https://blog.example.com</link>
    <description>Articles for developers</description>
    <item>
      <title>Understanding Database Indexes</title>
      <link>https://blog.example.com/db-indexes</link>
      <pubDate>Mon, 15 Jan 2024 09:00:00 GMT</pubDate>
      <category>Database</category>
    </item>
    <item>
      <title>REST API Design Patterns</title>
      <link>https://blog.example.com/rest-patterns</link>
      <pubDate>Wed, 17 Jan 2024 09:00:00 GMT</pubDate>
      <category>API</category>
    </item>
  </channel>
</rss>"""

# Analyser depuis une chaîne
root = ET.fromstring(rss_xml)

# Analyser depuis un fichier (alternative)
# tree = ET.parse('feed.xml')
# root = tree.getroot()

print(root.tag)           # rss
print(root.attrib)        # {'version': '2.0'}

channel = root.find('channel')
print(channel.find('title').text)  # Engineering Blog

find, findall et findtext — Rechercher dans l'arbre

Ces trois méthodes sont vos principaux outils pour extraire des données. Elles acceptent toutes une expression de chemin simple (comme un XPath limité) pour naviguer dans l'arbre d'éléments :

python
import xml.etree.ElementTree as ET

root = ET.fromstring(rss_xml)
channel = root.find('channel')

# find() — retourne le premier élément correspondant, ou None
first_item = channel.find('item')
print(first_item.find('title').text)  # Understanding Database Indexes

# findall() — retourne une liste de tous les éléments correspondants
items = channel.findall('item')
print(len(items))  # 2

for item in items:
    title = item.findtext('title')      # findtext() retourne .text directement
    link = item.findtext('link')
    pub_date = item.findtext('pubDate')
    print(f"{title} — {pub_date}")

# Chemin imbriqué avec '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']

# findtext() avec une valeur par défaut (évite AttributeError sur les éléments manquants)
author = channel.findtext('item/author', default='Auteur inconnu')
print(author)  # Auteur inconnu
Utilisez findtext() avec une valeur par défaut. Si vous utilisez find().text et que l'élément n'existe pas, find() retourne None et .text lève une AttributeError. findtext('tag', default='') gère les éléments manquants proprement — bien plus sûr lors de l'analyse de XML provenant de sources externes.

Lire les attributs

python
import xml.etree.ElementTree as ET

xml_str = """<catalog>
  <product id="P001" featured="true">
    <name>Mechanical Keyboard</name>
    <price currency="USD">189.00</price>
  </product>
  <product id="P002" featured="false">
    <name>USB-C Hub</name>
    <price currency="USD">49.99</price>
  </product>
</catalog>"""

root = ET.fromstring(xml_str)

for product in root.findall('product'):
    product_id = product.get('id')           # get() pour les attributs
    featured = product.get('featured', 'false')  # avec valeur par défaut
    name = product.findtext('name')
    price_el = product.find('price')
    price = float(price_el.text)
    currency = price_el.get('currency')

    print(f"{product_id}: {name} — {currency} {price} (en vedette: {featured})")

Gérer les espaces de noms XML

Les espaces de noms sont la partie de l'analyse XML qui fait soupirer la plupart des développeurs. Dans ElementTree, les URI d'espaces de noms apparaissent entre accolades dans les noms de balises : {http://...}tagname. Voici comment les gérer proprement :

python
import xml.etree.ElementTree as ET

soap_xml = """<?xml version="1.0"?>
<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:inv="http://www.example.com/invoice">
  <soap:Body>
    <inv:GetInvoiceResponse>
      <inv:InvoiceId>INV-2024-0042</inv:InvoiceId>
      <inv:Amount currency="EUR">1250.00</inv:Amount>
      <inv:Status>Paid</inv:Status>
    </inv:GetInvoiceResponse>
  </soap:Body>
</soap:Envelope>"""

root = ET.fromstring(soap_xml)

# ElementTree développe les préfixes d'espaces de noms en URI entre accolades
# Vous pouvez définir une carte d'espaces de noms pour des recherches XPath plus propres
ns = {
    'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
    'inv': 'http://www.example.com/invoice'
}

# Utiliser la carte d'espaces de noms dans find/findall
invoice_id = root.find('.//inv:InvoiceId', ns).text
amount_el = root.find('.//inv:Amount', ns)
status = root.findtext('.//inv:Status', namespaces=ns)

print(invoice_id)                    # INV-2024-0042
print(amount_el.text)                # 1250.00
print(amount_el.get('currency'))     # EUR
print(status)                        # Paid

Construire du XML par programmation

ElementTree vous permet également de construire du XML de zéro — utile lorsque vous devez créer des requêtes SOAP ou générer une sortie XML :

python
import xml.etree.ElementTree as ET

# Construire un document de commande
order = ET.Element('order', id='ORD-9981', status='pending')

customer = ET.SubElement(order, 'customer')
ET.SubElement(customer, 'name').text = 'Jane Smith'
ET.SubElement(customer, 'email').text = '[email protected]'

items = ET.SubElement(order, 'items')
for product_id, name, qty, price in [
    ('P001', 'Mechanical Keyboard', 1, 189.00),
    ('P002', 'USB-C Hub', 2, 49.99),
]:
    item = ET.SubElement(items, 'item', productId=product_id, qty=str(qty))
    ET.SubElement(item, 'name').text = name
    ET.SubElement(item, 'price', currency='USD').text = str(price)

# Sérialiser en chaîne
ET.indent(order, space='  ')  # Python 3.9+ — formatage en place
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)

iterparse — Diffuser en flux de grands fichiers XML

Pour les grands fichiers XML (dizaines de Mo ou plus), charger tout le document en mémoire avec parse() est coûteux. iterparse() diffuse le fichier en flux (dans un esprit similaire à l'approche SAX pilotée par événements) et déclenche des événements au fur et à mesure que les éléments sont rencontrés, vous permettant de traiter et de supprimer des éléments au fur et à mesure :

python
import xml.etree.ElementTree as ET

def process_large_feed(filepath):
    """Traiter un grand flux RSS/Atom sans tout charger en mémoire."""
    articles = []

    for event, elem in ET.iterparse(filepath, events=('end',)):
        if elem.tag == 'item':
            articles.append({
                'title': elem.findtext('title', ''),
                'link': elem.findtext('link', ''),
                'pub_date': elem.findtext('pubDate', ''),
            })
            # Crucial : effacer l'élément après traitement pour libérer la mémoire
            elem.clear()

        if len(articles) >= 1000:
            yield from articles
            articles.clear()

    yield from articles  # yield les éléments restants

for article in process_large_feed('large_feed.xml'):
    print(article['title'])

L'appel elem.clear() après le traitement de chaque élément est la clé pour maintenir une utilisation mémoire stable quelle que soit la taille du fichier. Sans cela, ElementTree accumule tous les éléments analysés en mémoire et vous perdez le bénéfice du streaming.

lxml — Quand vous avez besoin de plus de puissance

La bibliothèque lxml est une bibliothèque XML rapide basée sur C qui étend l'API d'ElementTree avec la prise en charge complète de XPath 1.0, la validation de schéma XSD et les transformations XSLT. Installez-la avec pip install lxml :

python
from lxml import etree

# lxml utilise la même API qu'ElementTree dans la plupart des cas
root = etree.fromstring(rss_xml.encode())  # lxml nécessite des bytes, pas str

# XPath 1.0 complet — bien plus puissant que le sous-ensemble d'ElementTree
items = root.xpath('//item[position() <= 2]/title/text()')
print(items)  # ['Understanding Database Indexes', 'REST API Design Patterns']

# XPath avec prédicats — obtenir les éléments d'une catégorie spécifique
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items)  # ['Understanding Database Indexes']

# Validation de schéma XSD
xsd_doc = etree.parse('schema.xsd')
schema = etree.XMLSchema(xsd_doc)
xml_doc = etree.parse('data.xml')

if schema.validate(xml_doc):
    print("Valide !")
else:
    for error in schema.error_log:
        print(f"Ligne {error.line}: {error.message}")

Outils utiles pour le travail XML

Lors de la création d'intégrations XML Python, ces outils navigateur aident du côté des données : Formateur XML pour rendre lisibles les réponses API brutes, Validateur XML pour vérifier la bonne forme avant de câbler votre parseur, et XML vers JSON quand vous préférez travailler avec des dicts plutôt que des éléments.

Conclusion

Le module xml.etree.ElementTree de Python couvre la plupart des scénarios XML du monde réel : utilisez find() et findall() avec une carte d'espaces de noms pour SOAP et les flux avec espaces de noms, findtext() avec des valeurs par défaut pour éviter les AttributeError sur les éléments manquants, et iterparse() pour les grands fichiers que vous ne pouvez pas charger entièrement en mémoire. Recourez à lxml quand vous avez besoin de la validation XSD ou du langage d'expression XPath complet. La bibliothèque standard gère tout le reste très bien.