¿Cómo analizo XML en Python?

1003

Tengo muchas filas en una base de datos que contiene XML y estoy tratando de escribir un script de Python para contar instancias de un atributo de nodo particular.

Mi árbol se ve así:

<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>

¿Cómo puedo acceder a los atributos "1"y "2"en el XML usando Python?

randombits
fuente

Respuestas:

781

Sugiero ElementTree. Hay otras implementaciones compatibles de la misma API, como lxml, y cElementTreeen la propia biblioteca estándar de Python; pero, en este contexto, lo que principalmente agregan es aún más velocidad: la facilidad de programación depende de la API, que ElementTreedefine.

Primero construya una instancia de Element a rootpartir del XML, por ejemplo, con la función XML , o analizando un archivo con algo como:

import xml.etree.ElementTree as ET
root = ET.parse('thefile.xml').getroot()

O cualquiera de las muchas otras formas que se muestran en ElementTree. Luego haz algo como:

for type_tag in root.findall('bar/type'):
    value = type_tag.get('foobar')
    print(value)

Y patrones de código similares, generalmente bastante simples.

Alex Martelli
fuente
41
Parece ignorar xml.etree.cElementTree que viene con Python y, en algunos aspectos, es más rápido que lxml ("lxml's iterparse () es un poco más lento que el de cET" - correo electrónico del autor lxml).
John Machin
77
ElementTree funciona y se incluye con Python. Sin embargo, existe un soporte limitado de XPath y no puede atravesar el elemento primario de un elemento, lo que puede ralentizar el desarrollo (especialmente si no lo sabe). Consulte la consulta xml de python get parent para obtener más detalles.
Samuel
11
lxmlagrega más que velocidad. Proporciona fácil acceso a información como el nodo principal, el número de línea en la fuente XML, etc. que puede ser muy útil en varios escenarios.
Saheel Godhane
13
Parece que ElementTree tiene algunos problemas de vulnerabilidad, esta es una cita de los documentos: Warning The xml.etree.ElementTree module is not secure against maliciously constructed data. If you need to parse untrusted or unauthenticated data see XML vulnerabilities.
Cristik
55
@Cristik Este parece ser el caso con la mayoría de los analizadores XML, consulte la página de vulnerabilidades XML .
gitaarik
427

minidom es el más rápido y bastante sencillo.

XML:

<data>
    <items>
        <item name="item1"></item>
        <item name="item2"></item>
        <item name="item3"></item>
        <item name="item4"></item>
    </items>
</data>

Pitón:

from xml.dom import minidom
xmldoc = minidom.parse('items.xml')
itemlist = xmldoc.getElementsByTagName('item')
print(len(itemlist))
print(itemlist[0].attributes['name'].value)
for s in itemlist:
    print(s.attributes['name'].value)

Salida:

4
item1
item1
item2
item3
item4
Ryan Christensen
fuente
99
¿Cómo se obtiene el valor de "item1"? Por ejemplo: <item name = "item1"> Value1 </item>
swmcdonnell
88
Lo descubrí, en caso de que alguien tenga la misma pregunta. Es s.childNodes [0] .nodeValue
swmcdonnell
1
Me gusta su ejemplo, quiero implementarlo, pero ¿dónde puedo encontrar las funciones de minidom disponibles? El sitio web de Python Minidom apesta en mi opinión.
Drewdin
1
También estoy confundido por qué se encuentra itemdirectamente desde el nivel superior del documento? ¿no sería más limpio si le proporcionaras la ruta ( data->items)? porque, ¿qué pasaría si también tuvieras data->secondSetOfItemsque también tuvieran nodos nombrados itemy quisieras enumerar solo uno de los dos conjuntos de item?
anfibio
240

Puedes usar BeautifulSoup :

from bs4 import BeautifulSoup

x="""<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>"""

y=BeautifulSoup(x)
>>> y.foo.bar.type["foobar"]
u'1'

>>> y.foo.bar.findAll("type")
[<type foobar="1"></type>, <type foobar="2"></type>]

>>> y.foo.bar.findAll("type")[0]["foobar"]
u'1'
>>> y.foo.bar.findAll("type")[1]["foobar"]
u'2'

fuente
Gracias por la información @ibz, sí, en realidad, si la fuente no está bien formada, será difícil analizar los analizadores también.
USTED
45
tres años más tarde con bs4 esta es una gran solución, muy flexible, especialmente si la fuente no está bien formada
cedbeu
8
@YOU BeautifulStoneSoupestá DEPRECIADO. Solo useBeautifulSoup(source_xml, features="xml")
andilabs
55
Otros 3 años más tarde, solo intenté cargar XML usando ElementTree, desafortunadamente no puedo analizar a menos que ajuste la fuente en algunos lugares, ¡pero BeautifulSoupfuncioné de inmediato sin ningún cambio!
ViKiG
8
@andi Te refieres a "obsoleto". "Depreciado" significa que disminuyó su valor, generalmente debido a la edad o al desgaste por el uso normal.
jpmc26
98

Hay muchas opciones alla afuera. cElementTree se ve excelente si la velocidad y el uso de memoria son un problema. Tiene muy poca sobrecarga en comparación con simplemente leer en el archivo usando readlines.

Las métricas relevantes se pueden encontrar en la tabla a continuación, copiadas de sitio web cElementTree :

library                         time    space
xml.dom.minidom (Python 2.1)    6.3 s   80000K
gnosis.objectify                2.0 s   22000k
xml.dom.minidom (Python 2.4)    1.4 s   53000k
ElementTree 1.2                 1.6 s   14500k  
ElementTree 1.2.4/1.3           1.1 s   14500k  
cDomlette (C extension)         0.540 s 20500k
PyRXPU (C extension)            0.175 s 10850k
libxml2 (C extension)           0.098 s 16000k
readlines (read as utf-8)       0.093 s 8850k
cElementTree (C extension)  --> 0.047 s 4900K <--
readlines (read as ascii)       0.032 s 5050k   

Como lo señaló @jfs , cElementTreeviene incluido con Python:

  • Python 2: from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(la versión C acelerada se usa automáticamente).
Ciro
fuente
99
¿Hay alguna desventaja al usar cElementTree? Parece ser una obviedad.
mayhewsw
66
Aparentemente no quieren usar la biblioteca en OS X, ya que he pasado más de 15 minutos tratando de averiguar dónde descargarla y no funciona ningún enlace. La falta de documentación impide que prosperen los buenos proyectos, ojalá más personas se den cuenta de eso.
Aturdidor el
8
@Stunner: está en stdlib, es decir, no necesita descargar nada. En Python 2: from xml.etree import cElementTree as ElementTree. En Python 3: from xml.etree import ElementTree(la versión C acelerada se usa automáticamente)
jfs
1
@mayhewsw Es más esfuerzo descubrir cómo usar de manera eficiente ElementTreepara una tarea en particular. Para documentos que caben en la memoria, es mucho más fácil de usar minidomy funciona bien para documentos XML más pequeños.
Acumenus
44

Sugiero xmltodict por simplicidad.

Analiza tu XML a un OrderedDict;

>>> e = '<foo>
             <bar>
                 <type foobar="1"/>
                 <type foobar="2"/>
             </bar>
        </foo> '

>>> import xmltodict
>>> result = xmltodict.parse(e)
>>> result

OrderedDict([(u'foo', OrderedDict([(u'bar', OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])]))]))])

>>> result['foo']

OrderedDict([(u'bar', OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])]))])

>>> result['foo']['bar']

OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])])
myildirim
fuente
3
Convenido. Si no necesita XPath o algo complicado, esto es mucho más simple de usar (especialmente en el intérprete); útil para las API REST que publican XML en lugar de JSON
Dan Passaro
44
Recuerde que OrderedDict no admite claves duplicadas. La mayoría de XML está repleto de múltiples hermanos del mismo tipo (por ejemplo, todos los párrafos de una sección o todos los tipos de su barra). Por lo tanto, esto solo funcionará para casos especiales muy limitados.
TextGeek
2
@TextGeek En este caso, result["foo"]["bar"]["type"]es una lista de todos los <type>elementos, por lo que aún funciona (aunque la estructura sea un poco inesperada).
luator
38

lxml.objectify es realmente simple.

Tomando su texto de muestra:

from lxml import objectify
from collections import defaultdict

count = defaultdict(int)

root = objectify.fromstring(text)

for item in root.bar.type:
    count[item.attrib.get("foobar")] += 1

print dict(count)

Salida:

{'1': 1, '2': 1}
Ryan Ginstrom
fuente
countalmacena los recuentos de cada elemento en un diccionario con claves predeterminadas, para que no tenga que verificar la membresía. También puedes intentar mirar collections.Counter.
Ryan Ginstrom
20

Python tiene una interfaz para el analizador XML de expatriados.

xml.parsers.expat

Es un analizador no validado, por lo que no se detectará un XML incorrecto. Pero si sabe que su archivo es correcto, entonces esto es bastante bueno, y probablemente obtendrá la información exacta que desea y puede descartar el resto sobre la marcha.

stringofxml = """<foo>
    <bar>
        <type arg="value" />
        <type arg="value" />
        <type arg="value" />
    </bar>
    <bar>
        <type arg="value" />
    </bar>
</foo>"""
count = 0
def start(name, attr):
    global count
    if name == 'type':
        count += 1

p = expat.ParserCreate()
p.StartElementHandler = start
p.Parse(stringofxml)

print count # prints 4
Tor Valamo
fuente
+1 porque estoy buscando un analizador no validado que funcione con extraños caracteres de origen. Espero que esto me dé los resultados que quiero.
Nathan C. Tresch
1
El ejemplo se hizo en el '09 y así es como se hizo.
Tor Valamo
14

Podría sugerir declxml .

Divulgación completa: escribí esta biblioteca porque estaba buscando una forma de convertir entre estructuras de datos XML y Python sin necesidad de escribir docenas de líneas de código de análisis / serialización imperativo con ElementTree.

Con declxml, utiliza procesadores para definir declarativamente la estructura de su documento XML y cómo mapear entre las estructuras de datos XML y Python. Los procesadores se utilizan tanto para la serialización y el análisis como para un nivel básico de validación.

Analizar las estructuras de datos de Python es sencillo:

import declxml as xml

xml_string = """
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>
"""

processor = xml.dictionary('foo', [
    xml.dictionary('bar', [
        xml.array(xml.integer('type', attribute='foobar'))
    ])
])

xml.parse_from_string(processor, xml_string)

Que produce la salida:

{'bar': {'foobar': [1, 2]}}

También puede usar el mismo procesador para serializar datos a XML

data = {'bar': {
    'foobar': [7, 3, 21, 16, 11]
}}

xml.serialize_to_string(processor, data, indent='    ')

Que produce el siguiente resultado

<?xml version="1.0" ?>
<foo>
    <bar>
        <type foobar="7"/>
        <type foobar="3"/>
        <type foobar="21"/>
        <type foobar="16"/>
        <type foobar="11"/>
    </bar>
</foo>

Si desea trabajar con objetos en lugar de diccionarios, también puede definir procesadores para transformar datos hacia y desde objetos.

import declxml as xml

class Bar:

    def __init__(self):
        self.foobars = []

    def __repr__(self):
        return 'Bar(foobars={})'.format(self.foobars)


xml_string = """
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>
"""

processor = xml.dictionary('foo', [
    xml.user_object('bar', Bar, [
        xml.array(xml.integer('type', attribute='foobar'), alias='foobars')
    ])
])

xml.parse_from_string(processor, xml_string)

Que produce el siguiente resultado

{'bar': Bar(foobars=[1, 2])}
Gatkin
fuente
13

Solo para agregar otra posibilidad, puede usar desenredar , ya que es una biblioteca simple de xml a python-object. Aquí tienes un ejemplo:

Instalación:

pip install untangle

Uso:

Su archivo XML (un poco cambiado):

<foo>
   <bar name="bar_name">
      <type foobar="1"/>
   </bar>
</foo>

Accediendo a los atributos con untangle:

import untangle

obj = untangle.parse('/path_to_xml_file/file.xml')

print obj.foo.bar['name']
print obj.foo.bar.type['foobar']

El resultado será:

bar_name
1

Se puede encontrar más información sobre desenredar en " desenredar ".

Además, si tiene curiosidad, puede encontrar una lista de herramientas para trabajar con XML y Python en " Python y XML ". También verá que los más comunes fueron mencionados en respuestas anteriores.

jchanger
fuente
¿Qué hace que desenredar sea diferente del minidom?
Aaron Mann
No puedo decirte la diferencia entre esos dos, ya que no he trabajado con minidom.
jchanger
10

Aquí un código muy simple pero efectivo usando cElementTree.

try:
    import cElementTree as ET
except ImportError:
  try:
    # Python 2.5 need to import a different module
    import xml.etree.cElementTree as ET
  except ImportError:
    exit_err("Failed to import cElementTree from any known place")      

def find_in_tree(tree, node):
    found = tree.find(node)
    if found == None:
        print "No %s in file" % node
        found = []
    return found  

# Parse a xml file (specify the path)
def_file = "xml_file_name.xml"
try:
    dom = ET.parse(open(def_file, "r"))
    root = dom.getroot()
except:
    exit_err("Unable to open and parse input definition file: " + def_file)

# Parse to find the child nodes list of node 'myNode'
fwdefs = find_in_tree(root,"myNode")

Esto es de " python xml parse ".

Jan Kohila
fuente
7

XML:

<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>

Código de Python:

import xml.etree.cElementTree as ET

tree = ET.parse("foo.xml")
root = tree.getroot() 
root_tag = root.tag
print(root_tag) 

for form in root.findall("./bar/type"):
    x=(form.attrib)
    z=list(x)
    for i in z:
        print(x[i])

Salida:

foo
1
2
Ahito
fuente
6
import xml.etree.ElementTree as ET
data = '''<foo>
           <bar>
               <type foobar="1"/>
               <type foobar="2"/>
          </bar>
       </foo>'''
tree = ET.fromstring(data)
lst = tree.findall('bar/type')
for item in lst:
    print item.get('foobar')

Esto imprimirá el valor del foobaratributo.

Souvik Dey
fuente
6

xml.etree.ElementTree vs. lxml

Estas son algunas de las ventajas de las dos bibliotecas más utilizadas que me beneficiaría saber antes de elegir entre ellas.

xml.etree.ElementTree:

  1. Desde la biblioteca estándar : no es necesario instalar ningún módulo

lxml

  1. Escriba fácilmente la declaración XML : por ejemplo, ¿necesita agregar standalone="no"?
  2. Impresión bonita : puede tener un buen XML sangrado sin código adicional.
  3. Funcionalidad Objectify : le permite usar XML como si estuviera tratando con una jerarquía de objetos Python normal .node.
  4. sourceline permite obtener fácilmente la línea del elemento XML que está utilizando.
  5. También puede utilizar un verificador de esquema XSD incorporado.
GM
fuente
5

Encuentro Python xml.dom y xml.dom.minidom bastante fácil. Tenga en cuenta que DOM no es bueno para grandes cantidades de XML, pero si su entrada es bastante pequeña, entonces funcionará bien.

EMP
fuente
2

No es necesario usar una API específica de lib si la usa python-benedict. Simplemente inicialice una nueva instancia desde su XML y adminístrela fácilmente, ya que es una dictsubclase.

La instalación es fácil: pip install python-benedict

from benedict import benedict as bdict

# data-source can be an url, a filepath or data-string (as in this example)
data_source = """
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>"""

data = bdict.from_xml(data_source)
t_list = data['foo.bar'] # yes, keypath supported
for t in t_list:
   print(t['@foobar'])

Apoya y normaliza operaciones de E / S con muchos formatos: Base64, CSV, JSON, TOML, XML, YAMLy query-string.

Está bien probado y de código abierto en GitHub .

Fabio Caccamo
fuente
0
#If the xml is in the form of a string as shown below then
from lxml  import etree, objectify
'''sample xml as a string with a name space {http://xmlns.abc.com}'''
message =b'<?xml version="1.0" encoding="UTF-8"?>\r\n<pa:Process xmlns:pa="http://xmlns.abc.com">\r\n\t<pa:firsttag>SAMPLE</pa:firsttag></pa:Process>\r\n'  # this is a sample xml which is a string


print('************message coversion and parsing starts*************')

message=message.decode('utf-8') 
message=message.replace('<?xml version="1.0" encoding="UTF-8"?>\r\n','') #replace is used to remove unwanted strings from the 'message'
message=message.replace('pa:Process>\r\n','pa:Process>')
print (message)

print ('******Parsing starts*************')
parser = etree.XMLParser(remove_blank_text=True) #the name space is removed here
root = etree.fromstring(message, parser) #parsing of xml happens here
print ('******Parsing completed************')


dict={}
for child in root: # parsed xml is iterated using a for loop and values are stored in a dictionary
    print(child.tag,child.text)
    print('****Derving from xml tree*****')
    if child.tag =="{http://xmlns.abc.com}firsttag":
        dict["FIRST_TAG"]=child.text
        print(dict)


### output
'''************message coversion and parsing starts*************
<pa:Process xmlns:pa="http://xmlns.abc.com">

    <pa:firsttag>SAMPLE</pa:firsttag></pa:Process>
******Parsing starts*************
******Parsing completed************
{http://xmlns.abc.com}firsttag SAMPLE
****Derving from xml tree*****
{'FIRST_TAG': 'SAMPLE'}'''
Siraj
fuente
Incluya también un contexto que explique cómo su respuesta resuelve el problema. Las respuestas de solo código no son recomendables.
Pedram Parsian
-1

Si la fuente es un archivo xml, digamos como esta muestra

<pa:Process xmlns:pa="http://sssss">
        <pa:firsttag>SAMPLE</pa:firsttag>
    </pa:Process>

puedes probar el siguiente código

from lxml import etree, objectify
metadata = 'C:\\Users\\PROCS.xml' # this is sample xml file the contents are shown above
parser = etree.XMLParser(remove_blank_text=True) # this line removes the  name space from the xml in this sample the name space is --> http://sssss
tree = etree.parse(metadata, parser) # this line parses the xml file which is PROCS.xml
root = tree.getroot() # we get the root of xml which is process and iterate using a for loop
for elem in root.getiterator():
    if not hasattr(elem.tag, 'find'): continue  # (1)
    i = elem.tag.find('}')
    if i >= 0:
        elem.tag = elem.tag[i+1:]

dict={}  # a python dictionary is declared
for elem in tree.iter(): #iterating through the xml tree using a for loop
    if elem.tag =="firsttag": # if the tag name matches the name that is equated then the text in the tag is stored into the dictionary
        dict["FIRST_TAG"]=str(elem.text)
        print(dict)

La salida sería

{'FIRST_TAG': 'SAMPLE'}
Siraj
fuente