Pythons Standardbibliothek enthält einen soliden XML-Parser — kein pip install erforderlich.
xml.etree.ElementTree bewältigt die große Mehrheit des realen XML:
RSS-Feeds,
SOAP-Antworten, Konfigurationsdateien, Android-Ressourcendateien, Maven-POMs. Auf
lxml
müssen Sie nur zurückgreifen, wenn Sie
XSD-Schema-Validierung,
komplexes XPath oder wirklich riesige Dateien benötigen. Schauen wir uns beide mit echten Beispielen an.
ElementTree-Grundlagen — Parsen aus einem String oder einer Datei
Das
xml.etree.ElementTree-Modul
bietet zwei Einstiegspunkte: fromstring() zum Parsen von XML-Strings und
parse() zum direkten Lesen aus einer Datei. Hier ist ein praktisches Beispiel mit
einer RSS-Feed-Struktur:
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>"""
# Aus einem String parsen
root = ET.fromstring(rss_xml)
# Aus einer Datei parsen (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 Blogfind, findall und findtext — Den Baum durchsuchen
Diese drei Methoden sind Ihre wichtigsten Werkzeuge zur Datenextraktion. Alle akzeptieren einen einfachen Pfadausdruck (ähnlich einem eingeschränkten XPath) zur Navigation im Elementbaum:
import xml.etree.ElementTree as ET
root = ET.fromstring(rss_xml)
channel = root.find('channel')
# find() — gibt das erste passende Element zurück, oder None
first_item = channel.find('item')
print(first_item.find('title').text) # Understanding Database Indexes
# findall() — gibt eine Liste aller passenden Elemente zurück
items = channel.findall('item')
print(len(items)) # 2
for item in items:
title = item.findtext('title') # findtext() gibt .text direkt zurück
link = item.findtext('link')
pub_date = item.findtext('pubDate')
print(f"{title} — {pub_date}")
# Verschachtelter Pfad mit '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']
# findtext() mit Standardwert (vermeidet AttributeError bei fehlenden Elementen)
author = channel.findtext('item/author', default='Unknown Author')
print(author) # Unknown Authorfindtext() mit einem Standardwert. Wenn Sie find().text
verwenden und das Element nicht existiert, gibt find() None zurück und .text
wirft einen AttributeError. findtext('tag', default='') behandelt fehlende
Elemente problemlos — viel sicherer beim Parsen von XML aus externen Quellen.Attribute lesen
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() für Attribute
featured = product.get('featured', 'false') # mit Standardwert
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} (featured: {featured})")XML-Namespaces verarbeiten
Namespaces
sind der Teil des XML-Parsens, der die meisten Entwickler stöhnen lässt. In ElementTree
erscheinen Namespace-URIs in geschweiften Klammern in Tag-Namen: {http://...}tagname. So
gehen Sie damit sauber um:
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 erweitert Namespace-Präfixe auf URIs in geschweiften Klammern
# Sie können eine Namespace-Map für sauberere XPath-ähnliche Suchen definieren
ns = {
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'inv': 'http://www.example.com/invoice'
}
# Die Namespace-Map in find/findall verwenden
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) # PaidXML programmatisch erstellen
ElementTree ermöglicht auch das Erstellen von XML von Grund auf — nützlich, wenn Sie SOAP-Anfragen erstellen oder XML-Ausgaben generieren müssen:
import xml.etree.ElementTree as ET
# Ein Bestelldokument erstellen
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)
# Als String serialisieren
ET.indent(order, space=' ') # Python 3.9+ — formatiert in-place
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)iterparse — Große XML-Dateien streamen
Bei großen XML-Dateien (mehrere dutzend MB oder mehr) ist das Laden des gesamten Dokuments in den Speicher mit
parse() aufwendig.
iterparse()
streamt die Datei (ähnlich dem ereignisgesteuerten SAX-Ansatz) und feuert Events, wenn Elemente
gefunden werden, sodass Sie Elemente während der Verarbeitung verwerfen können:
import xml.etree.ElementTree as ET
def process_large_feed(filepath):
"""Verarbeitet einen großen RSS/Atom-Feed ohne alles in den Speicher zu laden."""
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', ''),
})
# Wichtig: Element nach der Verarbeitung löschen, um Speicher freizugeben
elem.clear()
if len(articles) >= 1000:
yield from articles
articles.clear()
yield from articles # verbleibende Einträge zurückgeben
for article in process_large_feed('large_feed.xml'):
print(article['title'])Der elem.clear()-Aufruf nach der Verarbeitung jedes Elements ist der Schlüssel,
um die Speichernutzung unabhängig von der Dateigröße konstant zu halten. Ohne ihn sammelt ElementTree alle
geparsten Elemente im Speicher an, und Sie verlieren den Vorteil des Streamings.
lxml — Wenn Sie mehr Leistung brauchen
Die
lxml-Bibliothek
ist eine schnelle C-basierte XML-Bibliothek, die die ElementTree-API mit vollständiger
XPath-1.0-Unterstützung,
XSD-Schema-Validierung und XSLT-Transformationen erweitert. Installieren Sie sie mit pip install lxml:
from lxml import etree
# lxml verwendet in den meisten Fällen die gleiche API wie ElementTree
root = etree.fromstring(rss_xml.encode()) # lxml benötigt bytes, nicht str
# Vollständiges XPath 1.0 — viel mächtiger als ElementTrees Teilmenge
items = root.xpath('//item[position() <= 2]/title/text()')
print(items) # ['Understanding Database Indexes', 'REST API Design Patterns']
# XPath mit Prädikaten — Elemente einer bestimmten Kategorie abrufen
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items) # ['Understanding Database Indexes']
# XSD-Schema-Validierung
xsd_doc = etree.parse('schema.xsd')
schema = etree.XMLSchema(xsd_doc)
xml_doc = etree.parse('data.xml')
if schema.validate(xml_doc):
print("Valid!")
else:
for error in schema.error_log:
print(f"Line {error.line}: {error.message}")Nützliche Tools für die XML-Arbeit
Beim Aufbau von Python-XML-Integrationen helfen diese Browser-Tools auf der Datenseite: XML-Formatierer zum Lesbar-Machen roher API-Antworten, XML-Validator zur Wohlgeformtheitsprüfung vor der Parser-Anbindung, und XML zu JSON, wenn Sie lieber mit Dicts als mit Elementen arbeiten.
Fazit
Pythons xml.etree.ElementTree deckt die meisten realen XML-Szenarien ab:
Verwenden Sie find() und findall() mit einer Namespace-Map für SOAP und
namespace-behaftete Feeds, findtext() mit Standardwerten, um AttributeError
bei fehlenden Elementen zu vermeiden, und iterparse() für große Dateien, die Sie nicht
auf einmal in den Speicher laden können. Greifen Sie auf lxml zurück, wenn Sie
XSD-Validierung oder die vollständige XPath-Ausdruckssprache benötigen.
Die Standardbibliothek reicht für alles andere vollkommen aus.