Biblioteka standardowa Pythona zawiera solidny parser XML — bez pip install.
xml.etree.ElementTree obsługuje zdecydowaną większość XML-a spotykanegow praktyce:
kanały RSS,
odpowiedzi SOAP, pliki konfiguracyjne, pliki zasobów Android, pliki Maven POM. Po
lxml
sięgasz dopiero gdy potrzebujesz walidacji schematu
XSD,
złożonego XPath lub naprawdę dużych plików. Przejdźmy przez oba, na realnych przykładach.
Podstawy ElementTree — parsowanie z łańcucha lub pliku
Moduł
xml.etree.ElementTree
daje Ci dwa punkty wejścia: fromstring() do parsowania łańcuchów XML oraz
parse() do odczytu bezpośrednio z pliku. Oto praktyczny przykład z użyciem
struktury kanału RSS:
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>"""
# Parsowanie z łańcucha
root = ET.fromstring(rss_xml)
# Parsowanie z pliku (alternatywa)
# 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 i findtext — przeszukiwanie drzewa
Te trzy metody to Twoje główne narzędzia do wyodrębniania danych. Wszystkie przyjmują proste wyrażenie ścieżki (podobne do ograniczonego XPath) do nawigacji po drzewie elementów:
import xml.etree.ElementTree as ET
root = ET.fromstring(rss_xml)
channel = root.find('channel')
# find() — zwraca pierwszy pasujący element lub None
first_item = channel.find('item')
print(first_item.find('title').text) # Understanding Database Indexes
# findall() — zwraca listę wszystkich pasujących elementów
items = channel.findall('item')
print(len(items)) # 2
for item in items:
title = item.findtext('title') # findtext() zwraca bezpośrednio .text
link = item.findtext('link')
pub_date = item.findtext('pubDate')
print(f"{title} — {pub_date}")
# Zagnieżdżona ścieżka z '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']
# findtext() z domyślną wartością (unika AttributeError przy brakujących elementach)
author = channel.findtext('item/author', default='Unknown Author')
print(author) # Unknown Authorfindtext() z wartością domyślną. Jeśli używasz find().text
i element nie istnieje, find() zwraca None, a .text
zgłasza AttributeError. findtext('tag', default='') obsługuje brakujące
elementy w sposób bezpieczny — znacznie lepsze rozwiązanie przy parsowaniu XML z zewnętrznych źródeł.Odczyt atrybutów
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() do atrybutów
featured = product.get('featured', 'false') # z wartością domyślną
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})")Obsługa przestrzeni nazw XML
Przestrzenie nazw
to ta część parsowania XML, która sprawia, że większość programistów wzdycha. W ElementTree
URI przestrzeni nazw pojawiają się w nawiasach klamrowych w nazwach tagów: {http://...}nazwaElementu. Oto jak
sobie z tym poradzić w czysty sposób:
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 rozszerza prefiksy przestrzeni nazw do URI w nawiasach klamrowych
# Możesz zdefiniować mapę przestrzeni nazw dla czystszych wyszukiwań w stylu XPath
ns = {
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'inv': 'http://www.example.com/invoice'
}
# Użyj mapy przestrzeni nazw w 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) # PaidProgramowe budowanie XML
ElementTree pozwala też konstruować XML od zera — przydatne przy budowaniu żądań SOAP lub generowaniu wyjścia XML:
import xml.etree.ElementTree as ET
# Zbuduj dokument zamówienia
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)
# Serializacja do łańcucha
ET.indent(order, space=' ') # Python 3.9+ — formatowanie w miejscu
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)iterparse — strumieniowe przetwarzanie dużych plików XML
Dla dużych plików XML (dziesiątki MB lub więcej) wczytywanie całego dokumentu do pamięci
za pomocą parse() jest kosztowne.
iterparse()
strumieniuje plik (podobnie do podejścia zdarzeniowego SAX) i uruchamia zdarzenia podczas napotkania elementów,
pozwalając przetwarzać i odrzucać elementy na bieżąco:
import xml.etree.ElementTree as ET
def process_large_feed(filepath):
"""Przetwarzaj duży kanał RSS/Atom bez wczytywania całości do pamięci."""
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', ''),
})
# Kluczowe: wyczyść element po przetworzeniu, aby zwolnić pamięć
elem.clear()
if len(articles) >= 1000:
yield from articles
articles.clear()
yield from articles # zwróć pozostałe
for article in process_large_feed('large_feed.xml'):
print(article['title'])Wywołanie elem.clear() po przetworzeniu każdego elementu to klucz do utrzymania
stałego zużycia pamięci niezależnie od rozmiaru pliku. Bez tego ElementTree gromadzi wszystkie
przetworzone elementy w pamięci i tracisz korzyści ze strumieniowania.
lxml — gdy potrzebujesz więcej możliwości
Biblioteka
lxml
to szybka, oparta na C biblioteka XML, która rozszerza API ElementTree o pełną obsługę
XPath 1.0,
walidację schematu XSD i transformacje XSLT. Zainstaluj za pomocą pip install lxml:
from lxml import etree
# lxml używa tego samego API co ElementTree w większości przypadków
root = etree.fromstring(rss_xml.encode()) # lxml potrzebuje bajtów, nie str
# Pełny XPath 1.0 — znacznie potężniejszy niż podzbiór ElementTree
items = root.xpath('//item[position() <= 2]/title/text()')
print(items) # ['Understanding Database Indexes', 'REST API Design Patterns']
# XPath z predykatami — pobierz elementy z określonej kategorii
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items) # ['Understanding Database Indexes']
# Walidacja schematu XSD
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}")Przydatne narzędzia do pracy z XML
Przy budowaniu integracji XML w Pythonie te narzędzia pomagają po stronie danych: XML Formatter do czytelnego formatowania surowych odpowiedzi API, XML Validator do sprawdzania poprawności składniowej przed konfigurowaniem parsera, oraz XML to JSON, gdy wolisz pracować ze słownikami zamiast z elementami.
Podsumowanie
xml.etree.ElementTree Pythona obsługuje większość rzeczywistych scenariuszy XML:
używaj find() i findall() z mapą przestrzeni nazw dla SOAP i namespaced
kanałów, findtext() z wartościami domyślnymi, by uniknąć AttributeError
przy brakujących elementach, oraz iterparse() dla dużych plików, których nie możesz
wczytać naraz do pamięci. Po lxml sięgaj, gdy potrzebujesz walidacji XSD lub pełnego
języka wyrażeń XPath. Biblioteka standardowa z resztą radzi sobie doskonale.