De standaardbibliotheek van Python bevat een solide XML-parser — geen pip install nodig.
xml.etree.ElementTree verwerkt de grote meerderheid van reëel XML:
RSS-feeds,
SOAP-responses, configuratiebestanden, Android-resourcebestanden, Maven POM's. Je hoeft pas te grijpen naar
lxml
wanneer je XSD-schemavalidatie,
complexe XPath of echt enorme bestanden nodig hebt. Laten we beide behandelen, met echte voorbeelden.
ElementTree Basis — Parsen vanuit een String of Bestand
De module
xml.etree.ElementTree
geeft je twee toegangspunten: fromstring() voor het parsen van XML-strings, en
parse() voor het direct lezen vanuit een bestand. Hier is een praktijkvoorbeeld met
een RSS-feedstructuur:
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>"""
# Parseer vanuit een string
root = ET.fromstring(rss_xml)
# Parseer vanuit een bestand (alternatief)
# 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 en findtext — Zoeken in de Boom
Deze drie methoden zijn je primaire gereedschappen voor het extraheren van data. Ze accepteren allemaal een eenvoudige padexpressie (zoals een beperkte XPath) om door de elementenboom te navigeren:
import xml.etree.ElementTree as ET
root = ET.fromstring(rss_xml)
channel = root.find('channel')
# find() — retourneert het eerste overeenkomende element, of None
first_item = channel.find('item')
print(first_item.find('title').text) # Understanding Database Indexes
# findall() — retourneert een lijst van alle overeenkomende elementen
items = channel.findall('item')
print(len(items)) # 2
for item in items:
title = item.findtext('title') # findtext() retourneert .text direct
link = item.findtext('link')
pub_date = item.findtext('pubDate')
print(f"{title} — {pub_date}")
# Genest pad met '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']
# findtext() met een standaardwaarde (vermijdt AttributeError bij ontbrekende elementen)
author = channel.findtext('item/author', default='Unknown Author')
print(author) # Unknown Authorfindtext() met een standaardwaarde. Als je find().text
gebruikt en het element niet bestaat, retourneert find() None en gooit .text
een AttributeError. findtext('tag', default='') verwerkt ontbrekende
elementen soepel — veel veiliger bij het parsen van XML van externe bronnen.Attributen Lezen
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() voor attributen
featured = product.get('featured', 'false') # met standaardwaarde
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 Verwerken
Namespaces
zijn het deel van XML-parsen waar de meeste ontwikkelaars over klagen. In ElementTree
verschijnen namespace-URI's tussen accolades in tagnamen: {http://...}tagname. Zo
ga je er netjes mee om:
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 breidt namespace-prefixen uit naar URI's tussen accolades
# Je kunt een namespace-map definiëren voor overzichtelijkere XPath-zoekopdrachten
ns = {
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'inv': 'http://www.example.com/invoice'
}
# Gebruik de namespace-map in 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) # PaidXML Programmatisch Opbouwen
ElementTree stelt je ook in staat XML van scratch op te bouwen — handig wanneer je SOAP-verzoeken moet opbouwen of XML-uitvoer moet genereren:
import xml.etree.ElementTree as ET
# Bouw een besteldocument
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)
# Serialiseer naar string
ET.indent(order, space=' ') # Python 3.9+ — mooi opmaken in-place
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)iterparse — Grote XML-bestanden Streamen
Voor grote XML-bestanden (tientallen MB of meer) is het laden van het hele document in geheugen met
parse() duur.
iterparse()
streamt het bestand (vergelijkbaar in geest met de event-gestuurde SAX-aanpak) en vuurt events af wanneer elementen worden
gevonden, zodat je elementen kunt verwerken en weggooien terwijl je verder gaat:
import xml.etree.ElementTree as ET
def process_large_feed(filepath):
"""Verwerk een grote RSS/Atom-feed zonder alles in geheugen te 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', ''),
})
# Cruciaal: wis het element na verwerking om geheugen vrij te maken
elem.clear()
if len(articles) >= 1000:
yield from articles
articles.clear()
yield from articles # geef eventuele resterende items terug
for article in process_large_feed('large_feed.xml'):
print(article['title'])De aanroep elem.clear() na het verwerken van elk element is de sleutel tot het vlak houden van
geheugengebruik ongeacht de bestandsgrootte. Zonder dit accumuleert ElementTree alle geparseerde elementen
in geheugen en verlies je het voordeel van streamen.
lxml — Wanneer Je Meer Kracht Nodig Hebt
De
lxml-bibliotheek
is een snelle C-gebaseerde XML-bibliotheek die de API van ElementTree uitbreidt met volledige
XPath 1.0-ondersteuning,
XSD-schemavalidatie en XSLT-transformaties. Installeer het met pip install lxml:
from lxml import etree
# lxml gebruikt in de meeste gevallen dezelfde API als ElementTree
root = etree.fromstring(rss_xml.encode()) # lxml heeft bytes nodig, geen str
# Volledige XPath 1.0 — veel krachtiger dan de subset van ElementTree
items = root.xpath('//item[position() <= 2]/title/text()')
print(items) # ['Understanding Database Indexes', 'REST API Design Patterns']
# XPath met predicaten — haal items op uit een specifieke categorie
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items) # ['Understanding Database Indexes']
# XSD-schemavalidatie
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}")Handige Tools voor XML-werk
Bij het bouwen van Python XML-integraties helpen deze browsertools met de datakant: XML Formatter om ruwe API-responses leesbaar te maken, XML Validator om goed gevormd zijn te controleren vóór het instellen van je parser, en XML naar JSON wanneer je liever met dicts werkt dan met elementen.
Samenvatting
Python's xml.etree.ElementTree dekt de meeste reële XML-scenario's:
gebruik find() en findall() met een namespace-map voor SOAP en naamruimte-feeds,
findtext() met standaardwaarden om AttributeError bij ontbrekende elementen te vermijden,
en iterparse() voor grote bestanden die je niet in één keer in geheugen kunt laden.
Grijp naar lxml wanneer je XSD-validatie of de volledige XPath-expressietaal nodig hebt.
De standaardbibliotheek regelt de rest prima.