ElementTree: lee y modifica XML en Python

Publicado el: 22/08/2026
Tempo de leitura: 5 minutos
Vibrant green snake coiled on a tree branch amidst lush jungle foliage.

xml.etree.ElementTree ofrece una API sencilla y eficiente para leer, consultar, modificar y generar XML. Representa el documento como un árbol: ElementTree envuelve el documento y cada Element representa una etiqueta con atributos, texto, tail y elementos hijos.

ElementTree resulta útil para archivos de configuración, feeds, integraciones heredadas, formatos de proyecto y otros documentos XML. La documentación oficial advierte que XML no autenticado puede ser peligroso. Impone límites de tamaño, profundidad, tiempo y acceso a recursos externos.

Leer un archivo XML

import xml.etree.ElementTree as ET

arbol = ET.parse("catalogo.xml")
raiz = arbol.getroot()

print(raiz.tag)
print(raiz.attrib)

parse() devuelve un ElementTree. Para una cadena pequeña ya cargada, usa fromstring():

xml = "<producto id='7'><nombre>Teclado</nombre></producto>"
raiz = ET.fromstring(xml)

No leas un archivo arbitrariamente grande antes de aplicar un límite de bytes. Valida tamaño en almacenamiento, upload o HTTP antes de entregar datos al parser.

Tags, atributos, text y tail

Un elemento expone tag, attrib, text y tail. text contiene contenido antes del primer hijo, mientras tail contiene texto después del cierre y antes del siguiente hermano.

for hijo in raiz:
    print(hijo.tag, hijo.get("id"), hijo.text, hijo.tail)

Para juntar todo el texto interno de una subárbol, usa "".join(elemento.itertext()). No supongas que todo el texto visible está únicamente en .text.

Buscar elementos

for producto in raiz.findall("producto"):
    nombre = producto.findtext("nombre", default="")
    precio = producto.findtext("precio", default="0")
    print(nombre, precio)

find() devuelve el primer elemento o None. Comprueba con elemento is None. El test booleano de elementos vacíos está deprecado y puede confundir un elemento vacío con uno ausente.

Iteración recursiva

for enlace in raiz.iter("enlace"):
    print(enlace.get("href"))

iter() recorre toda la subárbol en orden del documento. Para contenido grande o no confiable, cuenta elementos y detén el procesamiento al superar el límite.

XPath limitado

ElementTree soporta un subconjunto útil de XPath para rutas, descendientes, atributos, texto y posiciones.

activos = raiz.findall(".//producto[@activo='si']")
segundos = raiz.findall(".//item[2]")

No es un motor XPath completo. Evita construir expresiones con texto arbitrario del usuario. Usa consultas conocidas y valida valores antes de interpolarlos.

Namespaces

Durante el parsing, las etiquetas con namespace se expanden al formato {URI}nombre.

namespaces = {
    "atom": "http://www.w3.org/2005/Atom",
}

for entrada in raiz.findall("atom:entry", namespaces):
    titulo = entrada.findtext("atom:title", namespaces=namespaces)
    print(titulo)

Define tu propio mapa de prefijos. El prefijo original no es la identidad del namespace; la URI sí lo es.

Crear XML

raiz = ET.Element("catalogo", {"version": "1"})
producto = ET.SubElement(raiz, "producto", {"id": "7"})
ET.SubElement(producto, "nombre").text = "Teclado"
ET.SubElement(producto, "precio").text = "199.90"

ET.indent(raiz, space="  ")
xml = ET.tostring(raiz, encoding="unicode")
print(xml)

El serializer escapa texto y atributos. Aun así, valida nombres de tags y estructura cuando se elijan dinámicamente. No permitas nombres arbitrarios sin allowlist.

Escribir en archivo

arbol = ET.ElementTree(raiz)
arbol.write(
    "catalogo.xml",
    encoding="utf-8",
    xml_declaration=True,
)

Escribe primero en un archivo temporal del mismo directorio y reemplaza el destino después del éxito. Consulta tempfile en Python.

Modificar elementos

for producto in raiz.findall("producto"):
    producto.set("revisado", "si")
    precio = producto.find("precio")
    if precio is not None:
        precio.text = str(round(float(precio.text or "0"), 2))

Para eliminar hijos, recógelos primero y modifica después. Cambiar la colección durante la iteración puede saltar elementos.

eliminar = [
    producto
    for producto in raiz.findall("producto")
    if producto.get("inactivo") == "si"
]
for producto in eliminar:
    raiz.remove(producto)

Parsing incremental con iterparse

iterparse() procesa documentos grandes sin mantener todos los nodos útiles en memoria, aunque realiza lecturas bloqueantes.

for evento, elemento in ET.iterparse(
    "datos.xml",
    events=("end",),
):
    if elemento.tag == "registro":
        procesar(elemento)
        elemento.clear()

Usa eventos end cuando necesitas hijos y texto completos. En un evento start, el contenido puede no estar listo. clear() libera referencias y reduce memoria.

XMLPullParser para entrada por bloques

parser = ET.XMLPullParser(events=("end",))

for bloque in fuente_de_bloques():
    parser.feed(bloque)
    for evento, elemento in parser.read_events():
        if elemento.tag == "registro":
            procesar(elemento)
            elemento.clear()

parser.close()

XMLPullParser encaja cuando otra capa controla lecturas no bloqueantes. Todavía necesitas límites de bytes, elementos, texto y profundidad.

Límites de recursos

Antes del parsing, limita bytes. Durante el procesamiento, cuenta elementos, atributos, profundidad, namespaces y tamaño de texto. Interrumpe al superar cualquier umbral contractual.

MAX_ELEMENTOS = 100_000
contador = 0

for evento, elemento in ET.iterparse("datos.xml", events=("end",)):
    contador += 1
    if contador > MAX_ELEMENTOS:
        raise ValueError("el XML supera el límite de elementos")

XML no confiable

XML puede explotar expansión de entidades, referencias externas, profundidad excesiva y payloads gigantes, según el parser y entorno. Para datos de terceros, considera una biblioteca endurecida, aislamiento de proceso y límites de infraestructura.

Un parsing exitoso no es validación. ElementTree no valida automáticamente un XSD ni garantiza que el documento siga el esquema de negocio.

XInclude

xml.etree.ElementInclude puede sustituir nodos XInclude por archivos o recursos. El loader por defecto lee del disco. No proceses XInclude de XML no confiable sin loader restrictivo, raíz permitida y profundidad limitada.

from xml.etree import ElementInclude

ElementInclude.include(
    raiz,
    loader=loader_controlado,
    max_depth=3,
)

No uses max_depth=None con datos externos. Rechaza rutas absolutas, URLs y traversal.

Canonicalización

canonicalize() aplica C14N 2.0 y genera una representación determinista útil para comparaciones y flujos de firma.

normalizado = ET.canonicalize(
    xml_data=xml_original,
    with_comments=False,
    strip_text=False,
)

Canonicalizar no verifica una firma ni vuelve confiable el documento. Solo normaliza decisiones de serialización como namespaces, atributos y espacios.

Orden de atributos

El modelo de información XML no asigna significado al orden de atributos. Las versiones actuales preservan la inserción, pero la lógica de negocio no debe depender de ella. Usa canonicalización para salida criptográfica o comparación byte a byte.

Encoding

tostring(..., encoding="unicode") devuelve str; otros encodings devuelven bytes. Combina el tipo con el stream correcto. No escribas string en stream binario ni bytes en stream de texto.

Para problemas de encoding, consulta codecs en Python.

Errores de parsing

XML malformado lanza ET.ParseError, normalmente con línea y columna.

try:
    raiz = ET.fromstring(xml_recibido)
except ET.ParseError as error:
    linea, columna = error.position
    raise ValueError(
        f"XML inválido en línea {linea}, columna {columna}"
    ) from error

No expongas fragmentos sensibles del XML en mensajes públicos.

Validación de dominio

Después del parsing, valida tags obligatorias, tipos, rangos, cardinalidad y relaciones. Usa Decimal para dinero, reglas explícitas de timezone y allowlists para enums y URLs.

Pruebas recomendadas

Prueba documento vacío, XML malformado, namespaces, atributos ausentes, texto tail, XPath soportado y no soportado, archivos grandes, profundidad excesiva, demasiados elementos, encoding divergente, escritura atómica, XInclude bloqueado y canonicalización.

Errores comunes

Los fallos frecuentes son cargar XML ilimitado, ignorar namespaces, asumir que .text contiene todo, probar con if not elemento, modificar hijos durante iteración, esperar XPath completo, habilitar XInclude libremente, depender del orden de atributos y tratar parsing como validación.

Conclusión

ElementTree es una solución práctica para XML en la biblioteca estándar. Usa parse() y fromstring() para documentos pequeños, iterparse() para archivos grandes y XMLPullParser para entrada incremental. Modela namespaces, valida el dominio e impone límites antes y durante el parsing.

Consulta la documentación oficial de ElementTree y las orientaciones de seguridad XML de Python. Para XML externo, combina parser actualizado, límites, validación y aislamiento.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026