Jak parsować XML w Pythonie?


1002

Mam wiele wierszy w bazie danych zawierającej XML i próbuję napisać skrypt w języku Python, aby policzyć wystąpienia określonego atrybutu węzła.

Moje drzewo wygląda następująco:

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

Jak mogę uzyskać dostęp do atrybutów "1"i "2"kodu XML za pomocą Pythona?


Odpowiedzi:


780

Sugeruję ElementTree. Istnieją inne kompatybilne implementacje tego samego API, jak lxmli cElementTreew samej bibliotece standardowej Pythona; ale w tym kontekście dodają przede wszystkim jeszcze większą szybkość - łatwość programowania zależy od API, które ElementTreedefiniuje.

Najpierw zbuduj instancję Element rootna podstawie XML, np. Za pomocą funkcji XML lub parsując plik za pomocą czegoś takiego:

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

Lub dowolny z wielu innych sposobów pokazanych na ElementTree. Następnie zrób coś takiego:

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

I podobne, zwykle dość proste, wzory kodu.


41
Wydaje się, że ignorujesz xml.etree.cElementTree, który jest dostarczany z Pythonem, aw niektórych aspektach jest szybszy niż lxml („iterparse (lxml) () jest nieco wolniejszy niż ten w cET” - e-mail od autora lxml).
John Machin

7
ElementTree działa i jest dołączone do Pythona. Obsługa XPath jest jednak ograniczona i nie można przejść do elementu nadrzędnego elementu, co może spowolnić rozwój (szczególnie jeśli nie wiesz o tym). Zobacz szczegóły w python xml get parent, aby uzyskać szczegółowe informacje.
Samuel

11
lxmldodaje więcej niż prędkości. Zapewnia łatwy dostęp do informacji, takich jak węzeł nadrzędny, numer wiersza w źródle XML itp., Które mogą być bardzo przydatne w kilku scenariuszach.
Saheel Godhane

13
Wygląda na to, że ElementTree ma pewne problemy z podatnością, to cytat z dokumentacji: 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 Wydaje się, że tak jest w przypadku większości parserów XML, patrz strona podatności XML .
gitaarik

427

minidom jest najszybszy i całkiem prosty.

XML:

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

Pyton:

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)

Wynik:

4
item1
item1
item2
item3
item4

9
Jak uzyskać wartość „item1”? Na przykład: <item name = "item1"> Value1 </item>
swmcdonnell

88
Zrozumiałem, na wypadek, gdyby ktoś miał to samo pytanie. Jest to s.childNodes [0] .nodeValue
swmcdonnell

1
Podoba mi się twój przykład, chcę go wdrożyć, ale gdzie mogę znaleźć dostępne funkcje minidom. Moim zdaniem strona internetowa python minidom jest do bani.
Drewdin,

1
Jestem również zdezorientowany, dlaczego znajduje się itembezpośrednio z najwyższego poziomu dokumentu? czy nie byłoby czystsze, gdybyś podał mu ścieżkę ( data->items)? bo co gdybyś też miał data->secondSetOfItemste same węzły itemi chciałbyś wymienić tylko jeden z dwóch zestawów item?
amfibia


240

Możesz użyć 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'

Dzięki za info @ibz, Tak, w rzeczywistości, jeśli źródło nie jest dobrze sformułowane, trudno będzie również przeanalizować parsery.
TY

45
trzy lata później z bs4 jest to świetne rozwiązanie, bardzo elastyczne, zwłaszcza jeśli źródło nie jest dobrze uformowane
cedbeu

8
@YOU JEST DEPRECIATED BeautifulStoneSoup. Wystarczy użyćBeautifulSoup(source_xml, features="xml")
andilabs

5
Kolejne 3 lata później właśnie próbowałem załadować XML przy użyciu ElementTree, niestety nie można go przeanalizować, chyba że dostosuję źródło w niektórych miejscach, ale BeautifulSoupdziałało od razu bez żadnych zmian!
ViKiG,

8
@ i masz na myśli „przestarzałe”. „Amortyzowane” oznacza, że ​​wartość spadła, zwykle z powodu wieku lub zużycia wynikającego z normalnego użytkowania.
jpmc26,

98

Istnieje wiele opcji. cElementTree wygląda doskonale, jeśli problemem jest szybkość i zużycie pamięci. Ma bardzo mały narzut w porównaniu do zwykłego odczytu pliku za pomocą readlines.

Odpowiednie wskaźniki można znaleźć w poniższej tabeli, skopiowanej ze strony internetowej 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   

Jak wskazał @jfs , cElementTreejest dostarczany w pakiecie z Pythonem:

  • Python 2: from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(przyspieszona wersja C jest używana automatycznie).

9
Czy są jakieś wady korzystania z cElementTree? Wydaje się to oczywiste.
mayhewsw

6
Najwyraźniej nie chcą korzystać z biblioteki w systemie OS X, ponieważ spędziłem ponad 15 minut, próbując dowiedzieć się, skąd ją pobrać i nie działa link. Brak dokumentacji uniemożliwia rozkwit dobrych projektów, chciałbym, aby więcej osób to zrozumiało.
Stunner,

8
@Stunner: jest w stdlib, tzn. Nie musisz niczego pobierać. Na Python 2: from xml.etree import cElementTree as ElementTree. W Pythonie 3: from xml.etree import ElementTree(przyspieszona wersja C jest używana automatycznie)
jfs

1
@mayhewsw Więcej wysiłku wymaga zastanowienie się, jak efektywnie wykorzystać ElementTreedane zadanie. W przypadku dokumentów, które mieszczą się w pamięci, jest o wiele łatwiejszy w użyciu minidomi działa dobrze w przypadku mniejszych dokumentów XML.
Acumenus,

44

Dla uproszczenia sugeruję xmltodict .

Analizuje twój XML do OrDERDict;

>>> 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
Zgoda. Jeśli nie potrzebujesz XPath ani niczego skomplikowanego, jest to o wiele prostsze w użyciu (szczególnie w tłumaczu); przydatne dla interfejsów API REST, które publikują XML zamiast JSON
Dan Passaro

4
Pamiętaj, że OrdersDict nie obsługuje duplikatów kluczy. Większość XML jest wypełniona wieloma rodzeństwem tego samego typu (powiedzmy, wszystkie akapity w sekcji lub wszystkie typy na pasku). Będzie to działać tylko w bardzo ograniczonych przypadkach specjalnych.
TextGeek,

2
@TextGeek W tym przypadku result["foo"]["bar"]["type"]znajduje się lista wszystkich <type>elementów, więc nadal działa (chociaż struktura może być nieco nieoczekiwana).
luator

38

Plik lxml.objectify jest naprawdę prosty.

Biorąc przykładowy tekst:

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)

Wynik:

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

countprzechowuje liczniki każdego elementu w słowniku z domyślnymi kluczami, więc nie musisz sprawdzać członkostwa. Możesz także spróbować spojrzeć collections.Counter.
Ryan Ginstrom

20

Python ma interfejs do analizatora XML expat.

xml.parsers.expat

To parser nie sprawdzający poprawności, więc zły kod XML nie zostanie przechwycony. Ale jeśli wiesz, że plik jest poprawny, to jest całkiem niezły i prawdopodobnie otrzymasz dokładne informacje, których potrzebujesz, a resztę możesz odrzucić w locie.

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, ponieważ szukam parsera nie sprawdzającego poprawności, który będzie działał z dziwnymi znakami źródłowymi. Mam nadzieję, że da mi to oczekiwane rezultaty.
Nathan C. Tresch,

1
Przykład powstał w '09 i tak właśnie się to stało.
Tor Valamo,

13

Mogę zaproponować declxml .

Pełne ujawnienie: Napisałem tę bibliotekę, ponieważ szukałem sposobu na konwersję między strukturami danych XML i Python bez konieczności pisania dziesiątek wierszy bezwzględnego parsowania / serializacji kodu za pomocą ElementTree.

Za pomocą declxml używasz procesorów do deklaratywnego definiowania struktury dokumentu XML oraz sposobu mapowania między strukturami danych XML i Python. Procesory są używane zarówno do serializacji i parsowania, jak i do podstawowego poziomu sprawdzania poprawności.

Analizowanie struktur danych w języku Python jest proste:

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)

Co daje wynik:

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

Możesz także użyć tego samego procesora do serializacji danych do formatu XML

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

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

Który daje następujący wynik

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

Jeśli chcesz pracować z obiektami zamiast ze słownikami, możesz zdefiniować procesory do przekształcania danych do i z obiektów.

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)

Który daje następujący wynik

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

13

Aby dodać kolejną możliwość, możesz użyć rozplątania , ponieważ jest to prosta biblioteka obiektów xml-to-python. Oto przykład:

Instalacja:

pip install untangle

Stosowanie:

Twój plik XML (nieco zmieniony):

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

Dostęp do atrybutów za pomocą untangle:

import untangle

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

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

Dane wyjściowe będą:

bar_name
1

Więcej informacji o rozplątywaniu można znaleźć w „ rozplątywaniu ”.

Ponadto, jeśli jesteś ciekawy, możesz znaleźć listę narzędzi do pracy z XML i Python w „ Python and XML ”. Przekonasz się również, że najczęściej spotykane były poprzednie odpowiedzi.


Co odróżnia unangle od minidom?
Aaron Mann

Nie potrafię powiedzieć różnicy między tymi dwoma, ponieważ nie pracowałem z minidomem.
jchanger

10

Tutaj bardzo prosty, ale skuteczny kod używający 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")

Pochodzi z „ parsowania xml Pythona ”.


7

XML:

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

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

Wynik:

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

Spowoduje to wydrukowanie wartości foobaratrybutu.


6

xml.etree.ElementTree vs. lxml

Oto niektóre zalety dwóch najczęściej używanych bibliotek, które chciałbym poznać, zanim dokonam wyboru między nimi.

xml.etree.ElementTree:

  1. Ze standardowej biblioteki : nie ma potrzeby instalowania żadnego modułu

lxml

  1. Łatwo napisz deklarację XML : na przykład musisz dodaćstandalone="no" ?
  2. Ładne drukowanie : możesz mieć ładne wcięcie XML bez dodatkowego kodu.
  3. Funkcjonalność Objectify : Pozwala ci używać XML tak, jakbyś miał do czynienia z normalną hierarchią obiektów Pythona .node.
  4. sourceline pozwala łatwo uzyskać linię używanego elementu XML.
  5. możesz również użyć wbudowanego sprawdzania schematu XSD.

5

Uważam, że Python xml.dom i xml.dom.minidom są dość łatwe. Pamiętaj, że DOM nie nadaje się do dużych ilości XML, ale jeśli twoje dane wejściowe są dość małe, to będzie działać dobrze.


2

Jeśli używasz , nie musisz używać interfejsu API specyficznego dla libpython-benedict . Wystarczy zainicjować nowe wystąpienie z pliku XML i łatwo nim zarządzać, ponieważ jest to dictpodklasa.

Instalacja jest łatwa: 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'])

Wspiera i normalizuje I / O operacje z wieloma formatami: Base64, CSV, JSON, TOML, XML, YAMLi query-string.

Jest dobrze przetestowany i otwarty na 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'}'''

Dołącz także kontekst wyjaśniający, w jaki sposób Twoja odpowiedź rozwiązuje problem. Odpowiedzi zawierające tylko kod nie są zachęcane.
Pedram Parsian

-1

Jeśli źródłem jest plik xml, powiedz tak jak w tym przykładzie

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

możesz wypróbować następujący kod

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)

Wyjście byłoby

{'FIRST_TAG': 'SAMPLE'}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.