Bagaimana cara mem-parsing XML dengan Python?

1003

Saya memiliki banyak baris dalam database yang berisi XML dan saya mencoba menulis skrip Python untuk menghitung instance dari atribut node tertentu.

Pohon saya terlihat seperti:

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

Bagaimana saya bisa mengakses atribut "1"dan "2"dalam XML menggunakan Python?

Randand
sumber

Jawaban:

781

Saya sarankan ElementTree. Ada implementasi lain yang kompatibel dari API yang sama, seperti lxml, dan cElementTreedi pustaka standar Python itu sendiri; tetapi, dalam konteks ini, apa yang terutama mereka tambahkan bahkan lebih cepat - kemudahan bagian pemrograman tergantung pada API, yang ElementTreemendefinisikan.

Pertama-tama buat instance Elemen rootdari XML, misalnya dengan fungsi XML , atau dengan mem-parsing file dengan sesuatu seperti:

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

Atau salah satu dari banyak cara lain yang ditunjukkan di ElementTree. Kemudian lakukan sesuatu seperti:

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

Dan serupa, biasanya cukup sederhana, pola kode.

Alex Martelli
sumber
41
Anda tampaknya mengabaikan xml.etree.cElementTree yang datang dengan Python dan dalam beberapa aspek lebih cepat daripada lxml ("lxml's iterparse () sedikit lebih lambat daripada yang ada di cET" - e-mail dari penulis lxml).
John Machin
7
ElementTree berfungsi dan disertakan dengan Python. Meskipun demikian, ada dukungan XPath terbatas dan Anda tidak dapat melintasi hingga induk elemen, yang dapat memperlambat pengembangan (terutama jika Anda tidak tahu ini). Lihat permintaan python xml, dapatkan induk untuk detailnya.
Samuel
11
lxmlmenambahkan lebih dari kecepatan. Ini memberikan akses mudah ke informasi seperti simpul orangtua, nomor baris dalam sumber XML, dll. Yang dapat sangat berguna dalam beberapa skenario.
Saheel Godhane
13
Tampaknya ElementTree memiliki beberapa masalah kerentanan, ini adalah kutipan dari dokumen: 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 Sepertinya sebagian besar xml parser, lihat halaman XML kerentanan .
gitaarik
427

minidom adalah yang tercepat dan cukup lurus ke depan.

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)

Keluaran:

4
item1
item1
item2
item3
item4
Ryan Christensen
sumber
9
Bagaimana Anda mendapatkan nilai "item1"? Misalnya: <item name = "item1"> Value1 </item>
swmcdonnell
88
Saya menemukan jawabannya, siapa tahu ada pertanyaan yang sama. It's s.childNodes [0] .nodeValue
swmcdonnell
1
Saya suka contoh Anda, saya ingin mengimplementasikannya tetapi di mana saya dapat menemukan fungsi minidom tersedia. Situs python minidom menyebalkan menurut saya.
Drewdin
1
Saya juga bingung mengapa menemukan itemlangsung dari tingkat atas dokumen? bukankah akan lebih bersih jika Anda menyediakan path ( data->items)? karena, bagaimana jika Anda juga memiliki data->secondSetOfItemsnode yang bernama itemdan Anda ingin daftar hanya satu dari dua set item?
amfibi
1
silakan lihat stackoverflow.com/questions/21124018/…
amphibient
240

Anda dapat menggunakan 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'
KAMU
sumber
Terima kasih atas info @ibz, Ya, Sebenarnya, Jika sumber tidak terbentuk dengan baik, akan sulit untuk menguraikan parser juga.
ANDA
45
tiga tahun kemudian dengan bs4 ini adalah solusi hebat, sangat fleksibel, terutama jika sumbernya tidak terbentuk dengan baik
cedbeu
8
@ YOU TERGANGGU BeautifulStoneSoup. Cukup gunakanBeautifulSoup(source_xml, features="xml")
andilabs
5
3 tahun kemudian, saya hanya mencoba memuat XML menggunakan ElementTree, sayangnya tidak dapat mengurai kecuali saya menyesuaikan sumber di tempat tetapi BeautifulSoupbekerja segera tanpa perubahan!
ViKiG
8
@andi Maksudmu "usang." "Depresiasi" berarti nilainya menurun, biasanya karena usia atau keausan dari penggunaan normal.
jpmc26
98

Ada banyak opsi di luar sana. cElementTree terlihat bagus jika kecepatan dan penggunaan memori menjadi masalah. Ini memiliki overhead yang sangat sedikit dibandingkan dengan hanya membaca di file menggunakan readlines.

Metrik yang relevan dapat ditemukan pada tabel di bawah ini, disalin dari situs 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   

Seperti yang ditunjukkan oleh @jfs , cElementTreedibundel dengan Python:

  • Python 2: from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(versi C dipercepat digunakan secara otomatis).
Cyrus
sumber
9
Apakah ada kelemahan menggunakan cElementTree? Tampaknya menjadi no-brainer.
mayhewsw
6
Tampaknya mereka tidak ingin menggunakan pustaka pada OS X karena saya telah menghabiskan lebih dari 15 menit untuk mencari tahu dari mana untuk mengunduhnya dan tidak ada tautan yang berfungsi. Kurangnya dokumentasi membuat proyek-proyek bagus tidak berkembang, berharap lebih banyak orang akan menyadarinya.
Stunner
8
@Stunner: ada di stdlib yaitu, Anda tidak perlu mengunduh apa pun. Pada Python 2: from xml.etree import cElementTree as ElementTree. Pada Python 3: from xml.etree import ElementTree(versi C yang dipercepat digunakan secara otomatis)
jfs
1
@ mayhewsw Lebih banyak upaya untuk mengetahui cara efisien digunakan ElementTreeuntuk tugas tertentu. Untuk dokumen yang sesuai dengan memori, jauh lebih mudah digunakan minidom, dan berfungsi baik untuk dokumen XML yang lebih kecil.
Acumenus
44

Saya sarankan xmltodict untuk kesederhanaan.

Itu mem-parsing XML Anda ke 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
sumber
3
Sepakat. Jika Anda tidak memerlukan XPath atau sesuatu yang rumit, ini jauh lebih mudah untuk digunakan (terutama di penerjemah); berguna untuk REST API yang menerbitkan XML dan bukan JSON
Dan Passaro
4
Ingatlah bahwa OrderedDict tidak mendukung kunci duplikat. Sebagian besar XML penuh dengan beberapa saudara kandung dari jenis yang sama (katakanlah, semua paragraf di bagian, atau semua jenis di bilah Anda). Jadi ini hanya akan berfungsi untuk kasus khusus yang sangat terbatas.
TextGeek
2
@TextGeek Dalam hal ini, result["foo"]["bar"]["type"]adalah daftar semua <type>elemen, jadi masih berfungsi (meskipun strukturnya mungkin sedikit tidak terduga).
luator
38

lxml.objectify sangat sederhana.

Mengambil teks sampel Anda:

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)

Keluaran:

{'1': 1, '2': 1}
Ryan Ginstrom
sumber
countmenyimpan jumlah setiap item dalam kamus dengan kunci default, jadi Anda tidak perlu memeriksa keanggotaan. Anda juga dapat mencoba melihat collections.Counter.
Ryan Ginstrom
20

Python memiliki antarmuka ke parser XML expat.

xml.parsers.expat

Ini adalah parser yang tidak valid, jadi XML yang buruk tidak akan ditangkap. Tetapi jika Anda tahu file Anda benar, maka ini cukup bagus, dan Anda mungkin akan mendapatkan info persis yang Anda inginkan dan Anda dapat membuang sisanya dengan cepat.

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
sumber
+1 karena saya mencari parser yang tidak valid yang akan bekerja dengan karakter sumber aneh. Semoga ini akan memberi saya hasil yang saya inginkan.
Nathan C. Tresch
1
Contoh dibuat di '09 dan ini adalah bagaimana hal itu dilakukan.
Tor Valamo
14

Saya mungkin menyarankan declxml .

Pengungkapan penuh: Saya menulis perpustakaan ini karena saya sedang mencari cara untuk mengkonversi antara struktur data XML dan Python tanpa perlu menulis lusinan baris kode penguraian / serialisasi imperatif dengan ElementTree.

Dengan declxml, Anda menggunakan prosesor untuk secara deklaratif mendefinisikan struktur dokumen XML Anda dan cara memetakan antara struktur data XML dan Python. Prosesor digunakan untuk serialisasi dan parsing serta tingkat validasi dasar.

Parsing ke dalam struktur data Python sangat mudah:

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)

Yang menghasilkan output:

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

Anda juga dapat menggunakan prosesor yang sama untuk membuat serialisasi data ke XML

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

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

Yang menghasilkan output sebagai berikut

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

Jika Anda ingin bekerja dengan objek alih-alih kamus, Anda dapat menentukan prosesor untuk mengubah data ke dan dari objek juga.

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)

Yang menghasilkan output sebagai berikut

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

Hanya untuk menambahkan kemungkinan lain, Anda dapat menggunakan untangle , karena ini adalah pustaka xml-to-python-object yang sederhana. Di sini Anda memiliki contoh:

Instalasi:

pip install untangle

Pemakaian:

File XML Anda (sedikit berubah):

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

Mengakses atribut dengan untangle:

import untangle

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

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

Outputnya adalah:

bar_name
1

Informasi lebih lanjut tentang untangle dapat ditemukan di " untangle ".

Juga, jika Anda penasaran, Anda dapat menemukan daftar alat untuk bekerja dengan XML dan Python di " Python dan XML ". Anda juga akan melihat bahwa yang paling umum disebutkan oleh jawaban sebelumnya.

Jchanger
sumber
Apa yang membuat kusut berbeda dari minidom?
Aaron Mann
Saya tidak bisa memberi tahu Anda perbedaan antara keduanya karena saya belum bekerja dengan minidom.
Jchanger
10

Di sini menggunakan kode yang sangat sederhana namun efektif 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")

Ini dari " python xml parse ".

Jan Kohila
sumber
7

XML:

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

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

Keluaran:

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

Ini akan mencetak nilai foobaratribut.

Souvik Dey
sumber
6

xml.etree.ElementTree vs lxml

Ini adalah beberapa kelebihan dari dua perpustakaan yang paling sering saya manfaatkan untuk diketahui sebelum memilih di antara mereka.

xml.etree.ElementTree:

  1. Dari perpustakaan standar : tidak perlu memasang modul apa pun

lxml

  1. Menulis deklarasi XML dengan mudah : misalnya apakah Anda perlu menambahkan standalone="no"?
  2. Pencetakan cantik : Anda dapat memiliki XML indentasi yang bagus tanpa kode tambahan.
  3. Fungsi Objectify : Ini memungkinkan Anda untuk menggunakan XML seolah-olah Anda berhadapan dengan hierarki objek Python normal .node.
  4. sourceline memungkinkan untuk dengan mudah mendapatkan garis elemen XML yang Anda gunakan.
  5. Anda dapat menggunakan juga pemeriksa skema XSD bawaan.
GM
sumber
5

Saya menemukan Python xml.dom dan xml.dom.minidom cukup mudah. Ingatlah bahwa DOM tidak baik untuk XML dalam jumlah besar, tetapi jika input Anda cukup kecil maka ini akan berfungsi dengan baik.

EMP
sumber
2

Tidak perlu menggunakan API khusus lib jika Anda menggunakannya python-benedict. Inisialisasi saja instance baru dari XML Anda dan kelola dengan mudah karena merupakan dictsubclass.

Instalasi mudah: 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'])

Mendukung dan menormalkan I / O operasi dengan banyak format: Base64, CSV, JSON, TOML, XML, YAMLdan query-string.

Ini diuji dengan baik dan open-source di GitHub .

Fabio Caccamo
sumber
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
sumber
Harap sertakan juga beberapa konteks yang menjelaskan bagaimana jawaban Anda memecahkan masalah. Jawaban khusus kode tidak dianjurkan.
Pedram Parsian
-1

Jika sumbernya adalah file xml, katakan seperti contoh ini

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

Anda dapat mencoba kode berikut

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)

Output akan menjadi

{'FIRST_TAG': 'SAMPLE'}
Siraj
sumber