Come posso analizzare XML in Python?


1003

Ho molte righe in un database che contiene XML e sto provando a scrivere uno script Python per contare le istanze di un attributo nodo particolare.

Il mio albero sembra:

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

Come posso accedere agli attributi "1"e "2"nell'XML usando Python?


Risposte:


781

Io suggerisco ElementTree. Esistono altre implementazioni compatibili della stessa API, come lxml, e cElementTreenella stessa libreria standard di Python; ma, in questo contesto, ciò che aggiungono principalmente è ancora più velocità: la facilità di programmazione della parte dipende dall'API, che ElementTreedefinisce.

Innanzitutto crea un'istanza Element rootdall'XML, ad esempio con la funzione XML , o analizzando un file con qualcosa del tipo:

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

O uno dei tanti altri modi mostrati in ElementTree. Quindi fai qualcosa del tipo:

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

E modelli di codice simili, di solito piuttosto semplici.


41
Sembra che ignori xml.etree.cElementTree che viene fornito con Python e in alcuni aspetti è più veloce tham lxml ("lxml's iterparse () è leggermente più lento di quello in cET" - e-mail dall'autore lxml).
John Machin,

7
ElementTree funziona ed è incluso in Python. Tuttavia, il supporto XPath è limitato e non è possibile passare al genitore di un elemento, il che può rallentare lo sviluppo (soprattutto se non lo si conosce). Per ulteriori dettagli, consultare la query XML di Python, ottenere il genitore .
Samuel,

11
lxmlaggiunge più della velocità. Fornisce un facile accesso a informazioni come nodo genitore, numero di riga nell'origine XML, ecc. Che possono essere molto utili in diversi scenari.
Saheel Godhane,

13
Sembra che ElementTree abbia alcuni problemi di vulnerabilità, questa è una citazione dai documenti: 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 Questo sembra essere il caso della maggior parte dei parser xml, vedere la pagina delle vulnerabilità XML .
gitaarik,

427

minidom è il più veloce e piuttosto semplice.

XML:

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

Pitone:

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)

Produzione:

4
item1
item1
item2
item3
item4

9
Come si ottiene il valore di "item1"? Ad esempio: <item name = "item1"> Value1 </item>
swmcdonnell

88
L'ho capito, nel caso qualcuno abbia la stessa domanda. È s.childNodes [0] .nodeValue
swmcdonnell,

1
Mi piace il tuo esempio, voglio implementarlo, ma dove posso trovare le funzioni minidom disponibili. Il sito Web di minidom python fa schifo secondo me.
Drewdin,

1
Sono anche confuso perché trova itemdirettamente dal livello superiore del documento? non sarebbe più pulito se gli fornissi il percorso ( data->items)? perché, e se avessi avuto data->secondSetOfItemsanche dei nodi con nome iteme volessi elencare solo una delle due serie di item?
anfibio,


240

Puoi usare 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'

Grazie per info @ibz, Sì, in realtà, se la fonte non è ben formata, sarà difficile analizzare anche i parser.
TU,

45
tre anni dopo con bs4 questa è un'ottima soluzione, molto flessibile, specialmente se la fonte non è ben formata
cedbeu

8
@YOU BeautifulStoneSoupè DEPRECIATED . Basta usareBeautifulSoup(source_xml, features="xml")
andilabs

5
Altri 3 anni dopo, ho appena provato a caricare XML utilizzando ElementTree, sfortunatamente non è in grado di analizzare a meno che non aggiorni l'origine in alcuni punti, ma ha BeautifulSoupfunzionato immediatamente senza modifiche!
ViKiG,

8
@andi Intendi "deprecato". "Ammortizzato" significa che è diminuito di valore, di solito a causa dell'età o dell'usura dovuta al normale utilizzo.
jpmc26,

98

Ci sono molte opzioni là fuori. cElementTree sembra eccellente se la velocità e l'utilizzo della memoria sono un problema. Ha un sovraccarico minimo rispetto alla semplice lettura del file mediante readlines.

Le metriche pertinenti sono disponibili nella tabella seguente, copiata dal sito 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   

Come sottolineato da @jfs , cElementTreeviene fornito in bundle con Python:

  • Python 2: from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(la versione C accelerata viene utilizzata automaticamente).

9
Ci sono degli svantaggi nell'utilizzo di cElementTree? Sembra essere un gioco da ragazzi.
Mayhewsw,

6
Apparentemente non vogliono usare la libreria su OS X poiché ho trascorso più di 15 minuti a cercare di capire da dove scaricarlo e nessun collegamento funziona. La mancanza di documentazione impedisce ai buoni progetti di prosperare, desiderando che più persone se ne rendano conto.
Storditore,

8
@Stunner: è in stdlib, cioè non è necessario scaricare nulla. Su Python 2: from xml.etree import cElementTree as ElementTree. Su Python 3: from xml.etree import ElementTree(la versione C accelerata viene utilizzata automaticamente)
jfs il

1
@mayhewsw È più difficile capire come utilizzare in modo efficiente ElementTreeper un determinato compito. Per i documenti che si adattano alla memoria, è molto più facile da usare minidome funziona bene con documenti XML più piccoli.
Acumenus,

44

Suggerisco xmltodict per semplicità.

Analizza il tuo XML in 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')])])])

3
Concordato. Se non hai bisogno di XPath o di qualcosa di complicato, questo è molto più semplice da usare (specialmente nell'interprete); utile per le API REST che pubblicano XML anziché JSON
Dan Passaro il

4
Ricorda che OrderedDict non supporta chiavi duplicate. La maggior parte di XML è piena zeppa di più fratelli dello stesso tipo (diciamo, tutti i paragrafi in una sezione o tutti i tipi nella barra). Quindi questo funzionerà solo per casi speciali molto limitati.
TextGeek,

2
@TextGeek In questo caso, result["foo"]["bar"]["type"]è un elenco di tutti gli <type>elementi, quindi funziona ancora (anche se la struttura è forse un po 'inaspettata).
Luator,

38

lxml.objectify è davvero semplice.

Prendendo il testo di esempio:

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)

Produzione:

{'1': 1, '2': 1}

countmemorizza i conteggi di ciascun elemento in un dizionario con le chiavi predefinite, quindi non è necessario verificare l'appartenenza. Puoi anche provare a guardare collections.Counter.
Ryan Ginstrom,

20

Python ha un'interfaccia per il parser XML expat.

xml.parsers.expat

È un parser non validante, quindi XML non verrà catturato. Ma se sai che il tuo file è corretto, allora è abbastanza buono e probabilmente otterrai le informazioni esatte che desideri e puoi scartare il resto al volo.

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

+1 perché sto cercando un parser non validante che funzionerà con caratteri di origine strani. Spero che questo mi dia i risultati che desidero.
Nathan C. Tresch

1
L'esempio è stato fatto nel '09 ed è così che è stato fatto.
Tor Valamo

14

Potrei suggerire declxml .

Divulgazione completa: ho scritto questa libreria perché stavo cercando un modo per convertire tra le strutture di dati XML e Python senza dover scrivere dozzine di righe di codice di analisi / serializzazione imperativo con ElementTree.

Con declxml, usi i processori per definire in modo dichiarativo la struttura del tuo documento XML e come mappare tra le strutture di dati XML e Python. I processori vengono utilizzati sia per la serializzazione e l'analisi sia per un livello base di convalida.

L'analisi delle strutture di dati Python è semplice:

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)

Che produce l'output:

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

È inoltre possibile utilizzare lo stesso processore per serializzare i dati in XML

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

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

Che produce il seguente output

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

Se si desidera lavorare con oggetti anziché dizionari, è possibile definire processori per trasformare anche dati da e verso oggetti.

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)

Che produce il seguente output

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

13

Solo per aggiungere un'altra possibilità, puoi usare districare , in quanto è una semplice libreria da xml a python-object. Ecco un esempio:

Installazione:

pip install untangle

Uso:

Il tuo file XML (leggermente modificato):

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

Accedere agli attributi con untangle:

import untangle

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

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

L'output sarà:

bar_name
1

Ulteriori informazioni su districare sono disponibili in " districare ".

Inoltre, se sei curioso, puoi trovare un elenco di strumenti per lavorare con XML e Python in " Python e XML ". Vedrai anche che quelli più comuni sono stati menzionati dalle risposte precedenti.


Ciò che rende districare diverso dal minidom?
Aaron Mann,

Non posso dirti la differenza tra quei due poiché non ho lavorato con Minidom.
jchanger

10

Ecco un codice molto semplice ma efficace 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")

Questo è da " python xml parse ".


7

XML:

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

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

Produzione:

foo
1
2

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

Questo stamperà il valore foobardell'attributo.


6

xml.etree.ElementTree vs. lxml

Questi sono alcuni dei vantaggi delle due biblioteche più utilizzate che avrei bisogno di conoscere prima di scegliere tra di loro.

xml.etree.ElementTree:

  1. Dalla libreria standard : non è necessario installare alcun modulo

lxml

  1. Scrivi facilmente una dichiarazione XML : ad esempio devi aggiungere standalone="no"?
  2. Bella stampa : puoi avere un bel rientro XML senza codice aggiuntivo.
  3. Ottimizza funzionalità: ti consente di utilizzare XML come se avessi a che fare con una normale gerarchia di oggetti Python .node.
  4. sourceline consente di ottenere facilmente la linea dell'elemento XML che si sta utilizzando.
  5. puoi anche usare un correttore di schemi XSD incorporato.

5

Trovo il Python xml.dom e xml.dom.minidom abbastanza facile. Tieni presente che DOM non è buono per grandi quantità di XML, ma se il tuo input è abbastanza piccolo, allora funzionerà bene.


2

Non è necessario utilizzare un'API specifica per lib se si utilizza python-benedict. Basta inizializzare una nuova istanza dal proprio XML e gestirla facilmente poiché si tratta di una dictsottoclasse.

L'installazione è semplice: 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'])

Sostiene e normalizza operazioni di I / O con molti formati: Base64, CSV, JSON, TOML, XML, YAMLe query-string.

È ben testato e open-source su GitHub .


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'}'''

Ti preghiamo di includere anche un contesto che spieghi come la tua risposta risolve il problema. Le risposte di solo codice non sono incoraggiate.
Pedram Parsian,

-1

Se l'origine è un file XML, dire come in questo esempio

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

puoi provare il seguente codice

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)

L'output sarebbe

{'FIRST_TAG': 'SAMPLE'}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.