¿Cómo analizar XML y contar instancias de un atributo de nodo en particular?
Frecuentes
Visto 1,401 veces
1079
Tengo muchas filas en una base de datos que contiene XML y estoy tratando de escribir una secuencia de comandos de Python para contar las instancias de un atributo de nodo en particular.
Mi árbol se parece a:
<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?
18 Respuestas
856
Le sugiero ElementTree
. Hay otras implementaciones compatibles de la misma API, como lxml
y cElementTree
en la propia biblioteca estándar de Python; pero, en este contexto, lo que principalmente agregan es aún más velocidad: la parte de la facilidad de programación depende de la API, que ElementTree
define.
Primero construye una instancia de Element root
del XML, por ejemplo, con el XML función, 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
. Entonces 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.
Respondido el 13 de diciembre de 19 a las 00:12
Parece ignorar xml.etree.cElementTree que viene con Python y en algunos aspectos es más rápido que lxml ("iterparse () de lxml es un poco más lento que el de cET" - correo electrónico del autor de lxml). - Juan maquin
ElementTree funciona y se incluye con Python. Sin embargo, existe una compatibilidad limitada con XPath y no puede pasar al padre de un elemento, lo que puede ralentizar el desarrollo (especialmente si no lo sabe). Ver consulta python xml obtener padre para detalles. - Samuel
lxml
agrega más que velocidad. Proporciona un 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
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
@Cristik Este parece ser el caso con la mayoría de los analizadores xml, consulte el Página de vulnerabilidades XML. - gitaarik
449
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
Respondido el 12 de diciembre de 19 a las 23:12
¿Cómo se obtiene el valor de "item1"? Por ejemplo: Valor1 - Swmcdonnell
donde esta la documentacion para minidom
? Solo encontré esto, pero eso no funciona: docs.python.org/2/library/xml.dom.minidom.html - anfibio
También estoy confundido por qué encuentra item
directamente desde el nivel superior del documento? ¿No sería más limpio si le proporcionara la ruta (data->items
)? porque, y si tu tambien tuvieras data->secondSetOfItems
que también tenía nodos nombrados item
y deseaba enumerar solo uno de los dos conjuntos de item
? - anfibio
por favor mira stackoverflow.com/questions/21124018/… - anfibio
La sintaxis no funcionará aquí, debe eliminar el paréntesis for s in itemlist: print(s.attributes['name'].value)
- Alex Borsody
250
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'
Respondido el 12 de diciembre de 19 a las 23:12
tres años después, con bs4, esta es una gran solución, muy flexible, especialmente si la fuente no está bien formada. cedbeu
@USTED BeautifulStoneSoup
es DEPRECIADO. Solo usa BeautifulSoup(source_xml, features="xml")
- andilalabs
Otros 3 años después, intenté cargar XML usando ElementTree
, desafortunadamente no se puede analizar a menos que ajuste la fuente en algunos lugares, pero BeautifulSoup
funcionó de inmediato sin ningún cambio! - ViKiG
@andi Quieres decir "obsoleto". "Depreciado" significa que su valor disminuyó, generalmente debido a la edad o al desgaste debido al uso normal. - jpmc26
ElementTree y minidom se ahogaron con datos XML válidos diciendo que había datos XML no válidos, y BeautifulSoup pudo procesarlos sin problemas. +1 - leetNocturna
105
Hay muchas opciones alla afuera. ÁrbolCemento 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 del ÁrbolCemento 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:
- Pitón 2:
from xml.etree import cElementTree as ElementTree
. - Pitón 3:
from xml.etree import ElementTree
(la versión C acelerada se utiliza automáticamente).
Respondido el 31 de enero de 18 a las 05:01
¿Existen desventajas en el uso de cElementTree? Parece una obviedad. - Mayhewsw
Aparentemente, no quieren usar la biblioteca en OS X, ya que he pasado más de 15 minutos tratando de averiguar de dónde descargarla y ningún enlace funciona. La falta de documentación impide que los buenos proyectos prosperen, desearía que más personas se dieran cuenta de eso. - Stunner
@Stunner: está en stdlib, es decir, no necesitas 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
@mayhewsw Es más esfuerzo descubrir cómo usar de manera eficiente ElementTree
para una tarea en particular. Para documentos que quepan en la memoria, es mucho más fácil de usar. minidom
y funciona bien para documentos XML más pequeños. - agudeza
46
Le sugiero xmltodict por 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')])])])
Respondido el 12 de diciembre de 19 a las 23:12
Acordado. Si no necesita XPath ni nada complicado, esto es mucho más sencillo de usar (especialmente en el intérprete); útil para las API REST que publican XML en lugar de JSON - Dan Passaro
Recuerde que OrderedDict no admite claves duplicadas. La mayoría de XML está repleto de varios 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
@TextGeek En este caso, result["foo"]["bar"]["type"]
es una lista de todos <type>
elementos, por lo que todavía está funcionando (aunque la estructura es quizás un poco inesperada). - luador
39
lxml.objetar 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}
Respondido el 07 de junio de 13 a las 09:06
count
almacena los recuentos de cada elemento en un diccionario con claves predeterminadas, por lo que no tiene que verificar la membresía. También puedes intentar mirar collections.Counter
. - ryan ginström
20
Python tiene una interfaz para el analizador XML expatriado.
xml.parsers.expat
Es un analizador no validado, por lo que no se detectará 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 podrá 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
Respondido el 12 de diciembre de 19 a las 23:12
17
Solo para agregar otra posibilidad, puede usar desenredar, ya que es una biblioteca simple de objetos xml a python. 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']
La salida será:
bar_name
1
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.
Respondido el 12 de diciembre de 19 a las 23:12
¿Qué diferencia a desenredar del minidom? - aarón mann
No puedo decirte la diferencia entre esos dos porque no he trabajado con minidom. - jcambiador
14
Yo 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 imperativo de análisis / serialización con ElementTree.
Con declxml, usa procesadores para definir de forma declarativa 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 como para el análisis, así como para un nivel básico de validación.
Analizar en 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 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, 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 la siguiente salida
{'bar': Bar(foobars=[1, 2])}
Respondido el 04 de Septiembre de 17 a las 18:09
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 "análisis de Python XML".
Respondido el 12 de diciembre de 19 a las 23:12
10
XML:
<foo>
<bar>
<type foobar="1"/>
<type foobar="2"/>
</bar>
</foo>
Código 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
Respondido el 12 de diciembre de 19 a las 23:12
8
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 de la foobar
atributo.
Respondido el 12 de diciembre de 19 a las 23:12
8
xml.etree.ElementTree frente a lxml
Estas son algunas de las ventajas de las dos bibliotecas más utilizadas que me gustaría conocer antes de elegir entre ellas.
xml.etree.ElementTree:
- Desde el biblioteca estándar: no es necesario instalar ningún módulo
lxml
- Escribir fácilmente Declaración XML: por ejemplo, necesita agregar
standalone="no"
? - Bonita impresión: puedes tener un buen sangrado XML sin código adicional.
- Justificar funcionalidad: le permite usar XML como si estuviera tratando con una jerarquía de objetos Python normal
.node
. sourceline
permite obtener fácilmente la línea del elemento XML que está utilizando.- también puede utilizar un comprobador de esquemas XSD incorporado.
Respondido el 03 de junio de 20 a las 17:06
6
Hay no es necesario utilizar una API específica de lib si usas python-benedict
. Simplemente inicialice una nueva instancia desde su XML y adminístrelo fácilmente ya que es un dict
subclase.
La instalación es sencilla: 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'])
Es compatible y normaliza Operaciones de E / S con muchos formatos: Base64
, CSV
, JSON
, TOML
, XML
, YAML
y query-string
.
Está bien probado y es de código abierto en GitHub. Divulgación: soy el autor.
Respondido 29 Oct 20, 17:10
2
Una nueva biblioteca, me enamoré de ella después de usarla. Te lo recomiendo.
from simplified_scrapy import SimplifiedDoc
xml = '''
<foo>
<bar>
<type foobar="1"/>
<type foobar="2"/>
</bar>
</foo>
'''
doc = SimplifiedDoc(xml)
types = doc.selects('bar>type')
print (len(types)) # 2
print (types.foobar) # ['1', '2']
print (doc.selects('bar>type>foobar()')) # ['1', '2']
Aquí son más ejemplos. Esta biblioteca es fácil de usar.
Respondido 22 Oct 20, 10:10
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'}'''
Respondido 20 Feb 20, 12:02
Incluya también algo de contexto que explique cómo su respuesta resuelve el problema. No se recomiendan las respuestas de solo código. - Pedram parsiano
0
Si no desea utilizar bibliotecas externas o herramientas de terceros, intente con el siguiente código.
- Esto analizará
xml
en pitóndictionary
- Esto también analizará los atributos xml
- Esto también analizará etiquetas vacías como
<tag/>
y etiquetas con solo atributos como<tag var=val/>
Code
import re
def getdict(content):
res=re.findall("<(?P<var>\S*)(?P<attr>[^/>]*)(?:(?:>(?P<val>.*?)</(?P=var)>)|(?:/>))",content)
if len(res)>=1:
attreg="(?P<avr>\S+?)(?:(?:=(?P<quote>['\"])(?P<avl>.*?)(?P=quote))|(?:=(?P<avl1>.*?)(?:\s|$))|(?P<avl2>[\s]+)|$)"
if len(res)>1:
return [{i[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,i[1].strip())]},{"$values":getdict(i[2])}]} for i in res]
else:
return {res[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,res[1].strip())]},{"$values":getdict(res[2])}]}
else:
return content
with open("test.xml","r") as f:
print(getdict(f.read().replace('\n','')))
Entrada de muestra
<details class="4b" count=1 boy>
<name type="firstname">John</name>
<age>13</age>
<hobby>Coin collection</hobby>
<hobby>Stamp collection</hobby>
<address>
<country>USA</country>
<state>CA</state>
</address>
</details>
<details empty="True"/>
<details/>
<details class="4a" count=2 girl>
<name type="firstname">Samantha</name>
<age>13</age>
<hobby>Fishing</hobby>
<hobby>Chess</hobby>
<address current="no">
<country>Australia</country>
<state>NSW</state>
</address>
</details>
Salida (Embellecido)
[
{
"details": [
{
"@attributes": [
{
"class": "4b"
},
{
"count": "1"
},
{
"boy": ""
}
]
},
{
"$values": [
{
"name": [
{
"@attributes": [
{
"type": "firstname"
}
]
},
{
"$values": "John"
}
]
},
{
"age": [
{
"@attributes": []
},
{
"$values": "13"
}
]
},
{
"hobby": [
{
"@attributes": []
},
{
"$values": "Coin collection"
}
]
},
{
"hobby": [
{
"@attributes": []
},
{
"$values": "Stamp collection"
}
]
},
{
"address": [
{
"@attributes": []
},
{
"$values": [
{
"country": [
{
"@attributes": []
},
{
"$values": "USA"
}
]
},
{
"state": [
{
"@attributes": []
},
{
"$values": "CA"
}
]
}
]
}
]
}
]
}
]
},
{
"details": [
{
"@attributes": [
{
"empty": "True"
}
]
},
{
"$values": ""
}
]
},
{
"details": [
{
"@attributes": []
},
{
"$values": ""
}
]
},
{
"details": [
{
"@attributes": [
{
"class": "4a"
},
{
"count": "2"
},
{
"girl": ""
}
]
},
{
"$values": [
{
"name": [
{
"@attributes": [
{
"type": "firstname"
}
]
},
{
"$values": "Samantha"
}
]
},
{
"age": [
{
"@attributes": []
},
{
"$values": "13"
}
]
},
{
"hobby": [
{
"@attributes": []
},
{
"$values": "Fishing"
}
]
},
{
"hobby": [
{
"@attributes": []
},
{
"$values": "Chess"
}
]
},
{
"address": [
{
"@attributes": [
{
"current": "no"
}
]
},
{
"$values": [
{
"country": [
{
"@attributes": []
},
{
"$values": "Australia"
}
]
},
{
"state": [
{
"@attributes": []
},
{
"$values": "NSW"
}
]
}
]
}
]
}
]
}
]
}
]
Respondido 26 ago 20, 09:08
Es un buen método, pero el resultado que devuelve no es conveniente de usar. - yazz
-1
Si la fuente es un archivo xml, diga como este ejemplo
<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'}
Respondido 20 Feb 20, 12:02
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas python xml or haz tu propia pregunta.
Relacionado: Python xml ElementTree de una fuente de cadena? - Stevoisiak