curso
Tutorial Python XML con ElementTree: Guía para principiantes
Ejecute y edite el código de este tutorial en línea
Ejecutar códigoComo científico de datos, descubrirás que comprender XML es muy útil tanto para el web-scraping como para la práctica general de analizar un documento estructurado.
- Aprenderás más sobre XML y conocerás el paquete Python
ElementTree
. - A continuación, descubrirás cómo puedes explorar árboles XML para comprender mejor los datos con los que trabajas con la ayuda de las funciones
ElementTree
, los bucles for y las expresiones XPath. - A continuación, aprenderás cómo puedes modificar un archivo XML.
- También utilizarás expresiones xpath para rellenar archivos XML.
Aprende Python desde cero
¿Qué es XML?
XML significa "Lenguaje de marcado extensible". Se utiliza principalmente en páginas web, donde los datos tienen una estructura específica y son comprendidos dinámicamente por el marco XML.
XML crea una estructura en forma de árbol que es fácil de interpretar y admite una jerarquía. Siempre que una página siga XML, puede llamarse documento XML.
- Los documentos XML tienen secciones llamadas elementos, que se definen mediante una etiqueta de inicio y otra de fin. Una etiqueta es una construcción de marcado que empieza por
<
y termina por>
. Los caracteres entre la etiqueta de inicio y la de fin, si los hay, son el contenido del elemento. Los elementos pueden contener marcas, incluidos otros elementos, que se denominan "elementos hijos". - El elemento más grande, de nivel superior, se llama raíz, que contiene todos los demás elementos.
- Los atributos son pares nombre-valor que existen dentro de una etiqueta de inicio o de elemento vacío. Un atributo XML sólo puede tener un único valor y cada atributo puede aparecer como máximo una vez en cada elemento.
Para entenderlo un poco mejor, echa un vistazo al siguiente archivo XML (abreviado):
<?xml version="1.0"?>
<collection>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back 2 the Future">
<format multiple="False">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
</genre>
<genre category="Thriller">
<decade years="1970s">
<movie favorite="False" title="ALIEN">
<format multiple="Yes">DVD</format>
<year>1979</year>
<rating>R</rating>
<description>"""""""""</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="True" title="Ferris Bueller's Day Off">
<format multiple="No">DVD</format>
<year>1986</year>
<rating>PG13</rating>
<description>Funny movie about a funny guy</description>
</movie>
<movie favorite="FALSE" title="American Psycho">
<format multiple="No">blue-ray</format>
<year>2000</year>
<rating>Unrated</rating>
<description>psychopathic Bateman</description>
</movie>
</decade>
</genre>
Por lo que has leído más arriba, ves que
es el elemento raíz único: contiene todos los demás elementos, como
, o
, que son los elementos hijos o subelementos. Como puedes ver, estos elementos están anidados.
Ten en cuenta que estos elementos hijos también pueden actuar como padres y contener sus propios elementos hijos, que entonces se denominan "elementos subhijos".
- Verás que, por ejemplo, el elemento
contiene un par de "atributos", como
favorite
title
¡que dan aún más información!
Con esta breve introducción a los archivos XML en mente, ¡ya estás preparado para aprender más sobre ElementTree
!
Introducción a ElementTree
La estructura de árbol XML hace que la navegación, modificación y eliminación sean relativamente sencillas mediante programación. Python tiene una biblioteca incorporada, ElementTree
, que dispone de funciones para leer y manipular XML (y otros archivos de estructura similar).
En primer lugar, importa ElementTree
. Es una práctica habitual utilizar el alias de ET
:
import xml.etree.ElementTree as ET
Análisis de datos XML
El archivo XML proporcionado describe una colección básica de películas. ¡El único problema es que los datos son un desastre! Ha habido muchos conservadores diferentes de esta colección, y cada uno tiene su propia forma de introducir datos en el archivo. El objetivo principal de este tutorial será leer y comprender el archivo con Python y luego solucionar los problemas.
Primero, tienes que leer el archivo con ElementTree
.
tree = ET.parse('movies.xml')
root = tree.getroot()
Ahora que has inicializado el árbol, debes mirar el XML e imprimir los valores para comprender cómo está estructurado el árbol.
Cada parte de un árbol (raíz incluida) tiene una etiqueta que describe el elemento. Además, como has visto en la introducción, los elementos pueden tener atributos, que son descriptores adicionales utilizados especialmente para el uso repetido de etiquetas. Los atributos también ayudan a validar los valores introducidos para esa etiqueta, contribuyendo una vez más al formato estructurado de un XML.
Más adelante en este tutorial verás que los atributos pueden ser muy potentes cuando se incluyen en un XML.
root.tag
'collection'
En el nivel superior, ves que este XML tiene su raíz en la etiqueta collection
.
root.attrib
{}
Por tanto, la raíz no tiene atributos.
Para bucles
Puedes iterar fácilmente sobre los subelementos (comúnmente llamados "hijos") de la raíz utilizando un simple bucle "for".
for child in root:
print(child.tag, child.attrib)
genre {'category': 'Action'}
genre {'category': 'Thriller'}
genre {'category': 'Comedy'}
Ahora ya sabes que los hijos de la raíz collection
son todos genre
. Para designar el género, el XML utiliza el atributo category
. Hay películas de Acción, Suspense y Comedia según el elemento genre
.
Normalmente es útil conocer todos los elementos del árbol completo. Una función útil para hacerlo es root.iter()
. Puedes poner esta función en un bucle "for" e iterará sobre todo el árbol.
[elem.tag for elem in root.iter()]
['collection',
'genre',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'genre',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'genre',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description']
Esto da una noción general de cuántos elementos tienes, pero no muestra los atributos ni los niveles del árbol.
Hay una forma útil de ver todo el documento. Cualquier elemento tiene un método .tostring()
. Si pasas la raíz al método .tostring()
, puedes devolver el documento completo. Dentro de ElementTree
(recuerda el alias ET
), .tostring()
adopta una forma un poco extraña.
Puesto que ElementTree
es una potente biblioteca que puede interpretar algo más que XML. Debes especificar tanto la codificación como la descodificación del documento que estás mostrando como cadena. Para los XML, utiliza 'utf8'
- Es el formato de documento típico para un XML.
print(ET.tostring(root, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<collection>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back 2 the Future">
<format multiple="False">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
</genre>
<genre category="Thriller">
<decade years="1970s">
<movie favorite="False" title="ALIEN">
<format multiple="Yes">DVD</format>
<year>1979</year>
<rating>R</rating>
<description>"""""""""</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="True" title="Ferris Bueller's Day Off">
<format multiple="No">DVD</format>
<year>1986</year>
<rating>PG13</rating>
<description>Funny movie about a funny guy</description>
</movie>
<movie favorite="FALSE" title="American Psycho">
<format multiple="No">blue-ray</format>
<year>2000</year>
<rating>Unrated</rating>
<description>psychopathic Bateman</description>
</movie>
</decade>
</genre>
<genre category="Comedy">
<decade years="1960s">
<movie favorite="False" title="Batman: The Movie">
<format multiple="Yes">DVD,VHS</format>
<year>1966</year>
<rating>PG</rating>
<description>What a joke!</description>
</movie>
</decade>
<decade years="2010s">
<movie favorite="True" title="Easy A">
<format multiple="No">DVD</format>
<year>2010</year>
<rating>PG--13</rating>
<description>Emma Stone = Hester Prynne</description>
</movie>
<movie favorite="True" title="Dinner for SCHMUCKS">
<format multiple="Yes">DVD,digital,Netflix</format>
<year>2011</year>
<rating>Unrated</rating>
<description>Tim (Rudd) is a rising executive
who “succeeds” in finding the perfect guest,
IRS employee Barry (Carell), for his boss’ monthly event,
a so-called “dinner for idiots,” which offers certain
advantages to the exec who shows up with the biggest buffoon.
</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="False" title="Ghostbusters">
<format multiple="No">Online,VHS</format>
<year>1984</year>
<rating>PG</rating>
<description>Who ya gonna call?</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Robin Hood: Prince of Thieves">
<format multiple="No">Blu_Ray</format>
<year>1991</year>
<rating>Unknown</rating>
<description>Robin Hood slaying</description>
</movie>
</decade>
</genre>
</collection>
Puedes ampliar el uso de la función iter()
para ayudarte a encontrar elementos concretos de interés. root.iter()
listará todos los subelementos bajo la raíz que coincidan con el elemento especificado. Aquí listarás todos los atributos del elemento movie
en el árbol:
for movie in root.iter('movie'):
print(movie.attrib)
{'favorite': 'True', 'title': 'Indiana Jones: The raiders of the lost Ark'}
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'Back 2 the Future'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'True', 'title': "Ferris Bueller's Day Off"}
{'favorite': 'FALSE', 'title': 'American Psycho'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Easy A'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
{'favorite': 'False', 'title': 'Ghostbusters'}
{'favorite': 'True', 'title': 'Robin Hood: Prince of Thieves'}
Ya puedes ver cómo los movies
se han introducido de distintas formas. No te preocupes por eso de momento. Tendrás la oportunidad de corregir uno de los errores más adelante en este tutorial.
Expresiones XPath
Muchas veces los elementos no tendrán atributos, sólo tendrán contenido de texto. Utilizando el atributo .text
, puedes imprimir este contenido.
Ahora, imprime todas las descripciones de las películas.
for description in root.iter('description'):
print(description.text)
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
None provided.
Marty McFly
Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.
NA.
WhAtEvER I Want!!!?!
"""""""""
Funny movie about a funny guy
psychopathic Bateman
What a joke!
Emma Stone = Hester Prynne
Tim (Rudd) is a rising executive
who “succeeds” in finding the perfect guest,
IRS employee Barry (Carell), for his boss’ monthly event,
a so-called “dinner for idiots,” which offers certain
advantages to the exec who shows up with the biggest buffoon.
Who ya gonna call?
Robin Hood slaying
Imprimir el XML es útil, pero XPath es un lenguaje de consulta que se utiliza para buscar en un XML de forma rápida y sencilla. XPath significa Lenguaje de rutas XML y utiliza, como su nombre indica, una sintaxis "similar a una ruta" para identificar y navegar por los nodos de un documento XML.
Comprender XPath es de vital importancia para explorar y rellenar XMLs. ElementTree
tiene una función .findall()
que recorrerá los hijos inmediatos del elemento referenciado. Puedes utilizar expresiones XPath para especificar búsquedas más útiles.
Aquí buscarás en el árbol las películas estrenadas en 1992:
for movie in root.findall("./genre/decade/movie/[year='1992']"):
print(movie.attrib)
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
La función .findall()
comienza siempre en el elemento especificado. Este tipo de función es muy potente para "buscar y sustituir". ¡Incluso puedes buscar por atributos!
Ahora, imprime sólo las películas que estén disponibles en varios formatos (un atributo).
for movie in root.findall("./genre/decade/movie/format/[@multiple='Yes']"):
print(movie.attrib)
{'multiple': 'Yes'}
{'multiple': 'Yes'}
{'multiple': 'Yes'}
{'multiple': 'Yes'}
{'multiple': 'Yes'}
Piensa por qué, en este caso, la sentencia print devuelve los valores "Sí" de multiple
. Piensa en cómo se define el bucle "for". ¿Podrías reescribir este bucle para imprimir en su lugar los títulos de las películas? Pruébalo a continuación:
Consejo: utiliza '...'
dentro de XPath para devolver el elemento padre del elemento actual.
for movie in root.findall("./genre/decade/movie/format[@multiple='Yes']..."):
print(movie.attrib)
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
Modificar un XML
Antes, los títulos de las películas eran un caos absoluto. Ahora, imprímelos de nuevo:
for movie in root.iter('movie'):
print(movie.attrib)
{'favorite': 'True', 'title': 'Indiana Jones: The raiders of the lost Ark'}
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'Back 2 the Future'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'True', 'title': "Ferris Bueller's Day Off"}
{'favorite': 'FALSE', 'title': 'American Psycho'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Easy A'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
{'favorite': 'False', 'title': 'Ghostbusters'}
{'favorite': 'True', 'title': 'Robin Hood: Prince of Thieves'}
Arregla el "2" de Regreso al Futuro. Eso debería ser un problema de buscar y reemplazar. Escribe un código para encontrar el título "Regreso al Futuro" y guárdalo como variable:
b2tf = root.find("./genre/decade/movie[@title='Back 2 the Future']")
print(b2tf)
<Element 'movie' at 0x10ce00ef8>
Observa que el método .find()
devuelve un elemento del árbol. Muchas veces, es más útil editar el contenido dentro de un elemento.
Modifica el atributo title
de la variable del elemento Regreso al Futuro para que diga "Regreso al Futuro". Después, imprime los atributos de tu variable para ver tu cambio. Puedes hacerlo fácilmente accediendo al atributo de un elemento y asignándole después un nuevo valor:
b2tf.attrib["title"] = "Back to the Future"
print(b2tf.attrib)
{'favorite': 'False', 'title': 'Back to the Future'}
Escribe tus cambios en el XML para que queden fijados permanentemente en el documento. Vuelve a imprimir los atributos de tu película para asegurarte de que tus cambios han funcionado. Para ello, utiliza el método .write()
:
tree.write("movies.xml")
tree = ET.parse('movies.xml')
root = tree.getroot()
for movie in root.iter('movie'):
print(movie.attrib)
{'favorite': 'True', 'title': 'Indiana Jones: The raiders of the lost Ark'}
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'Back to the Future'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'True', 'title': "Ferris Bueller's Day Off"}
{'favorite': 'FALSE', 'title': 'American Psycho'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Easy A'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
{'favorite': 'False', 'title': 'Ghostbusters'}
{'favorite': 'True', 'title': 'Robin Hood: Prince of Thieves'}
Fijar atributos
El atributo multiple
es incorrecto en algunos lugares. Utiliza ElementTree
para fijar el designador en función de los formatos en los que venga la película. Primero, imprime el atributo format
y el texto para ver qué partes hay que arreglar.
for form in root.findall("./genre/decade/movie/format"):
print(form.attrib, form.text)
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,Online
{'multiple': 'False'} Blu-ray
{'multiple': 'Yes'} dvd, digital
{'multiple': 'No'} VHS
{'multiple': 'No'} Online
{'multiple': 'Yes'} DVD
{'multiple': 'No'} DVD
{'multiple': 'No'} blue-ray
{'multiple': 'Yes'} DVD,VHS
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,digital,Netflix
{'multiple': 'No'} Online,VHS
{'multiple': 'No'} Blu_Ray
Hay que trabajar un poco en esta etiqueta.
Puedes utilizar regex para encontrar comas, que te dirán si el atributo multiple
debe ser "Sí" o "No". Añadir y modificar atributos puede hacerse fácilmente con el método .set()
.
Nota: re
es el intérprete de expresiones regulares estándar de Python. Si quieres saber más sobre expresiones regulares, ten en cuenta este tutorial.
import re
for form in root.findall("./genre/decade/movie/format"):
# Search for the commas in the format text
match = re.search(',',form.text)
if match:
form.set('multiple','Yes')
else:
form.set('multiple','No')
# Write out the tree to the file again
tree.write("movies.xml")
tree = ET.parse('movies.xml')
root = tree.getroot()
for form in root.findall("./genre/decade/movie/format"):
print(form.attrib, form.text)
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,Online
{'multiple': 'No'} Blu-ray
{'multiple': 'Yes'} dvd, digital
{'multiple': 'No'} VHS
{'multiple': 'No'} Online
{'multiple': 'No'} DVD
{'multiple': 'No'} DVD
{'multiple': 'No'} blue-ray
{'multiple': 'Yes'} DVD,VHS
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,digital,Netflix
{'multiple': 'Yes'} Online,VHS
{'multiple': 'No'} Blu_Ray
Elementos móviles
Algunos datos se han colocado en la década equivocada. Utiliza lo que has aprendido sobre XML y ElementTree
para encontrar y corregir los errores de datos de la década.
Será útil imprimir tanto las etiquetas decade
como las etiquetas year
a lo largo del documento.
for decade in root.findall("./genre/decade"):
print(decade.attrib)
for year in decade.findall("./movie/year"):
print(year.text, '\n')
{'years': '1980s'}
1981
1984
1985
{'years': '1990s'}
2000
1992
1992
{'years': '1970s'}
1979
{'years': '1980s'}
1986
2000
{'years': '1960s'}
1966
{'years': '2010s'}
2010
2011
{'years': '1980s'}
1984
{'years': '1990s'}
1991
Los dos años que están en la década equivocada son las películas de la década de 2000. Averigua cuáles son esas películas utilizando una expresión XPath.
for movie in root.findall("./genre/decade/movie/[year='2000']"):
print(movie.attrib)
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'FALSE', 'title': 'American Psycho'}
Tienes que añadir una nueva etiqueta de década, la década de 2000, al género Acción para poder mover los datos de X-Men. Puedes utilizar el método .SubElement()
para añadir esta etiqueta al final del XML.
action = root.find("./genre[@category='Action']")
new_dec = ET.SubElement(action, 'decade')
new_dec.attrib["years"] = '2000s'
print(ET.tostring(action, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back to the Future">
<format multiple="No">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
<decade years="2000s" /></genre>
Ahora añade la película de X-Men a la década de 2000 y elimínala de la década de 1990, utilizando .append()
y .remove()
, respectivamente.
xmen = root.find("./genre/decade/movie[@title='X-Men']")
dec2000s = root.find("./genre[@category='Action']/decade[@years='2000s']")
dec2000s.append(xmen)
dec1990s = root.find("./genre[@category='Action']/decade[@years='1990s']")
dec1990s.remove(xmen)
print(ET.tostring(action, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back to the Future">
<format multiple="No">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
<decade years="2000s"><movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
</decade></genre>
Construir documentos XML
Bien, así que esencialmente fuiste capaz de trasladar una película entera a una nueva década. Vuelve a guardar los cambios en el XML.
tree.write("movies.xml")
tree = ET.parse('movies.xml')
root = tree.getroot()
print(ET.tostring(root, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<collection>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back to the Future">
<format multiple="No">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
<decade years="2000s"><movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
</decade></genre>
<genre category="Thriller">
<decade years="1970s">
<movie favorite="False" title="ALIEN">
<format multiple="No">DVD</format>
<year>1979</year>
<rating>R</rating>
<description>"""""""""</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="True" title="Ferris Bueller's Day Off">
<format multiple="No">DVD</format>
<year>1986</year>
<rating>PG13</rating>
<description>Funny movie about a funny guy</description>
</movie>
<movie favorite="FALSE" title="American Psycho">
<format multiple="No">blue-ray</format>
<year>2000</year>
<rating>Unrated</rating>
<description>psychopathic Bateman</description>
</movie>
</decade>
</genre>
<genre category="Comedy">
<decade years="1960s">
<movie favorite="False" title="Batman: The Movie">
<format multiple="Yes">DVD,VHS</format>
<year>1966</year>
<rating>PG</rating>
<description>What a joke!</description>
</movie>
</decade>
<decade years="2010s">
<movie favorite="True" title="Easy A">
<format multiple="No">DVD</format>
<year>2010</year>
<rating>PG--13</rating>
<description>Emma Stone = Hester Prynne</description>
</movie>
<movie favorite="True" title="Dinner for SCHMUCKS">
<format multiple="Yes">DVD,digital,Netflix</format>
<year>2011</year>
<rating>Unrated</rating>
<description>Tim (Rudd) is a rising executive
who “succeeds” in finding the perfect guest,
IRS employee Barry (Carell), for his boss’ monthly event,
a so-called “dinner for idiots,” which offers certain
advantages to the exec who shows up with the biggest buffoon.
</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="False" title="Ghostbusters">
<format multiple="Yes">Online,VHS</format>
<year>1984</year>
<rating>PG</rating>
<description>Who ya gonna call?</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Robin Hood: Prince of Thieves">
<format multiple="No">Blu_Ray</format>
<year>1991</year>
<rating>Unknown</rating>
<description>Robin Hood slaying</description>
</movie>
</decade>
</genre>
</collection>
¿Qué hay de nuevo en ElementTree?
Aquí tienes un resumen de las nuevas funciones y mejoras de la biblioteca ElementTree en las nuevas versiones de Python:
1. Compatibilidad con XPath 1.0 (Python 3.8): A partir de Python 3.8, ElementTree incluye compatibilidad total con XPath 1.0 con los métodos find()
y findall()
, lo que permite realizar consultas XML más ricas y complejas. Ejemplo:
# Finding all movies with a specific attribute using XPath
for movie in root.findall(".//movie[@favorite='True']"):
print(movie.attrib)
2. Mejoras en los espacios de nombres (Python 3.8+): Se ha mejorado la compatibilidad con los espacios de nombres XML, lo que permite una interacción más directa con los archivos XML que utilizan espacios de nombres prefijados o predeterminados. Ejemplo:
# Register a namespace and find elements using it
ET.register_namespace('', 'http://example.com/namespace')
movies = root.findall(".//{http://example.com/namespace}movie")
3. Mejoras en el analizador sintáctico (Python 3.9): Un mejor análisis de los mensajes de error facilita la depuración de archivos XML mal formados.
4. Nueva función indent()
(Python 3.9): Se ha añadido la función xml.etree.ElementTree.indent()
para imprimir documentos XML con sangría en sus elementos. Ejemplo:
ET.indent(root, space=" ", level=0)
ET.dump(root)
5. Análisis sintáctico eficiente con iterparse
(Python 3.10): Optimizado para ahorrar memoria, especialmente útil cuando se trabaja con archivos XML de gran tamaño.
6. Documentación ampliada (Actualizaciones continuas): La documentación en Python de ElementTree es ahora más completa, e incluye las mejores prácticas y casos de uso avanzados.
Funciones obsoletas en ElementTree y alternativas
1. write() con xml_declaration en Python 3.8+: El parámetro xml_declaration
del método write()
queda obsoleto cuando la codificación se establece en 'unicode'
.
- Alternativa: Utiliza
xml_declaration
sólo cuando la codificación se defina explícitamente como algo distinto de'unicode'
.
tree.write("output.xml", encoding="utf-8", xml_declaration=True)
2. analizador html: Aunque no está oficialmente obsoleto, se desaconseja utilizar ElementTree para el análisis sintáctico de HTML, ya que es limitado en el manejo de HTML no bien formado.
- Alternativa: Utiliza bibliotecas diseñadas específicamente para el análisis sintáctico de HTML, como
BeautifulSoup
del paquetebs4
.
from bs4 import BeautifulSoup soup = BeautifulSoup(html_content, 'html.parser')
3. Soluciones para el manejo del espacio de nombres: Los métodos antiguos de gestionar manualmente los espacios de nombres (por ejemplo, concatenar URIs de espacios de nombres con etiquetas de elementos) son menos recomendables con la introducción de un sólido soporte de espacios de nombres en las versiones más recientes.
- Alternativa: Utiliza los métodos y funciones incorporados que tienen en cuenta el espacio de nombres.
ET.register_namespace('', 'http://example.com/namespace') movies = root.findall(".//{http://example.com/namespace}movie")
4. Impresión manual bonita: Las técnicas manuales para sangrar y formatear XML han quedado obsoletas gracias a la nueva función indent()
(Python 3.9).
- Alternativa: Utiliza
ET.indent()
para el formateo automático de XML.
ET.indent(root, space=" ")
5. Uso directo de _ElementoInterfaz: Las clases internas como _ElementInterface
no están pensadas para su uso directo y podrían romperse en futuras versiones.
- Alternativa: Interactúa siempre con la API pública documentada de la biblioteca ElementTree.
Conclusión
Hay algunas cosas clave que debes recordar sobre los XML y el uso de ElementTree
.
Las etiquetas construyen la estructura del árbol y designan qué valores deben delinearse en él. Utilizar una estructuración inteligente puede facilitar la lectura y escritura de un XML. Las etiquetas siempre necesitan corchetes de apertura y cierre para mostrar las relaciones padre e hijo.
Los atributos describen además cómo validar una etiqueta o permiten designaciones booleanas. Los atributos suelen tomar valores muy concretos para que el analizador XML (y el usuario) puedan utilizarlos para comprobar los valores de las etiquetas.
ElementTree
es una importante biblioteca de Python que te permite analizar y navegar por un documento XML. Utilizar ElementTree
descompone el documento XML en una estructura de árbol con la que es fácil trabajar. En caso de duda, imprímelo (print(ET.tostring(root, encoding='utf8').decode('utf8'))
) - utiliza esta útil sentencia de impresión para ver todo el documento XML de una vez. Ayuda a comprobarlo cuando editas, añades o eliminas de un XML.
Ahora ya estás preparado para entender XML y empezar a analizarlo.
Conviértete en Desarrollador Python
Preguntas frecuentes
¿Cuáles son algunos casos de uso comunes para utilizar XML en la ciencia de datos?
El XML se utiliza a menudo en la ciencia de datos para el intercambio de datos entre sistemas, el web scraping, los archivos de configuración y el manejo de datos con una estructura compleja y jerárquica. Es especialmente útil cuando se trabaja con API que devuelven datos XML.
¿Puede ElementTree manejar eficazmente archivos XML de gran tamaño?
ElementTree es adecuado para manejar archivos XML de tamaño moderado, pero para archivos muy grandes, puedes considerar el uso de bibliotecas como lxml
o xml.sax
que son más eficientes en el uso de la memoria y pueden gestionar el flujo de archivos grandes.
¿Cómo se compara ElementTree con otras bibliotecas de análisis XML como lxml o minidom?
ElementTree forma parte de la biblioteca estándar de Python y es fácil de usar para tareas básicas de análisis sintáctico de XML. lxml
es más potente y rápido, y ofrece funciones adicionales como la compatibilidad con XPath 2.0. minidom
, otra biblioteca estándar, se basa en el Modelo de Objetos del Documento (DOM) y es menos eficiente para documentos grandes.
¿Qué son las expresiones XPath y cómo son útiles en el análisis sintáctico de XML?
Las expresiones XPath son lenguajes de consulta para seleccionar nodos de un documento XML. Son útiles para navegar por los elementos y atributos de los documentos XML, lo que permite recuperar y manipular datos con precisión.
¿Cómo puedo validar un documento XML antes de analizarlo con ElementTree?
Los documentos XML pueden validarse mediante una Definición de Esquema XML (XSD) o una Definición de Tipo de Documento (DTD). Las bibliotecas de Python como lxml
ofrecen soporte integrado para la validación según estas normas.
¿Cuáles son las mejores prácticas para modificar datos XML utilizando ElementTree?
Las mejores prácticas incluyen trabajar sobre una copia de los datos XML para evitar la pérdida accidental de datos, utilizar expresiones XPath para una navegación precisa y asegurarse de que todos los cambios mantienen la buena forma del documento XML.
¿Se puede utilizar ElementTree para convertir datos XML a JSON?
Aunque ElementTree por sí mismo no proporciona conversión directa de XML a JSON, puedes analizar los datos XML en un diccionario Python utilizando ElementTree y luego convertir el diccionario a JSON utilizando el módulo de Python json
de Python.
¿Cómo se gestionan los espacios de nombres XML con ElementTree?
ElementTree puede manejar espacios de nombres XML utilizando el prefijo{namespace}
en los nombres de las etiquetas. También puedes registrar espacios de nombres en ET.register_namespace()
para facilitar el manejo de XML con espacios de nombres.
¿Qué debo hacer si encuentro un error de análisis XML en ElementTree?
Comprueba problemas comunes como XML malformado, codificaciones no compatibles o rutas de archivo incorrectas. Utiliza los mecanismos de tratamiento de errores de Python (try-except
blocks) para diagnosticar y gestionar los errores de análisis sintáctico con elegancia.
¿Es posible imprimir un documento XML utilizando ElementTree?
ElementTree no admite la impresión bonita directamente, pero puedes utilizar xml.dom.minidom
para analizar la cadena XML y luego utilizar su método toprettyxml()
para formatear el XML y hacerlo más legible.
Más información sobre Python
curso
Intermediate Python
curso
Introduction to Functions in Python
tutorial
Tutorial de Excel en Python: La guía definitiva
tutorial
Tutorial de análisis NLTK del sentimiento para principiantes
tutorial
21 herramientas esenciales de Python
tutorial
Datos JSON en Python
tutorial
Tutorial de Pandas: DataFrames en Python
tutorial