Python XML 解析

什麼是 XML?

XML 指可擴展標記語言(eXtensible Markup Language)。html

XML 被設計用來傳輸和存儲數據。python

XML 是一套定義語義標記的規則,這些標記將文檔分紅許多部件並對這些部件加以標識。編程

它也是元標記語言,即定義了用於定義其餘與特定領域有關的、語義的、結構化的標記語言的句法語言。api

Python 對 XML 的解析

常見的 XML 編程接口有 DOM 和 SAX,這兩種接口處理 XML 文件的方式不一樣,固然使用場合也不一樣。函數

Python 有三種方法解析 XML,SAX,DOM,以及 ElementTree:工具

1.SAX (simple API for XML )

Python 標準庫包含 SAX 解析器,SAX 用事件驅動模型,經過在解析XML的過程當中觸發一個個的事件並調用用戶定義的回調函數來處理XML文件。性能

2.DOM(Document Object Model)

將 XML 數據在內存中解析成一個樹,經過對樹的操做來操做XML。學習

3.ElementTree(元素樹)

ElementTree就像一個輕量級的DOM,具備方便友好的API。代碼可用性好,速度快,消耗內存少。this

注:因DOM須要將XML數據映射到內存中的樹,一是比較慢,二是比較耗內存,而SAX流式讀取XML文件,比較快,佔用內存少,但須要用戶實現回調函數(handler)。推薦使用ET來處理XML,除非你有什麼很是特別的須要。spa

ElementTree - 一個 API ,兩種實現

ElementTree 生來就是爲了處理 XML ,它在 Python 標準庫中有兩種實現。一種是純 Python 實現例如 xml.etree.ElementTree ,另一種是速度快一點的 xml.etree.cElementTree 。你要記住: 儘可能使用 C 語言實現的那種,由於它速度更快,並且消耗的內存更少。若是你的電腦上沒有 _elementtree 那麼你須要這樣作:

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

這是一個讓 Python 不一樣的庫使用相同 API 的一個比較經常使用的辦法。仍是那句話,你的編譯環境和別人的極可能不同,因此這樣作能夠防止一些莫名其妙的小問題。注意:從 Python 3.3 開始,你沒有必要這麼作了,由於 ElementTree 模塊會自動尋找可用的 C 庫來加快速度。因此只須要 import xml.etree.ElementTree 就能夠了。可是在 3.3 正式推出以前,你最好仍是使用我上面提供的那段代碼。

將 XML 解析爲樹的形式

咱們來說點基礎的。XML 是一種分級的數據形式,因此最天然的表示方法是將它表示爲一棵樹。ET 有兩個對象來實現這個目的 - ElementTree 將整個 XML 解析爲一棵樹, Element 將單個結點解析爲樹。若是是整個文檔級別的操做(好比說讀,寫,找到一些有趣的元素)一般用 ElementTree 。單個 XML 元素和它的子元素一般用 Element 。下面的例子能說明我剛纔囉嗦的一大堆。

咱們用這個 XML 文件來作例子:

<?xml version="1.0"?>
<doc>
    <branch name="testing" hash="1cdf045c">
        text,source
    </branch>
    <branch name="release01" hash="f200013e">
        <sub-branch name="subrelease01">
            xml,sgml
        </sub-branch>
    </branch>
    <branch name="invalid">
    </branch>
</doc>

讓咱們加載而且解析這個 XML :

>>> import xml.etree.cElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')

而後抓根結點元素:

>>> tree.getroot()
<Element 'doc' at 0x11eb780>

和預期同樣,root 是一個 Element 元素。咱們能夠來看看:

>>> root = tree.getroot()
>>> root.tag, root.attrib
('doc', {})

看吧,根元素沒有任何狀態。就像任何 Element 同樣,它能夠找到本身的子結點:

>>> for child_of_root in root:
...     print(child_of_root.tag, child_of_root.attrib)
...
('branch', {'hash': '1cdf045c', 'name': 'testing'})
('branch', {'hash': 'f200013e', 'name': 'release01'})
('branch', {'name': 'invalid'})

咱們也能夠進入一個指定的子結點:

>>> root[0].tag, root[0].text
('branch', '\n        text,source\n    ')

找到咱們感興趣的元素

從上面的例子咱們能夠垂手可得的看到,咱們能夠用一個簡單的遞歸獲取 XML 中的任何元素。然而,由於這個操做比較廣泛,ET 提供了一些有用的工具來簡化操做.

Element 對象有一個 iter 方法能夠對子結點進行深度優先遍歷。(好像本身手敲哦一個dfs...) ElementTree 對象也有 iter 方法來提供便利。

>>> for elem in tree.iter():
...     print(elem.tag, elem.attrib)
... 
('doc', {})
('branch', {'hash': '1cdf045c', 'name': 'testing'})
('branch', {'hash': 'f200013e', 'name': 'release01'})
('sub-branch', {'name': 'subrelease01'})
('branch', {'name': 'invalid'})

遍歷全部的元素,而後檢驗有沒有你想要的。ET 可讓這個過程更便捷。 iter 方法接受一個標籤名字,而後只遍歷那些有指定標籤的元素:

>>> for elem in tree.iter(tag='branch'):
...     print elem.tag, elem.attrib
... 
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}

來自 XPath 的幫助

爲了尋找咱們感興趣的元素,一個更加有效的辦法是使用 XPath 支持。 Element 有一些關於尋找的方法能夠接受 XPath 做爲參數。 find返回第一個匹配的子元素, findall 以列表的形式返回全部匹配的子元素, iterfind 爲全部匹配項提供迭代器。這些方法在 ElementTree 裏面也有。

給出一個例子:

>>> for elem in tree.iterfind('branch/sub-branch'):
...      print(elem.tag, elem.attrib)
... 
('sub-branch', {'name': 'subrelease01'})

這個例子在 branch 下面找到全部標籤爲 sub-branch 的元素。而後給出如何找到全部的 branch 元素,用一個指定 name 的狀態便可:

>>> for elem in tree.iterfind('branch[@name="release01"]'):
...      print(elem.tag, elem.attrib)
... 
('branch', {'hash': 'f200013e', 'name': 'release01'})

想要深刻學習 XPath 的話,請看 這裏 。

創建 XML 文檔

ET 提供了創建 XML 文檔和寫入文件的便捷方式。 ElementTree 對象提供了 write 方法。

如今,這兒有兩個經常使用的寫 XML 文檔的腳本。

修改文檔可使用 Element 對象的方法:

>>> for subelem in root:
...    print(subelem.tag, subelem.attrib)
... 
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}

咱們在這裏刪除了根元素的第三個子結點,而後爲第一個子結點增長新狀態。而後這個樹能夠寫回到文件中。

>>> import sys
>>> tree.write(sys.stdout)   # ET.dump can also serve this purpose, ET.dump(tree)
<doc>
    <branch foo="bar" hash="1cdf045c" name="testing">
        text,source
    </branch>
<branch hash="f200013e" name="release01">
    <sub-branch name="subrelease01">
        xml,sgml
    </sub-branch>
</branch>
</doc>

注意狀態的順序和原文檔的順序不太同樣。這是由於 ET 講狀態保存在無序的字典中。語義上來講,XML 並不關心順序。

創建一個全新的元素也很容易。ET 模塊提供了 SubElement 函數來簡化過程:

>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
<root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>

若是要修改好的保存到指定文件

tree.write(new_data.xml)

使用 iterparse 來處理 XML 流

就像我在文章一開頭提到的那樣,XML 文檔一般比較大,因此將它們所有讀入內存的庫可能會有點兒小問題。這也是爲何我建議使用 SAX API 來替代 DOM 。

咱們剛講過如何使用 ET 來將 XML 讀入內存而且處理。但它就不會碰到和 DOM 同樣的內存問題麼?固然會。這也是爲何這個包提供一個特殊的工具,用來處理大型文檔,而且解決了內存問題,這個工具叫 iterparse 。

我給你們演示一個 iterparse 如何使用的例子。我用 自動生成 拿到了一個 XML 文檔來進行說明。這只是開頭的一小部分:

<?xml version="1.0" standalone="yes"?>
<site>
    <regions>
        <africa>
            <item id="item0">
                <location>United States</location>    <!-- Counting locations -->
                <quantity>1</quantity>
                <name>duteous nine eighteen </name>
                <payment>Creditcard</payment>
                <description>
                    <parlist>
[...]

我已經用註釋標出了我要處理的元素,咱們用一個簡單的腳原本計數有多少 location 元素而且文本內容爲「Zimbabwe」。這是用 ET.parse 的一個標準的寫法:

tree = ET.parse(sys.argv[2])

count = 0
for elem in tree.iter(tag='location'):
    if elem.text == 'Zimbabwe':
        count += 1
print count

全部 XML 樹中的元素都會被檢驗。當處理一個大約 100MB 的 XML 文件時,佔用的內存大約是 560MB ,耗時 2.9 秒。

注意:咱們並不須要在內存中加載整顆樹。它檢測咱們須要的帶特定值的 location 元素。其餘元素被丟棄。這是 iterparse 的來源:

count = 0
for event, elem in ET.iterparse(sys.argv[2]):
    if event == 'end':
        if elem.tag == 'location' and elem.text == 'Zimbabwe':
            count += 1
    elem.clear() # discard the element

print(count)

這個循環遍歷 iterparse 事件,檢測「閉合的」(end)事件而且尋找 location 標籤和指定的值。在這裏 elem.clear() 是關鍵 - iterparse 仍然創建一棵樹,只不過不須要所有加載進內存,這樣作能夠有效的利用內存空間。

處理一樣的文件,這個腳本佔用內存只須要僅僅的 7MB ,耗時 2.5 秒。速度的提高歸功於生成樹的時候只遍歷一次。相比較來講, parse方法首先創建了整個樹,而後再次遍從來尋找咱們須要的元素(因此慢了一點)。

結論

在 Python 衆多處理 XML 的模塊中, ElementTree 真是屌爆了。它將輕量,符合 Python 哲學的 API ,出色的性能完美的結合在了一塊兒。因此說若是要處理 XML ,果斷地使用它吧!

 

 

參考資料:
 1. 用 ElementTree 在 Python 中解析 XML

 2. Python-xml-文件處理

相關文章
相關標籤/搜索