Wie analysiere ich XML in Python?

1003

Ich habe viele Zeilen in einer Datenbank, die XML enthält, und ich versuche, ein Python-Skript zu schreiben, um Instanzen eines bestimmten Knotenattributs zu zählen.

Mein Baum sieht aus wie:

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

Wie kann ich mit Python auf die Attribute "1"und "2"im XML zugreifen ?

Randombits
quelle

Antworten:

780

Ich schlage vor ElementTree. Es gibt andere kompatible Implementierungen derselben API, z. B. lxmlund cElementTreein der Python-Standardbibliothek selbst. In diesem Zusammenhang wird jedoch hauptsächlich noch mehr Geschwindigkeit hinzugefügt - die einfache Programmierung hängt von der API ab, die ElementTreedefiniert.

Erstellen Sie zuerst eine Elementinstanz rootaus dem XML, z. B. mit der XML- Funktion, oder analysieren Sie eine Datei mit folgenden Elementen :

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

Oder eine der vielen anderen Möglichkeiten, die unter gezeigt werden ElementTree. Dann mach so etwas wie:

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

Und ähnliche, normalerweise ziemlich einfache Codemuster.

Alex Martelli
quelle
41
Sie scheinen xml.etree.cElementTree zu ignorieren, das mit Python geliefert wird und in einigen Aspekten schneller ist als lxml ("lxml's iterparse () ist etwas langsamer als das in cET" - E-Mail vom lxml-Autor).
John Machin
7
ElementTree funktioniert und ist in Python enthalten. Es gibt jedoch nur begrenzte XPath-Unterstützung und Sie können nicht zum übergeordneten Element eines Elements wechseln, was die Entwicklung verlangsamen kann (insbesondere, wenn Sie dies nicht wissen). Weitere Informationen finden Sie unter Python xml query get parent .
Samuel
11
lxmlfügt mehr als Geschwindigkeit hinzu. Es bietet einfachen Zugriff auf Informationen wie den übergeordneten Knoten, die Zeilennummer in der XML-Quelle usw., die in verschiedenen Szenarien sehr nützlich sein können.
Saheel Godhane
13
Scheint, dass ElementTree einige Schwachstellenprobleme hat, dies ist ein Zitat aus den Dokumenten: 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
5
@Cristik Dies scheint bei den meisten XML-Parsern der Fall zu sein, siehe Seite XML-Schwachstellen .
Gitaarik
427

minidom ist am schnellsten und ziemlich einfach.

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)

Ausgabe:

4
item1
item1
item2
item3
item4
Ryan Christensen
quelle
9
Wie erhält man den Wert von "item1"? Zum Beispiel: <item name = "item1"> Wert1 </ item>
swmcdonnell
88
Ich habe es herausgefunden, falls jemand die gleiche Frage hat. Es ist s.childNodes [0] .nodeValue
swmcdonnell
1
Ich mag Ihr Beispiel, ich möchte es implementieren, aber wo finde ich die verfügbaren Minidom-Funktionen. Die Python Minidom Website ist meiner Meinung nach scheiße.
Drewdin
1
Ich bin auch verwirrt, warum es itemdirekt von der obersten Ebene des Dokuments findet? Wäre es nicht sauberer, wenn Sie ihm den Pfad ( data->items) geben würden? denn was wäre, wenn Sie auch data->secondSetOfItemsKnoten hätten itemund nur einen der beiden Sätze auflisten wollten item?
Amphibient
240

Sie können BeautifulSoup verwenden :

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'
DU
quelle
Vielen Dank für info @ibz, Ja, eigentlich, wenn die Quelle nicht gut geformt ist, wird es auch schwierig sein, nach Parsern zu analysieren.
SIE
45
drei Jahre später mit bs4 ist dies eine großartige Lösung, sehr flexibel, besonders wenn die Quelle nicht gut geformt ist
cedbeu
8
@YOU BeautifulStoneSoupist DEPECIATED. Verwenden BeautifulSoup(source_xml, features="xml")
Sie
5
Weitere 3 Jahre später habe ich nur versucht, XML mit zu laden ElementTree. Leider kann es nicht analysiert werden , es sei denn, ich passe die Quelle an bestimmten Stellen an, habe aber BeautifulSoupsofort ohne Änderungen gearbeitet!
ViKiG
8
@andi Du meinst "veraltet". "Abgeschrieben" bedeutet, dass der Wert abnimmt, normalerweise aufgrund des Alters oder der Abnutzung durch normalen Gebrauch.
jpmc26
98

Es gibt viele Möglichkeiten da draußen. cElementTree sieht hervorragend aus, wenn Geschwindigkeit und Speichernutzung ein Problem darstellen. Es hat sehr wenig Aufwand im Vergleich zum einfachen Einlesen der Datei mit readlines.

Die relevanten Metriken finden Sie in der folgenden Tabelle, die von der cElementTree- Website kopiert wurde :

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   

Wie von @jfs hervorgehoben , wird cElementTreePython mitgeliefert:

  • Python 2 : from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(Die beschleunigte C-Version wird automatisch verwendet).
Cyrus
quelle
9
Gibt es Nachteile bei der Verwendung von cElementTree? Es scheint ein Kinderspiel zu sein.
Mayhewsw
6
Anscheinend wollen sie die Bibliothek unter OS X nicht verwenden, da ich über 15 Minuten damit verbracht habe, herauszufinden, von wo sie heruntergeladen werden kann und kein Link funktioniert. Mangelnde Dokumentation verhindert, dass gute Projekte gedeihen. Ich wünschte, mehr Menschen würden dies realisieren.
Stunner
8
@Stunner: Es ist in stdlib, dh Sie müssen nichts herunterladen. Auf Python 2 : from xml.etree import cElementTree as ElementTree. Auf Python 3: from xml.etree import ElementTree(die beschleunigte C-Version wird automatisch verwendet)
jfs
1
@mayhewsw Es ist mehr Aufwand herauszufinden, wie man effizient ElementTreefür eine bestimmte Aufgabe verwendet. Für Dokumente, die in den Speicher passen, ist die Verwendung viel einfacher minidomund funktioniert für kleinere XML-Dokumente einwandfrei.
Acumenus
44

Der Einfachheit halber schlage ich xmltodict vor .

Es analysiert Ihr XML in ein 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
quelle
3
Einverstanden. Wenn Sie XPath oder etwas Kompliziertes nicht benötigen, ist dies viel einfacher zu verwenden (insbesondere im Interpreter). Praktisch für REST-APIs, die XML anstelle von JSON veröffentlichen
Dan Passaro
4
Denken Sie daran, dass OrderedDict keine doppelten Schlüssel unterstützt. Das meiste XML ist voll von mehreren Geschwistern desselben Typs (z. B. alle Absätze in einem Abschnitt oder alle Typen in Ihrer Leiste). Dies funktioniert also nur für sehr begrenzte Sonderfälle.
TextGeek
2
@TextGeek In diesem Fall result["foo"]["bar"]["type"]handelt es sich um eine Liste aller <type>Elemente, sodass es immer noch funktioniert (obwohl die Struktur möglicherweise etwas unerwartet ist).
Luator
38

lxml.objectify ist wirklich einfach.

Nehmen Sie Ihren Beispieltext:

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)

Ausgabe:

{'1': 1, '2': 1}
Ryan Ginstrom
quelle
countspeichert die Anzahl der einzelnen Elemente in einem Wörterbuch mit Standardschlüsseln, sodass Sie nicht nach einer Mitgliedschaft suchen müssen. Sie können auch versuchen, zu betrachten collections.Counter.
Ryan Ginstrom
20

Python hat eine Schnittstelle zum Expat-XML-Parser.

xml.parsers.expat

Es ist ein nicht validierender Parser, sodass schlechtes XML nicht abgefangen wird. Aber wenn Sie wissen, dass Ihre Datei korrekt ist, ist dies ziemlich gut, und Sie erhalten wahrscheinlich genau die gewünschten Informationen, und Sie können den Rest sofort verwerfen.

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
quelle
+1, weil ich nach einem nicht validierenden Parser suche, der mit seltsamen Quellzeichen funktioniert. Hoffentlich gibt mir das die gewünschten Ergebnisse.
Nathan C. Tresch
1
Das Beispiel wurde in '09 gemacht und so wurde es gemacht.
Tor Valamo
14

Ich könnte declxml vorschlagen .

Vollständige Offenlegung: Ich habe diese Bibliothek geschrieben, weil ich nach einer Möglichkeit gesucht habe, zwischen XML- und Python-Datenstrukturen zu konvertieren, ohne Dutzende von Zeilen imperativen Parsing- / Serialisierungscodes mit ElementTree schreiben zu müssen.

Mit declxml verwenden Sie Prozessoren , um die Struktur Ihres XML-Dokuments deklarativ zu definieren und um zwischen XML- und Python-Datenstrukturen zuzuordnen. Prozessoren werden sowohl für die Serialisierung und Analyse als auch für eine grundlegende Validierungsstufe verwendet.

Das Parsen in Python-Datenstrukturen ist einfach:

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)

Welches erzeugt die Ausgabe:

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

Sie können denselben Prozessor auch zum Serialisieren von Daten in XML verwenden

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

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

Welches erzeugt die folgende Ausgabe

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

Wenn Sie mit Objekten anstelle von Wörterbüchern arbeiten möchten, können Sie Prozessoren definieren, um Daten auch von und zu Objekten zu transformieren.

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)

Welches erzeugt die folgende Ausgabe

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

Um eine weitere Möglichkeit hinzuzufügen, können Sie entwirren verwenden , da es sich um eine einfache XML-zu-Python-Objektbibliothek handelt. Hier haben Sie ein Beispiel:

Installation:

pip install untangle

Verwendungszweck:

Ihre XML-Datei (etwas geändert):

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

Zugriff auf die Attribute mit untangle:

import untangle

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

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

Die Ausgabe wird sein:

bar_name
1

Weitere Informationen zum Entwirren finden Sie unter " Entwirren ".

Wenn Sie neugierig sind, finden Sie unter " Python und XML " eine Liste der Tools für die Arbeit mit XML und Python . Sie werden auch sehen, dass die häufigsten in früheren Antworten erwähnt wurden.

Jchanger
quelle
Was unterscheidet Entwirren von Minidom?
Aaron Mann
Ich kann Ihnen den Unterschied zwischen diesen beiden nicht sagen, da ich nicht mit Minidom gearbeitet habe.
Jchanger
10

Hier ein sehr einfacher aber effektiver Code mit 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")

Dies ist aus " Python XML Parse ".

Jan Kohila
quelle
7

XML:

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

Python-Code:

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

Ausgabe:

foo
1
2
Ahito
quelle
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')

Dadurch wird der Wert des foobarAttributs gedruckt .

Souvik Dey
quelle
6

xml.etree.ElementTree vs. lxml

Dies sind einige Vorteile der beiden am häufigsten verwendeten Bibliotheken, die ich kennen sollte, bevor ich mich zwischen ihnen entscheide.

xml.etree.ElementTree:

  1. Aus der Standardbibliothek : Es muss kein Modul installiert werden

lxml

  1. Einfach XML-Deklaration schreiben : Müssen Sie zum Beispiel hinzufügen standalone="no"?
  2. Hübsches Drucken : Sie können ein schönes eingerücktes XML ohne zusätzlichen Code haben.
  3. Objektivierungsfunktionalität : Sie können XML so verwenden, als ob Sie es mit einer normalen Python-Objekthierarchie zu tun hätten .node.
  4. sourceline Ermöglicht das einfache Abrufen der Zeile des von Ihnen verwendeten XML-Elements.
  5. Sie können auch eine integrierte XSD-Schemaüberprüfung verwenden.
GM
quelle
5

Ich finde die Python xml.dom und xml.dom.minidom ziemlich einfach. Denken Sie daran, dass DOM nicht für große Mengen an XML geeignet ist. Wenn Ihre Eingabe jedoch relativ klein ist, funktioniert dies einwandfrei.

EMP
quelle
2

Es ist nicht erforderlich, eine lib-spezifische API zu verwenden, wenn Sie diese verwendenpython-benedict . Initialisieren Sie einfach eine neue Instanz aus Ihrem XML und verwalten Sie sie einfach, da es sich um eine dictUnterklasse handelt.

Die Installation ist einfach: 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 unterstützt und normalisiert I / O - Operationen mit vielen Formaten: Base64, CSV, JSON, TOML, XML,YAML und query-string.

Es ist gut getestet und Open Source auf GitHub .

Fabio Caccamo
quelle
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
quelle
Bitte geben Sie auch einen Kontext an, in dem erläutert wird, wie Ihre Antwort das Problem löst. Nur-Code-Antworten werden nicht empfohlen.
Pedram Parsian
-1

Wenn die Quelle eine XML-Datei ist, sagen Sie wie in diesem Beispiel

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

Sie können den folgenden Code versuchen

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)

Ausgabe wäre

{'FIRST_TAG': 'SAMPLE'}
Siraj
quelle