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

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    minidom en Python: manipula XML con DOM

    Aprende xml.dom.minidom en Python para leer, navegar, crear y serializar XML con nodos DOM, namespaces, memoria y seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    22/08/2026
    A retro blue mailbox attached to a vibrant yellow wall, perfect for vintage themes.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    poplib en Python: lee correos con POP3

    Aprende poplib en Python para acceder a POP3 con TLS, listar y descargar mensajes, usar UIDL, aplicar límites y evitar

    Ler mais

    Tempo de leitura: 6 minutos
    22/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    imaplib en Python: lee correos con IMAP

    Aprende imaplib en Python para acceder a buzones IMAP con TLS, buscar por UID, leer sin marcar mensajes, usar flags

    Ler mais

    Tempo de leitura: 6 minutos
    22/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ftplib en Python: FTP y FTPS seguros

    Aprende ftplib en Python para listar, descargar y subir archivos por FTP o FTPS con TLS, timeouts, límites, reanudación y

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Rack de servidores que representa un endpoint creado con xmlrpc.server en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crea servidores XML-RPC

    Aprende xmlrpc.server en Python para crear servidores XML-RPC, registrar funciones, limitar métodos y rutas y evitar exposición insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Código de error sobre datos binarios que representa fallos manejados con urllib.error en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.error en Python: maneja errores HTTP

    Aprende urllib.error en Python para manejar URLError, HTTPError, descargas incompletas, retries selectivos y diagnósticos de red claros.

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026