Pregunta ¿Cómo analizo XML en Python?


Tengo muchas filas en una base de datos que contiene xml y estoy tratando de escribir un script de Python que recorra esas filas y cuente cuántas instancias de un atributo de nodo particular aparecen. Por ejemplo, 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?


760
2017-12-16 05:09


origen


Respuestas:


yo sugiero ElementTree. Hay otras implementaciones compatibles de la misma API, como lxmly cElementTree en la biblioteca estándar de Python; pero, en este contexto, lo que agregan principalmente es aún más velocidad: la facilidad de programación depende de la API, que ElementTree define.

Después de crear una instancia de Element e del XML, p. con el XML función, o al analizar un archivo con algo así como

import xml.etree.ElementTree
e = xml.etree.ElementTree.parse('thefile.xml').getroot()

o cualquiera de las muchas otras formas que se muestran en ElementTree, solo haces algo como:

for atype in e.findall('type'):
    print(atype.get('foobar'))

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


582
2017-12-16 05:21



minidom es el más directo y 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

372
2017-12-16 05:30



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'

201
2017-12-16 05:12



Hay muchas opciones alla afuera. cElementTree se ve excelente si la velocidad y el uso de la 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 cElementTree sitio web:

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 se señala por @jfs, cElementTree viene 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).

76
2017-10-10 17:44



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}

36
2017-12-16 10:42



yo 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')])])])

30
2018-06-12 11:57



Python tiene una interfaz para el analizador expat xml.

xml.parsers.expat

Es un analizador no validador, por lo que no se detectará el xml malo. Pero si sabes que tu archivo es correcto, entonces esto es bastante bueno, y probablemente obtenga 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

17
2017-12-16 05:28



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")

Fuente:

http://www.snip2code.com/Snippet/991/python-xml-parse?fromPage=1


8
2017-07-08 20:35



Solo para agregar otra posibilidad, puedes 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>

acceder a los atributos con desenredar:

import untangle

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

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

la salida será:

bar_name
1

Se puede encontrar más información sobre cómo desenredar aquí.
Además (si tiene curiosidad), puede encontrar una lista de herramientas para trabajar con XML y Python aquí (También verá que los más comunes fueron mencionados por respuestas anteriores).


8
2018-03-17 09:10



Podría sugerir declxml.

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

Con declxml, usas procesadores para definir declarativamente la estructura de su documento XML y cómo mapear entre las estructuras de datos de 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 en XML

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

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

Que produce la siguiente salida

<?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, puede definir procesadores para transformar datos hacia y desde objetos también.

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 la siguiente salida

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

7
2017-09-04 17:40