Làm cách nào để phân tích cú pháp XML bằng Python?


1003

Tôi có nhiều hàng trong cơ sở dữ liệu chứa XML và tôi đang cố gắng viết một tập lệnh Python để đếm các thể hiện của một thuộc tính nút cụ thể.

Cây của tôi trông như:

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

Làm cách nào tôi có thể truy cập các thuộc tính "1""2"trong XML bằng Python?


Câu trả lời:


781

Tôi đề nghị ElementTree. Có các triển khai tương thích khác của cùng một API, chẳng hạn như lxmlcElementTreetrong chính thư viện chuẩn Python; nhưng, trong bối cảnh này, những gì họ chủ yếu thêm thậm chí còn nhanh hơn - phần dễ lập trình phụ thuộc vào API, ElementTreeđịnh nghĩa này.

Trước tiên, xây dựng một cá thể Phần tử roottừ XML, ví dụ như với hàm XML hoặc bằng cách phân tích tệp bằng một cái gì đó như:

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

Hoặc bất kỳ cách nào trong số nhiều cách khác được hiển thị tại ElementTree. Sau đó làm một cái gì đó như:

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

Và tương tự, thường khá đơn giản, các mẫu mã.


41
Bạn dường như bỏ qua xml.etree.cEuityTree đi kèm với Python và trong một số khía cạnh nhanh hơn tham lxml ("lxml's iterparse () chậm hơn một chút so với trong cET" - e-mail từ tác giả lxml).
John Machin

7
ElementTree hoạt động và được bao gồm với Python. Mặc dù có hỗ trợ XPath hạn chế và bạn không thể truy cập vào phần tử mẹ của một phần tử, điều này có thể làm chậm sự phát triển (đặc biệt là nếu bạn không biết điều này). Xem truy vấn python xml lấy cha để biết chi tiết.
Samuel

11
lxmlthêm nhiều hơn tốc độ. Nó cung cấp quyền truy cập dễ dàng vào thông tin như nút cha, số dòng trong nguồn XML, v.v. có thể rất hữu ích trong một số tình huống.
Saheel Godhane

13
Có vẻ như ElementTree có một số vấn đề về lỗ hổng, đây là trích dẫn từ các tài liệu: 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 Đây có vẻ là trường hợp của hầu hết các trình phân tích cú pháp xml, xem trang lỗ hổng XML .
gitaarik

427

minidom là nhanh nhất và khá thẳng về phía trước.

XML:

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

Con trăn

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)

Đầu ra:

4
item1
item1
item2
item3
item4

9
Làm thế nào để bạn có được giá trị của "item1"? Ví dụ: <item name = "item1"> Value1 </ item>
swmcdonnell

88
Tôi đã tìm ra nó, trong trường hợp bất cứ ai có cùng một câu hỏi. Đó là s.childNodes [0] .nodeValue
swmcdonnell

1
Tôi thích ví dụ của bạn, tôi muốn thực hiện nó nhưng tôi có thể tìm thấy các hàm minidom ở đâu. Các trang web minidom python hút theo ý kiến ​​của tôi.
Drewdin

1
Tôi cũng bối rối tại sao nó tìm thấy itemtrực tiếp từ cấp cao nhất của tài liệu? nó sẽ sạch hơn nếu bạn cung cấp cho nó đường dẫn ( data->items)? bởi vì, điều gì sẽ xảy ra nếu bạn cũng có data->secondSetOfItemscác nút được đặt tên itemvà bạn chỉ muốn liệt kê một trong hai bộ item?
lưỡng cư


240

Bạn có thể sử dụng 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'

Cảm ơn thông tin @ibz, Yeah, Trên thực tế, Nếu nguồn không được định dạng tốt, sẽ rất khó để phân tích cú pháp cho các trình phân tích cú pháp.
BẠN

45
ba năm sau với bs4, đây là một giải pháp tuyệt vời, rất linh hoạt, đặc biệt nếu nguồn không được hình thành tốt
cedbeu

8
@YOU BeautifulStoneSouplà khấu hao. Chỉ cần sử dụngBeautifulSoup(source_xml, features="xml")
andilabs 24/07/2016

5
3 năm sau, tôi mới thử tải XML bằng cách sử dụng ElementTree, thật không may là nó không thể phân tích cú pháp trừ khi tôi điều chỉnh nguồn tại các địa điểm nhưng BeautifulSouphoạt động ngay lập tức mà không có bất kỳ thay đổi nào!
ViKiG

8
@andi Ý bạn là "không dùng nữa." "Khấu hao" có nghĩa là nó giảm giá trị, thường là do tuổi tác hoặc hao mòn do sử dụng bình thường.
jpmc26

98

Có rất nhiều lựa chọn ngoài kia. cEuityTree trông tuyệt vời nếu tốc độ và việc sử dụng bộ nhớ là một vấn đề. Nó có rất ít chi phí so với việc chỉ đọc trong tệp bằng cách sử dụng readlines.

Các số liệu liên quan có thể được tìm thấy trong bảng dưới đây, được sao chép từ trang web cEuityTree :

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   

Như được chỉ ra bởi @jfs , cElementTreeđi kèm với Python:

  • Con trăn 2 : from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(phiên bản C tăng tốc được sử dụng tự động).

9
Có bất kỳ nhược điểm nào khi sử dụng cEuityTree không? Nó có vẻ là một người không có trí tuệ.
mayhewsw

6
Rõ ràng họ không muốn sử dụng thư viện trên OS X vì tôi đã dành hơn 15 phút để cố gắng tìm ra nơi tải xuống và không có liên kết nào hoạt động. Thiếu tài liệu ngăn cản các dự án tốt phát triển mạnh, mong muốn nhiều người sẽ nhận ra điều đó.
Stunner

8
@Stunner: đó là trong stdlib, tức là bạn không cần tải xuống bất cứ thứ gì. Trên Python 2 : from xml.etree import cElementTree as ElementTree. Trên Python 3: from xml.etree import ElementTree(phiên bản C tăng tốc được sử dụng tự động)
jfs

1
@mayhewsw Nỗ lực nhiều hơn để tìm ra cách sử dụng hiệu quả ElementTreecho một nhiệm vụ cụ thể. Đối với các tài liệu phù hợp với bộ nhớ, nó dễ sử dụng hơn rất nhiều minidomvà nó hoạt động tốt đối với các tài liệu XML nhỏ hơn.
Acumenus

44

Tôi đề nghị xmltodict cho đơn giản.

Nó phân tích cú pháp XML của bạn thành 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
Đã đồng ý. Nếu bạn không cần XPath hoặc bất cứ điều gì phức tạp, việc này sẽ đơn giản hơn nhiều để sử dụng (đặc biệt là trong trình thông dịch); tiện dụng cho các API REST xuất bản XML thay vì JSON
Dan Passaro

4
Hãy nhớ rằng OrderedDict không hỗ trợ các khóa trùng lặp. Hầu hết XML chứa đầy đủ nhiều anh chị em cùng loại (giả sử, tất cả các đoạn trong một phần hoặc tất cả các loại trong thanh của bạn). Vì vậy, điều này sẽ chỉ làm việc cho các trường hợp đặc biệt rất hạn chế.
TextGeek 17/07/18

2
@TextGeek Trong trường hợp này, result["foo"]["bar"]["type"]là danh sách tất cả các <type>yếu tố, vì vậy nó vẫn hoạt động (mặc dù cấu trúc có thể hơi bất ngờ).
luator

38

lxml.objectify thực sự đơn giản.

Lấy văn bản mẫu của bạn:

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)

Đầu ra:

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

countlưu trữ số lượng của từng mục trong từ điển với các khóa mặc định, vì vậy bạn không phải kiểm tra tư cách thành viên. Bạn cũng có thể thử nhìn vào collections.Counter.
Ryan Ginstrom

20

Python có một giao diện cho trình phân tích cú pháp XML expat.

xml.parsers.expat

Đó là một trình phân tích cú pháp không xác thực, do đó, XML xấu sẽ không bị bắt. Nhưng nếu bạn biết tệp của mình là chính xác, thì điều này khá tốt và có thể bạn sẽ nhận được thông tin chính xác mà bạn muốn và bạn có thể loại bỏ phần còn lại một cách nhanh chóng.

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 vì tôi đang tìm kiếm một trình phân tích cú pháp không xác thực sẽ hoạt động với các ký tự nguồn wierd. Hy vọng điều này sẽ cho tôi kết quả mà tôi muốn.
Nathan C. Tresch

1
Ví dụ được thực hiện trong '09 và đây là cách nó được thực hiện.
Tor Valamo

14

Tôi có thể đề nghị decxml .

Tiết lộ đầy đủ: Tôi đã viết thư viện này bởi vì tôi đang tìm cách chuyển đổi giữa các cấu trúc dữ liệu XML và Python mà không cần phải viết hàng chục dòng mã phân tích / tuần tự hóa bắt buộc với ElementTree.

Với Decxml, bạn sử dụng các bộ xử lý để xác định khai báo cấu trúc của tài liệu XML của bạn và cách ánh xạ giữa các cấu trúc dữ liệu XML và Python. Bộ xử lý được sử dụng để cho cả tuần tự hóa và phân tích cú pháp cũng như cho một mức độ xác nhận cơ bản.

Phân tích cú pháp vào cấu trúc dữ liệu Python rất đơn giản:

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)

Mà tạo ra đầu ra:

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

Bạn cũng có thể sử dụng cùng một bộ xử lý để tuần tự hóa dữ liệu thành XML

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

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

Cái nào tạo ra đầu ra sau

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

Nếu bạn muốn làm việc với các đối tượng thay vì từ điển, bạn có thể xác định bộ xử lý để chuyển đổi dữ liệu đến và từ các đối tượng.

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)

Cái nào tạo ra đầu ra sau

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

13

Chỉ cần thêm một khả năng khác, bạn có thể sử dụng gỡ rối , vì nó là một thư viện đối tượng xml-to-python đơn giản. Ở đây bạn có một ví dụ:

Cài đặt:

pip install untangle

Sử dụng:

Tệp XML của bạn (một chút thay đổi):

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

Truy cập các thuộc tính với untangle:

import untangle

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

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

Đầu ra sẽ là:

bar_name
1

Thông tin thêm về gỡ rối có thể được tìm thấy trong " gỡ rối ".

Ngoài ra, nếu bạn tò mò, bạn có thể tìm thấy một danh sách các công cụ để làm việc với XML và Python trong " Python và XML ". Bạn cũng sẽ thấy rằng những câu hỏi phổ biến nhất đã được đề cập bởi các câu trả lời trước đó.


Điều gì làm cho gỡ rối khác với minidom?
Aaron Mann

Tôi không thể nói cho bạn biết sự khác biệt giữa hai điều đó vì tôi chưa từng làm việc với minidom.
trao đổi

10

Đây là một mã rất đơn giản nhưng hiệu quả bằng cách sử dụng 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")

Đây là từ " phân tích python xml ".


7

XML:

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

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

Đầu ra:

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

Điều này sẽ in giá trị của foobarthuộc tính.


6

xml.etree.EuityTree so với lxml

Đây là một số ưu điểm của hai thư viện được sử dụng nhiều nhất mà tôi có lợi ích cần biết trước khi chọn giữa chúng.

xml.etree.EuityTree:

  1. Từ thư viện chuẩn : không cần cài đặt bất kỳ mô-đun nào

lxml

  1. Dễ dàng viết khai báo XML : ví dụ bạn có cần thêm standalone="no"không?
  2. In ấn đẹp : bạn có thể có một XML thụt lề đẹp mà không cần thêm mã.
  3. Làm thành khách quan chức năng: Nó cho phép bạn sử dụng XML như thể bạn đang đối phó với một hệ thống phân cấp đối tượng Python bình thường .node.
  4. sourceline cho phép dễ dàng lấy dòng của phần tử XML mà bạn đang sử dụng.
  5. bạn cũng có thể sử dụng trình kiểm tra lược đồ XSD tích hợp.

5

Tôi thấy Python xml.domxml.dom.minidom khá dễ dàng. Hãy nhớ rằng DOM không tốt cho số lượng lớn XML, nhưng nếu đầu vào của bạn khá nhỏ thì điều này sẽ hoạt động tốt.


2

Không cần sử dụng API cụ thể lib nếu bạn sử dụng python-benedict. Chỉ cần khởi tạo một cá thể mới từ XML của bạn và quản lý nó dễ dàng vì nó là một dictlớp con.

Cài đặt dễ dàng: 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'])

Nó hỗ trợ và bình thường hóa I / O hoạt động với nhiều định dạng: Base64, CSV, JSON, TOML, XML, YAMLquery-string.

Nó được thử nghiệm tốt và mã nguồn mở trên 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'}'''

Vui lòng bao gồm một số bối cảnh giải thích cách câu trả lời của bạn giải quyết vấn đề. Câu trả lời chỉ có mã không được khuyến khích.
Pedram Parsian

-1

Nếu nguồn là tệp xml, hãy nói như mẫu này

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

bạn có thể thử đoạn mã sau

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)

Đầu ra sẽ là

{'FIRST_TAG': 'SAMPLE'}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.