Comment analyser XML en Python?

1003

J'ai plusieurs lignes dans une base de données qui contient du XML et j'essaie d'écrire un script Python pour compter les instances d'un attribut de nœud particulier.

Mon arbre ressemble à:

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

Comment puis-je accéder aux attributs "1"et "2"au XML en utilisant Python?

randombits
la source

Réponses:

781

Je suggère ElementTree. Il existe d'autres implémentations compatibles de la même API, telles que lxml, et cElementTreedans la bibliothèque standard Python elle-même; mais, dans ce contexte, ce qu'ils ajoutent principalement est encore plus de vitesse - la facilité de programmation dépend de l'API, qui ElementTreedéfinit.

Construisez d'abord une instance Element à rootpartir du XML, par exemple avec la fonction XML , ou en analysant un fichier avec quelque chose comme:

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

Ou l'une des nombreuses autres façons présentées sur ElementTree. Ensuite, faites quelque chose comme:

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

Et des modèles de code similaires, généralement assez simples.

Alex Martelli
la source
41
Vous semblez ignorer xml.etree.cElementTree qui vient avec Python et à certains égards est plus rapide tham lxml ("lxml's iterparse () est légèrement plus lent que celui de cET" - e-mail de l'auteur lxml).
John Machin
7
ElementTree fonctionne et est inclus avec Python. Cependant, le support XPath est limité et vous ne pouvez pas traverser jusqu'au parent d'un élément, ce qui peut ralentir le développement (surtout si vous ne le savez pas). Voir la requête xml python get parent pour plus de détails.
Samuel
11
lxmlajoute plus que la vitesse. Il fournit un accès facile aux informations telles que le nœud parent, le numéro de ligne dans la source XML, etc. qui peuvent être très utiles dans plusieurs scénarios.
Saheel Godhane
13
Il semble qu'ElementTree ait des problèmes de vulnérabilité, voici une citation des documents: 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 Cela semble être le cas avec la plupart des analyseurs XML, voir la page des vulnérabilités XML .
gitaarik
427

minidom est le plus rapide et le plus simple.

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)

Production:

4
item1
item1
item2
item3
item4
Ryan Christensen
la source
9
Comment obtenez-vous la valeur de "item1"? Par exemple: <item name = "item1"> Value1 </item>
swmcdonnell
88
Je l'ai compris, au cas où quelqu'un aurait la même question. C'est s.childNodes [0] .nodeValue
swmcdonnell
1
J'aime votre exemple, je veux l'implémenter mais où puis-je trouver les fonctions minidom disponibles. Le site Web de minidom de python est nul à mon avis.
Drewdin
1
Je suis également confus pourquoi il trouve itemdirectement à partir du niveau supérieur du document? ne serait-il pas plus propre si vous lui fournissiez le chemin ( data->items)? parce que, si vous aviez data->secondSetOfItemségalement des nœuds nommés itemet que vous vouliez répertorier uniquement l'un des deux ensembles de item?
amphibient
1
veuillez consulter stackoverflow.com/questions/21124018/…
amphibient
240

Vous pouvez utiliser 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'
VOUS
la source
Merci pour info @ibz, oui, en fait, si la source n'est pas bien formée, il sera également difficile d'analyser les analyseurs.
VOUS
45
trois ans plus tard avec bs4 c'est une excellente solution, très flexible, surtout si la source n'est pas bien formée
cedbeu
8
@YOU BeautifulStoneSoupest DÉPRÉCIÉ . Il suffit d'utiliserBeautifulSoup(source_xml, features="xml")
andilabs
5
Encore 3 ans plus tard, j'ai juste essayé de charger XML en utilisant ElementTree, malheureusement il n'est pas en mesure d'analyser à moins que j'ajuste la source à certains endroits mais j'ai BeautifulSouptravaillé tout de suite sans aucun changement!
ViKiG
8
@andi Vous voulez dire "obsolète". «Déprécié» signifie qu'il a diminué de valeur, généralement en raison de l'âge ou de l'usure due à une utilisation normale.
jpmc26
98

Il existe de nombreuses options. cElementTree semble excellent si la vitesse et l'utilisation de la mémoire sont un problème. Il a très peu de frais généraux par rapport à la simple lecture du fichier en utilisant readlines.

Les mesures pertinentes peuvent être trouvées dans le tableau ci-dessous, copié à partir du site 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   

Comme souligné par @jfs , cElementTreeest livré avec Python:

  • Python 2: from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(la version C accélérée est utilisée automatiquement).
Cyrus
la source
9
Y a-t-il des inconvénients à utiliser cElementTree? Cela semble être une évidence.
mayhewsw
6
Apparemment, ils ne veulent pas utiliser la bibliothèque sur OS X, car j'ai passé plus de 15 minutes à essayer de savoir d'où le télécharger et aucun lien ne fonctionne. Le manque de documentation empêche les bons projets de prospérer, souhaite que plus de gens s'en rendent compte.
Stunner
8
@Stunner: c'est dans stdlib c'est-à-dire que vous n'avez pas besoin de télécharger quoi que ce soit. Sur Python 2: from xml.etree import cElementTree as ElementTree. Sur Python 3: from xml.etree import ElementTree(la version C accélérée est utilisée automatiquement)
jfs
1
@mayhewsw C'est plus d'efforts pour comprendre comment l'utiliser efficacement ElementTreepour une tâche particulière. Pour les documents qui tiennent en mémoire, c'est beaucoup plus facile à utiliser minidomet cela fonctionne bien pour les petits documents XML.
Acumenus
44

Je suggère xmltodict pour plus de simplicité.

Il analyse votre XML en 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')])])])
myildirim
la source
3
D'accord. Si vous n'avez pas besoin de XPath ou de quelque chose de compliqué, c'est beaucoup plus simple à utiliser (surtout dans l'interpréteur); pratique pour les API REST qui publient du XML au lieu de JSON
Dan Passaro
4
N'oubliez pas que OrderedDict ne prend pas en charge les clés en double. La plupart des fichiers XML regorgent de frères et sœurs multiples du même type (par exemple, tous les paragraphes d'une section ou tous les types de votre barre). Cela ne fonctionnera donc que pour des cas spéciaux très limités.
TextGeek
2
@TextGeek Dans ce cas, result["foo"]["bar"]["type"]est une liste de tous les <type>éléments, donc cela fonctionne toujours (même si la structure est peut-être un peu inattendue).
luator
38

lxml.objectify est vraiment simple.

Prendre votre exemple de texte:

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)

Production:

{'1': 1, '2': 1}
Ryan Ginstrom
la source
countstocke le nombre de chaque élément dans un dictionnaire avec des clés par défaut, vous n'avez donc pas à vérifier l'appartenance. Vous pouvez également essayer de regarder collections.Counter.
Ryan Ginstrom
20

Python possède une interface avec l'analyseur XML expat.

xml.parsers.expat

Il s'agit d'un analyseur non validant, donc le mauvais XML ne sera pas détecté. Mais si vous savez que votre fichier est correct, c'est très bien, et vous obtiendrez probablement les informations exactes que vous souhaitez et vous pouvez supprimer le reste à la volée.

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
la source
+1 parce que je recherche un analyseur non validant qui fonctionnera avec des personnages sources étranges. J'espère que cela me donnera les résultats que je souhaite.
Nathan C. Tresch
1
L'exemple a été réalisé en '09 et c'est ainsi que cela a été fait.
Tor Valamo
14

Je pourrais suggérer declxml .

Divulgation complète: J'ai écrit cette bibliothèque parce que je cherchais un moyen de convertir entre les structures de données XML et Python sans avoir besoin d'écrire des dizaines de lignes de code d'analyse / de sérialisation impératif avec ElementTree.

Avec declxml, vous utilisez des processeurs pour définir de manière déclarative la structure de votre document XML et comment mapper entre les structures de données XML et Python. Les processeurs sont utilisés à la fois pour la sérialisation et l'analyse ainsi que pour un niveau de validation de base.

L'analyse dans les structures de données Python est simple:

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)

Ce qui produit la sortie:

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

Vous pouvez également utiliser le même processeur pour sérialiser les données en XML

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

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

Qui produit la sortie suivante

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

Si vous souhaitez travailler avec des objets au lieu de dictionnaires, vous pouvez également définir des processeurs pour transformer les données vers et depuis les objets.

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)

Qui produit la sortie suivante

{'bar': Bar(foobars=[1, 2])}
gatkin
la source
13

Juste pour ajouter une autre possibilité, vous pouvez utiliser démêler , car il s'agit d'une simple bibliothèque d'objets xml en python. Voici un exemple:

Installation:

pip install untangle

Usage:

Votre fichier XML (un peu modifié):

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

Accéder aux attributs avec untangle:

import untangle

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

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

La sortie sera:

bar_name
1

Pour plus d'informations sur le démêlage, voir " démêler ".

De plus, si vous êtes curieux, vous pouvez trouver une liste d'outils pour travailler avec XML et Python dans " Python et XML ". Vous verrez également que les plus courantes ont été mentionnées dans les réponses précédentes.

jchanger
la source
Qu'est-ce qui différencie le démêlage du minidom?
Aaron Mann
Je ne peux pas vous dire la différence entre ces deux-là car je n'ai pas travaillé avec minidom.
jchanger
10

Voici un code très simple mais efficace à utiliser 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")

Cela vient de " python xml parse ".

Jan Kohila
la source
7

XML:

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

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

Production:

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

Cela affichera la valeur de l' foobarattribut.

Souvik Dey
la source
6

xml.etree.ElementTree contre lxml

Ce sont des avantages des deux bibliothèques les plus utilisées que j'aurais intérêt à connaître avant de choisir entre elles.

xml.etree.ElementTree:

  1. Depuis la bibliothèque standard : pas besoin d'installer de module

lxml

  1. Écrivez facilement la déclaration XML : par exemple, devez-vous ajouter standalone="no"?
  2. Jolie impression : vous pouvez avoir un joli XML indenté sans code supplémentaire.
  3. Fonctionnalité Objectify : elle vous permet d'utiliser XML comme si vous aviez affaire à une hiérarchie d'objets Python normale .node.
  4. sourceline permet d'obtenir facilement la ligne de l'élément XML que vous utilisez.
  5. vous pouvez également utiliser un vérificateur de schéma XSD intégré.
GM
la source
5

Je trouve le Python xml.dom et xml.dom.minidom assez facile. Gardez à l'esprit que DOM n'est pas bon pour de grandes quantités de XML, mais si votre entrée est assez petite, cela fonctionnera bien.

EMP
la source
2

Il n'est pas nécessaire d'utiliser une API spécifique à la bibliothèque si vous utilisez python-benedict. Il suffit d'initialiser une nouvelle instance à partir de votre XML et de la gérer facilement car il s'agit d'une dictsous - classe.

L'installation est simple: 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'])

Il soutient et normalise les opérations d' E / S avec de nombreux formats: Base64, CSV, JSON, TOML, XML, YAMLet query-string.

Il est bien testé et open-source sur GitHub .

Fabio Caccamo
la source
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
la source
Veuillez également inclure un contexte expliquant comment votre réponse résout le problème. Les réponses uniquement codées ne sont pas encouragées.
Pedram Parsian
-1

Si la source est un fichier xml, dites comme cet exemple

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

vous pouvez essayer le code suivant

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 sortie serait

{'FIRST_TAG': 'SAMPLE'}
Siraj
la source