La biblioteca estándar de Python viene con un sólido parser XML — sin pip install requerido.
xml.etree.ElementTree maneja la gran mayoría de XML del mundo real:
feeds RSS,
respuestas SOAP, archivos de configuración, archivos de recursos Android, POMs de Maven. Solo necesitas
recurrir a lxml
cuando llegas a la validación de esquemas XSD,
XPath complejo, o archivos verdaderamente masivos. Repasemos ambos, con ejemplos reales.
Fundamentos de ElementTree — Analizar desde una cadena o archivo
El módulo
xml.etree.ElementTree
te da dos puntos de entrada: fromstring() para analizar cadenas XML, y
parse() para leer directamente desde un archivo. Aquí hay un ejemplo práctico usando
una estructura de feed 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>"""
# Analizar desde una cadena
root = ET.fromstring(rss_xml)
# Analizar desde un archivo (alternativa)
# 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 y findtext — Buscar en el árbol
Estos tres métodos son tus herramientas principales para extraer datos. Todos aceptan una expresión de ruta simple (como un XPath limitado) para navegar por el árbol de elementos:
import xml.etree.ElementTree as ET
root = ET.fromstring(rss_xml)
channel = root.find('channel')
# find() — devuelve el primer elemento coincidente, o None
first_item = channel.find('item')
print(first_item.find('title').text) # Understanding Database Indexes
# findall() — devuelve una lista de todos los elementos coincidentes
items = channel.findall('item')
print(len(items)) # 2
for item in items:
title = item.findtext('title') # findtext() devuelve .text directamente
link = item.findtext('link')
pub_date = item.findtext('pubDate')
print(f"{title} — {pub_date}")
# Ruta anidada con '/'
all_titles = channel.findall('item/title')
print([el.text for el in all_titles])
# ['Understanding Database Indexes', 'REST API Design Patterns']
# findtext() con un valor por defecto (evita AttributeError en elementos faltantes)
author = channel.findtext('item/author', default='Autor desconocido')
print(author) # Autor desconocidofindtext() con un valor por defecto. Si usas find().text
y el elemento no existe, find() devuelve None y .text
lanza un AttributeError. findtext('tag', default='') maneja los elementos
faltantes con elegancia — mucho más seguro al analizar XML de fuentes externas.Leer atributos
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() para atributos
featured = product.get('featured', 'false') # con valor por defecto
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} (destacado: {featured})")Manejar espacios de nombres XML
Los espacios de nombres
son la parte del análisis XML que hace gruñir a la mayoría de los desarrolladores. En ElementTree,
los URIs de espacios de nombres aparecen entre llaves en los nombres de etiquetas: {http://...}tagname. Así es cómo
manejarlos de forma limpia:
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 expande los prefijos de espacio de nombres a URIs entre llaves
# Puedes definir un mapa de espacios de nombres para búsquedas XPath más limpias
ns = {
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'inv': 'http://www.example.com/invoice'
}
# Usar el mapa de espacios de nombres en 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) # PaidConstruir XML mediante programación
ElementTree también te permite construir XML desde cero — útil cuando necesitas crear peticiones SOAP o generar salida XML:
import xml.etree.ElementTree as ET
# Construir un documento de pedido
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)
# Serializar a cadena
ET.indent(order, space=' ') # Python 3.9+ — sangría en su lugar
xml_output = ET.tostring(order, encoding='unicode', xml_declaration=True)
print(xml_output)iterparse — Transmitir archivos XML grandes
Para archivos XML grandes (decenas de MB o más), cargar todo el documento en memoria con
parse() es costoso.
iterparse()
transmite el archivo (similar en espíritu al enfoque SAX orientado a eventos) y dispara eventos a medida que se encuentran los elementos,
permitiéndote procesar y descartar elementos sobre la marcha:
import xml.etree.ElementTree as ET
def process_large_feed(filepath):
"""Procesar un feed RSS/Atom grande sin cargarlo todo en memoria."""
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', ''),
})
# Crítico: limpiar el elemento después de procesarlo para liberar memoria
elem.clear()
if len(articles) >= 1000:
yield from articles
articles.clear()
yield from articles # yield cualquier restante
for article in process_large_feed('large_feed.xml'):
print(article['title'])La llamada elem.clear() después de procesar cada elemento es la clave para mantener
el uso de memoria estable independientemente del tamaño del archivo. Sin ella, ElementTree acumula todos los elementos analizados
en memoria y pierdes el beneficio del streaming.
lxml — Cuando necesitas más potencia
La biblioteca
lxml
es una biblioteca XML rápida basada en C que extiende la API de ElementTree con soporte completo de
XPath 1.0,
validación de esquemas XSD y transformaciones XSLT. Instálala con pip install lxml:
from lxml import etree
# lxml usa la misma API que ElementTree en la mayoría de los casos
root = etree.fromstring(rss_xml.encode()) # lxml necesita bytes, no str
# XPath 1.0 completo — mucho más potente que el subconjunto de ElementTree
items = root.xpath('//item[position() <= 2]/title/text()')
print(items) # ['Understanding Database Indexes', 'REST API Design Patterns']
# XPath con predicados — obtener elementos de una categoría específica
db_items = root.xpath('//item[category="Database"]/title/text()')
print(db_items) # ['Understanding Database Indexes']
# Validación de esquema XSD
xsd_doc = etree.parse('schema.xsd')
schema = etree.XMLSchema(xsd_doc)
xml_doc = etree.parse('data.xml')
if schema.validate(xml_doc):
print("¡Válido!")
else:
for error in schema.error_log:
print(f"Línea {error.line}: {error.message}")Herramientas útiles para trabajar con XML
Al construir integraciones XML en Python, estas herramientas del navegador ayudan con el lado de los datos: Formateador XML para hacer legibles las respuestas API sin procesar, Validador XML para verificar la buena formación antes de conectar tu parser, y XML a JSON cuando prefieres trabajar con dicts en lugar de elementos.
Resumen
El módulo xml.etree.ElementTree de Python cubre la mayoría de los escenarios XML del mundo real:
usa find() y findall() con un mapa de espacios de nombres para SOAP y feeds con espacios de nombres,
findtext() con valores por defecto para evitar AttributeError en elementos faltantes,
e iterparse() para archivos grandes que no puedes cargar en memoria de una vez.
Recurre a lxml cuando necesites validación XSD o el lenguaje de expresión XPath completo.
La biblioteca estándar maneja todo lo demás perfectamente bien.