2009-12-16 10 views
702

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 parece a:¿Cómo analizo XML en Python?

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

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

+0

relacionadas: [? Xml Python elementtree de una fuente cadena] (https://stackoverflow.com/q/647071/3357935) –

Respuesta

531

Sugiero ElementTree. Hay otras implementaciones compatibles de la misma API, como lxml y 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 de la parte depende de la API, que define ElementTree.

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

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

o cualquiera de las muchas otras maneras que se muestran en ElementTree, que acaba de hacer algo como:

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

y similares, por lo general bastante simple , patrones de código

+28

Parece que ignora xml.etree.cElementTree que viene con Python y en algunos aspectos es más rápido tham lxml ("iterparse de lxml() es un poco más lento que el de cET" - correo electrónico del autor de lxml). –

+6

ElementTree funciona y se incluye con Python. Sin embargo, hay una compatibilidad limitada con XPath y no se puede recorrer hasta el elemento primario de un elemento, lo que puede ralentizar el desarrollo (especialmente si no se conoce esto). Consulte [python xml query get parent] (http://stackoverflow.com/questions/5373902/python-xml-query-get-parent) para más detalles. – Samuel

+9

'lxml' agrega más que velocidad. Proporciona fácil acceso a la información, como nodo principal, número de línea en la fuente XML, etc. que puede ser muy útil en varios escenarios. –

183

Puede utilizar 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' 
+6

En realidad, hay 'BeautifulStoneSoup' en BeautifulSoup para XML – YOU

+0

Gracias por la información @ibz, Sí, en realidad, si la fuente no está bien formada, será difícil de analizar también para los analizadores. – YOU

+36

tres años después con bs4 esta es una gran solución, muy flexible, especialmente si la fuente no está bien formada – cedbeu

16

Todavía soy un novato de Python, pero mi impresión es que ElementTree es el estado del arte en el análisis y manejo de Python XML.

Mark Pilgrim tiene a good section en Análisis XML con ElementTree en su libro Dive Into Python 3.

17

Python tiene una interfaz para el analizador expat xml.

xml.parsers.expat 

Es un analizador no validador, por lo que xml malo no se detectará. 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 
+0

+1 porque estoy buscando un analizador no validador que funcionará con caracteres fuente extraños. Espero que esto me dé los resultados que quiero. –

+1

El ejemplo se hizo en '09 y así es como se hizo. –

6

encuentro el pitón 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, esto funcionará bien.

346

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> 

Python:

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 
+7

¿Cómo se obtiene el valor de "item1"? Por ejemplo: Value1 swmcdonnell

+73

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 minidom disponibles. El sitio web de python minidom apesta en mi opinión. – Drewdin

35

lxml.objectify es realmente sencillo.

Tomando el 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} 
+0

¿Qué se necesita para hacer en el código? – Clayton

+0

'count' almacena los recuentos de cada elemento en un diccionario con las teclas predeterminadas, por lo que no es necesario verificar la membresía. También puedes intentar mirar 'collections.Counter'. –

8

Aquí un código muy simple pero eficaz 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

70

Hay muchas opciones que hay. 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 de abajo, copiado del cElementTree página 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 ha señalado @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).
+8

¿Hay algún inconveniente al usar cElementTree? Parece ser una obviedad. – mayhewsw

+6

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 los buenos proyectos prosperen, ojalá más personas se den cuenta de eso. – Stunner

+8

@Stunner: está en stdlib, es decir, no necesita descargar nada. En Python 2: 'desde xml.etree import cElementTree como ElementTree'. En Python 3: 'from xml.etree import ElementTree' (la versión acelerada de C se usa automáticamente) – jfs

27

Sugiero xmltodict para simplicidad.

Analiza su 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')])])]) 
+2

De acuerdo. Si no necesita XPath o algo complicado, es mucho más fácil de usar (especialmente en el intérprete); útil para las API REST que publican XML en lugar de JSON –

-3

rec.xml: -

<?xml version="1.0"?> 
<nodes> 
    <node name="Car" child="Engine"></node> 
    <node name="Engine" child="Piston"></node> 
    <node name="Engine" child="Carb"></node> 
    <node name="Car" child="Wheel"></node> 
    <node name="Wheel" child="Hubcaps"></node> 
    <node name="Truck" child="Engine"></node> 
    <node name="Truck" child="Loading Bin"></node> 
    <node name="Piston" child="Loa"></node> 
    <node name="Spare Wheel" child=""></node> 
</nodes> 

par.py:-

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

for nodes in root.findall('node'): 
    parent = nodes.attrib.get('name') 
    child = nodes.attrib.get('child') 
    print parent,child 
+2

, esto no agrega nada que no se haya proporcionado en respuestas anteriores. el modelo xml es horrible, no tiene sentido almacenar una parte (nodo) varias veces solo para listar sus hijos. –

3
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 atributo foobar.

7

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

instalación

pip install untangle 

Uso

el archivo XML (cambió un poco):

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

acceder 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'] 

la salida será:

bar_name 
1 

Más información sobre untangle se puede encontrar here.
También (si tiene curiosidad), puede encontrar una lista de herramientas para trabajar con XML y Python here (también verá que las más comunes fueron mencionadas por respuestas anteriores).

2

¿Habla en serio?

¿Qué pasa con las preocupaciones de seguridad? Use defusedxml.

Esto también es recomendado por Two Scoops of Django.

Comparación sobre defusedxml vs otras bibliotecas

LXML está protegido contra ataques de mil millones de risas y no hace operaciones de búsqueda de la red por defecto.

libxml2 y lxml no son directamente vulnerables a las bombas de descompresión de gzip pero tampoco te protegen contra ellas.

xml.etree no expande entidades y genera un ParserError cuando se produce una entidad .

minidom no expande entidades y simplemente devuelve la entidad sin expandir literal.

genshi.input de genshi 0.6 no admite la expansión de entidad y aumenta un ParserError cuando se produce una entidad.

La biblioteca tiene (limitado) compatibilidad con XInclude pero requiere un paso adicional para procesar la inclusión.

4

Puedo 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, usted usa procesadores para definir declarativamente la estructura de su documento XML y cómo mapear entre 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 en 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 utilizar el mismo procesador para serializar los datos a 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])} 
Cuestiones relacionadas