XML 指可擴展標記語言(eXtensible Markup Language)。html
XML 被設計用來傳輸和存儲數據。python
XML 是一套定義語義標記的規則,這些標記將文檔分紅許多部件並對這些部件加以標識。編程
它也是元標記語言,即定義了用於定義其餘與特定領域有關的、語義的、結構化的標記語言的句法語言。api
常見的 XML 編程接口有 DOM 和 SAX,這兩種接口處理 XML 文件的方式不一樣,固然使用場合也不一樣。函數
Python 有三種方法解析 XML,SAX,DOM,以及 ElementTree:工具
Python 標準庫包含 SAX 解析器,SAX 用事件驅動模型,經過在解析XML的過程當中觸發一個個的事件並調用用戶定義的回調函數來處理XML文件。性能
將 XML 數據在內存中解析成一個樹,經過對樹的操做來操做XML。學習
ElementTree就像一個輕量級的DOM,具備方便友好的API。代碼可用性好,速度快,消耗內存少。this
注:因DOM須要將XML數據映射到內存中的樹,一是比較慢,二是比較耗內存,而SAX流式讀取XML文件,比較快,佔用內存少,但須要用戶實現回調函數(handler)。推薦使用ET來處理XML,除非你有什麼很是特別的須要。spa
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 是一種分級的數據形式,因此最天然的表示方法是將它表示爲一棵樹。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 支持。 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 的話,請看 這裏 。
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)
就像我在文章一開頭提到的那樣,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