Pythons standardbibliotek levereras med en solid XML-tolk — ingen pip install krävs. xml.etree.ElementTree hanterar den stora majoriteten av verklig XML: RSS-flöden, SOAP-svar, konfigurationsfiler, Android-resursfiler, Maven POM:ar. Du behöver bara lxml när du stöter på XSD-schemavalidering, komplex XPath eller riktigt stora filer. Låt oss gå igenom båda med verkliga exempel.

ElementTree-grunder — Tolka från sträng eller fil

Modulen xml.etree.ElementTree ger dig två ingångspunkter: fromstring() för att tolka XML-strängar och parse() för att läsa direkt från en fil. Här är ett praktiskt exempel med en RSS-flödesstruktur:

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

# Tolka från en sträng
root = ET.fromstring(rss_xml)

# Tolka från 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 och findtext — Söka i trädet

Dessa tre metoder är dina primära verktyg för att extrahera data. Alla accepterar ett enkelt sökvägsuttryck (som en begränsad XPath) för att navigera i elementträdet:

python
import xml.etree.ElementTree as ET

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

# find() — returnerar det första matchande elementet, eller None
first_item = channel.find('item')
print(first_item.find('title').text)  # Understanding Database Indexes

# findall() — returnerar en lista med alla matchande element
items = channel.findall('item')
print(len(items))  # 2

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

# Kapslad sökväg med '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']

# findtext() med ett standardvärde (undviker AttributeError för saknade element)
author = channel.findtext('item/author', default='Unknown Author')
print(author)  # Unknown Author
Använd findtext() med ett standardvärde. Om du använder find().text och elementet inte finns returnerar find() None och .text kastar ett AttributeError. findtext('tag', default='') hanterar saknade element smidigt — mycket säkrare när du tolkar XML från externa källor.

Läsa attribut

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() för attribut
    featured = product.get('featured', 'false')  # med standardvärde
    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})")

Hantera XML-namnrymder

Namnrymder är den del av XML-tolkning som får de flesta utvecklare att stöna. I ElementTree visas namnrymds-URI:er inom klammerparentes i taggnamn: {http://...}taggnamn. Så här hanterar du dem på ett rent sätt:

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 expanderar namnrymdsprefix till URI:er i klammerparentes
# Du kan definiera en namnrymdskarta för renare XPath-liknande sökningar
ns = {
    'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
    'inv': 'http://www.example.com/invoice'
}

# Använd namnrymdskartan 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

Bygga XML programmatiskt

ElementTree låter dig också konstruera XML från grunden — användbart när du behöver bygga SOAP-förfrågningar eller generera XML-utdata:

python
import xml.etree.ElementTree as ET

# Bygg ett beställningsdokument
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)

# Serialisera till sträng
ET.indent(order, space='  ')  # Python 3.9+ — snyggt formatera på plats
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)

iterparse — Strömma stora XML-filer

För stora XML-filer (tiotals MB eller mer) är det dyrt att läsa in hela dokumentet i minnet med parse(). iterparse() strömmar filen (liknande det händelsedrivna SAX-tillvägagångssättet) och utlöser händelser när element påträffas, vilket låter dig bearbeta och kassera element allteftersom:

python
import xml.etree.ElementTree as ET

def process_large_feed(filepath):
    """Bearbeta ett stort RSS/Atom-flöde utan att läsa in allt 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', ''),
            })
            # Kritiskt: rensa elementet efter bearbetning för att frigöra minne
            elem.clear()

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

    yield from articles  # yield eventuella återstående

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

Anropet elem.clear() efter bearbetning av varje element är nyckeln till att hålla minnesanvändningen konstant oavsett filstorlek. Utan det ackumulerar ElementTree alla tolkade element i minnet och du förlorar fördelen med strömning.

lxml — När du behöver mer kraft

Biblioteket lxml är ett snabbt C-baserat XML-bibliotek som utökar ElementTree-API:t med fullt XPath 1.0-stöd, XSD-schemavalidering och XSLT-transformationer. Installera med pip install lxml:

python
from lxml import etree

# lxml använder samma API som ElementTree i de flesta fall
root = etree.fromstring(rss_xml.encode())  # lxml behöver bytes, inte str

# Fullt XPath 1.0 — mycket kraftfullare än ElementTree-delmängden
items = root.xpath('//item[position() <= 2]/title/text()')
print(items)  # ['Understanding Database Indexes', 'REST API Design Patterns']

# XPath med predikat — hämta element från en specifik kategori
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items)  # ['Understanding Database Indexes']

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

Användbara verktyg för XML-arbete

När du bygger Python XML-integrationer hjälper dessa webbläsarverktyg med datasidan: XML Formatter för att göra råa API-svar läsbara, XML Validator för att kontrollera välformning innan du kopplar upp din tolk, och XML to JSON när du hellre vill arbeta med dict:ar istället för element.

Sammanfattning

Pythons xml.etree.ElementTree täcker de flesta verkliga XML-scenarier: använd find() och findall() med en namnrymdskarta för SOAP och namnrymdsbaserade flöden, findtext() med standardvärden för att undvika AttributeError vid saknade element, och iterparse() för stora filer du inte kan läsa in i minnet på en gång. Nå efter lxml när du behöver XSD-validering eller det fullständiga XPath-uttrycksspråket. Standardbiblioteket klarar allt annat utmärkt.