Pythons standardbibliotek leveres med en solid XML-parser — ingen pip install nødvendig. xml.etree.ElementTree håndterer de aller fleste virkelige XML-tilfeller: RSS-feeder, SOAP-svar, konfigurasjonsfiler, Android-ressursfiler, Maven POM-er. Du trenger bare å nå etter lxml når du støter på XSD-skjemavalidering, kompleks XPath eller virkelig store filer. La oss gå gjennom begge med virkelige eksempler.

ElementTree-grunnleggende — Parsing fra streng eller fil

Modulen xml.etree.ElementTree gir deg to inngangspunkter: fromstring() for parsing av XML-strenger og parse() for direkte lesing fra en fil. Her er et praktisk eksempel med en RSS-feed-struktur:

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>"""

# Parser fra en streng
root = ET.fromstring(rss_xml)

# Parser fra en fil (alternativ)
# 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 og findtext — Søk i treet

Disse tre metodene er dine primære verktøy for å hente ut data. De aksepterer alle et enkelt stiuttrykk (som en begrenset XPath) for å navigere i elementtreet:

python
import xml.etree.ElementTree as ET

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

# find() — returnerer det første samsvarende elementet, eller None
first_item = channel.find('item')
print(first_item.find('title').text)  # Understanding Database Indexes

# findall() — returnerer en liste over alle samsvarende elementer
items = channel.findall('item')
print(len(items))  # 2

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

# Nestet sti med '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']

# findtext() med en standardverdi (unngår AttributeError for manglende elementer)
author = channel.findtext('item/author', default='Unknown Author')
print(author)  # Unknown Author
Bruk findtext() med en standardverdi. Hvis du bruker find().text og elementet ikke finnes, returnerer find() None og .text kaster en AttributeError. findtext('tag', default='') håndterer manglende elementer elegant — mye tryggere ved parsing av XML fra eksterne kilder.

Lese attributter

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() for attributter
    featured = product.get('featured', 'false')  # med standardverdi
    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})")

Håndtering av XML-navnerom

Navnerom er den delen av XML-parsing som får de fleste utviklere til å sukke. I ElementTree vises navnerom-URI-er i krøllede parenteser i taggnavn: {http://...}taggnavn. Slik håndterer du dem på en ryddig måte:

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 utvider navnerom-prefikser til URI-er i krøllede parenteser
# Du kan definere et navneromskart for renere XPath-lignende søk
ns = {
    'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
    'inv': 'http://www.example.com/invoice'
}

# Bruk navneroms kartet i 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

Bygge XML programmatisk

ElementTree lar deg også konstruere XML fra bunnen av — nyttig når du trenger å bygge SOAP-forespørsler eller generere XML-utdata:

python
import xml.etree.ElementTree as ET

# Bygg et bestillingsdokument
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)

# Serialiser til streng
ET.indent(order, space='  ')  # Python 3.9+ — formatér på stedet
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)

iterparse — Strømming av store XML-filer

For store XML-filer (titalls MB eller mer) er det kostbart å laste inn hele dokumentet i minnet med parse(). iterparse() strømmer filen (lignende den hendelsesdrevne SAX-tilnærmingen) og utløser hendelser når elementer oppdages, slik at du kan behandle og kaste elementer underveis:

python
import xml.etree.ElementTree as ET

def process_large_feed(filepath):
    """Behandle en stor RSS/Atom-feed uten å laste alt inn i minnet."""
    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', ''),
            })
            # Kritisk: tøm elementet etter behandling for å frigjøre minne
            elem.clear()

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

    yield from articles  # yield eventuelle gjenværende

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

Kallet elem.clear() etter behandling av hvert element er nøkkelen til å holde minnebruken konstant uavhengig av filstørrelse. Uten det akkumulerer ElementTree alle parsede elementer i minnet og du mister fordelen med strømming.

lxml — Når du trenger mer kraft

Biblioteket lxml er et raskt C-basert XML-bibliotek som utvider ElementTree-API-et med full XPath 1.0-støtte, XSD-skjemavalidering og XSLT-transformasjoner. Installer med pip install lxml:

python
from lxml import etree

# lxml bruker den samme API-en som ElementTree i de fleste tilfeller
root = etree.fromstring(rss_xml.encode())  # lxml trenger bytes, ikke str

# Full XPath 1.0 — mye kraftigere enn ElementTree-delmengden
items = root.xpath('//item[position() <= 2]/title/text()')
print(items)  # ['Understanding Database Indexes', 'REST API Design Patterns']

# XPath med predikater — hent elementer fra en bestemt kategori
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items)  # ['Understanding Database Indexes']

# XSD-skjemavalidering
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}")

Nyttige verktøy for XML-arbeid

Når du bygger Python XML-integrasjoner, hjelper disse nettleserverktøyene med datasiden: XML Formatter for å gjøre rå API-svar lesbare, XML Validator for å sjekke velformethet før du kobler opp parseren din, og XML to JSON når du heller vil jobbe med dicts enn elementer.

Oppsummering

Pythons xml.etree.ElementTree dekker de fleste virkelige XML-scenarier: bruk find() og findall() med et navneromskart for SOAP og navneroms-feeder, findtext() med standardverdier for å unngå AttributeError for manglende elementer, og iterparse() for store filer du ikke kan laste inn i minnet på en gang. Nå etter lxml når du trenger XSD-validering eller det fullstendige XPath-uttrykksspråket. Standardbiblioteket håndterer alt annet fint.