Python 標準庫之XML
寫在之前
帶分隔符的檔案僅有兩維的資料:行 & 列。如果我們想在程式之間交換資料結構,需要一種方法把層次結構,序列,集合和其它的資料結構編碼成文字。
今天要說的 XML 是最突出的處理上述這種轉換的標記格式,它使用標籤(tag)分隔資料。XML 在軟體領域的用途非常廣泛。
XML
XML 是什麼?如果非要對其做一個定義式的說明,那這裡我不得不引用一下 w3school 裡面簡潔而明快的說明:
XML 指可擴充套件標記語言(EXtensible Markup Language);
XML 是一種標記語言,類似於 HTML;
XML 的設計宗旨是傳輸資料,而非顯示資料;
XML 標籤沒有被預定義,需要自行定義標籤;
XML 被設計為具有自我描述性;
XML 是 W3C 的推薦標準。
如果你想要詳細瞭解和學習 XML 的話,可以去閱讀 w3school 的 XML 教程即可,裡面講述的很詳細,在下面我還會引用一些裡面的內容。
XML 的重要性在於它是用來傳輸資料的,因此,特別是在 Web 程式設計中我們經常會用到它。有了它,讓資料傳輸變的更加簡單,這麼重要的東西,我大 Python 當然支援。
有大佬曾經說過:“一個引人關注的東西總會有很多人從不同側面去研究它”。這個在程式設計中也同樣適用,所以對於 XML 這個紅得發紫的東西,Python 提供了多種模組來處理。
-
xml.dom.* 模組:Document Object Model。適合用於處理 DOM API。它能夠將 XML 資料在記憶體中解析成一個樹,然後通過對樹的操作來操作 XML。但是這種方式由於將 XML 資料對映到記憶體中的樹,導致比較慢,且消耗更多記憶體。
-
xml.sax.* 模組:simple API for XML。由於 SAX 以流式讀取 XML 檔案,從而速度較快,佔用記憶體少,但是在操作上稍微複雜,需要使用者實現回撥函式。
當然還有一些別的,比如 xml.parse.expat,xml.etree.ElementTree 等等,我就不在列舉了,碰到的時候再去查查,否則光看這些東西頭就大了,而且無聊的很。
遍歷查詢
先要做一個 XML 文件,我自己想也想不出個啥太好的來,所以直接用 w3school 中的一個例子,如下圖所示:
上圖表示下面的 XML 中的一本書:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
將上述的 XML 儲存並且命名為 test.xml 檔案,接下來就是以它為物件,練習各種操作了。
>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file = 'test.xml')
>>> tree
<xml.etree.ElementTree.ElementTree object at 0x00000000025B8630>
上面建立起 XML 解析樹物件,然後通過根節點向下開始讀取各個元素(element 物件)。
在上述 XML 文件中,根元素是 bookstore,它沒有屬性,也可以說是屬性為空。
>>> root = tree.getroot()
>>> root.tag
'bookstore'
>>> root.attrib
{}
要想將根下面的元素都讀取出來,可以進行如下操作:
>>> for child in root:
... print(child.tag,child.attrib)
...
('book', {'category': 'COOKING'})
('book', {'category': 'CHILDREN'})
('book', {'category': 'WEB'})
也可以像下面這樣讀取指定元素的資訊:
>>> root[0].tag
'book'
>>> root[0].attrib
{'category': 'COOKING'}
>>> root[0].text
'\n '
上述的 root[0].text 無內容,再深入一層,我們就可以看到內容了:
>>> root[0][0].tag
'title'
>>> root[0][0].attrib
{'lang': 'en'}
>>> root[0][0].text
'Everyday Italian'
對於 ElementTree 物件,有一個 iter() 方法可以對指定名稱的子節點進行深度優先遍歷,例如下面這樣:
>>> for ele in tree.iter(tag='book'):
... print(ele.tag,ele.attrib)
...
('book', {'category': 'COOKING'})
('book', {'category': 'CHILDREN'})
('book', {'category': 'WEB'})
上述程式碼是遍歷名稱為 book 的節點,如果不指定節點的話,就是將所有的元素遍歷一遍:
>>> for ele in tree.iter():
... print(ele.tag,ele.attrib)
...
('bookstore', {})
('book', {'category': 'COOKING'})
('title', {'lang': 'en'})
('author', {})
('year', {})
('price', {})
('book', {'category': 'CHILDREN'})
('title', {'lang': 'en'})
('author', {})
('year', {})
('price', {})
('book', {'category': 'WEB'})
('title', {'lang': 'en'})
('author', {})
('year', {})
('price', {})
除了上面的方法外,還可以通過路徑搜尋到指定的元素,然後讀取其內容,這就是 xpath,關於 xpath 是什麼,在這不多做介紹,感興趣的可以去 Google。
編輯(增刪改查)
我們還是用上面的例子,為了方便檢視,我把內容再貼上過來,下面的內容記得儲存並且命名為 test.xml。
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
上一篇文章我們主要是對 xml 進行了讀取的有關操作,其實還可以對 XML 進行編輯,也就是增刪改查的功能,下面我們來操作一下:
>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file = "test.xml")
>>> root = tree.getroot() #獲得根
>>> root[1].tag
'book'
>>> del root[1]
>>> for ele in root:
... print(ele.tag)
...
book
book
如上,我們成功的刪除了一個節點,原來有 3 個 book 節點,現在就只剩下兩個了。接下來讓我們開啟原始檔看看,是不是正好缺少了第 2 個節點呢?結果讓我們很失望,原始檔並沒有什麼變化。
確實如此,原始檔並沒有變,因為到了這一步的修改動作還只是停留在記憶體裡,還沒有將修改的結果輸出到檔案,不要忘記我們是在記憶體中建立的 ElementTree 物件。那麼該如何做呢?請接著往下看:
>>> import os
>>> outpath = os.getcwd()
>>> file = outpath + "/test.xml"
把當前檔案的路徑拼裝好。
>>> tree.write(file)
做完上面的操作以後再去看原始檔,已經變成兩個節點了。
除了刪除,也是可以修改的:
>>> for price in root.iter('price'): #原來每本書的價格
... print(price.text)
...
30.00
39.95
>>> for price in root.iter('price'): #每本上漲 10 元並做標記
... new_price = float(price.text) + 10
... price.text = str(new_price)
... price.set("updated","up")
...
>>> tree.write(file)
然後我們來檢視一下原始檔:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price updated="up">50.0</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price updated="up">49.95</price>
</book>
</bookstore>
通過對比我們可以發現,不僅價格改變了,而且在 price 標籤裡面增加了屬性標記。
上面我們是用 del 來刪除某個元素,其實這個在程式設計中我們用的並不多,一般情況下更喜歡用 remove() 方法。比如要刪除 price = 50 的書,可以像下面這樣操作:
>>> tree.write(file)
>>> for book in root.findall("book"):
... price = book.find("price").text
... if float(price) == 50:
... root.remove(book)
...
>>> tree.write(file)
於是就有了下面的結果:
<bookstore>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price updated="up">49.95</price>
</book>
</bookstore>
接下來我們來看看增加元素:
>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file = 'test.xml')
>>> root = tree.getroot()
>>> ET.SubElement(root,"book") # 在root裡面新增book節點
<Element 'book' at 0x000000000209C778>
>>> for ele in root:
... print(ele.tag)
...
book
book
>>> b2 = root[1]
>>> b2.text = 'python'
>>> tree.write('test.xml')
這樣就大功告成了,然後再像上面一樣看一下原始檔,發現果真增加了。
常用的屬性 & 方法
ET 裡面的屬性 & 方法很多,這裡列出常用的幾個,供使用中備查。
1.Element 物件
常用的屬性如下:
- tag:string,元素資料種類
- text:string,元素的內容
- attrib:dictionary,元素的屬性字典
- tail:string,元素的尾形
針對屬性的操作如下:
- clear():清空元素的後代,屬性,text 和 tail 也設定為 None。
- items():根據屬性字典返回一個列表,列表元素為(key,value)。
- keys():返回包含所有元素屬性鍵的列表。
- set(key,value):設定新的屬性鍵和值。
針對後代的操作如下:
- append(subelement):新增直系子元素。
- extend(sunelements):增加一串元素物件作為子元素。
- find(match):尋找第一個匹配子元素,匹配物件可以為 tag 或 path。
- findall(match):尋找所有匹配子元素,匹配物件可以為 tag 或 path。
- insert(index,element):在指定位置插入子元素。
- remove(subelement):刪除子元素
2.ElementTree 物件
- find(match)。
- findall(match)。
- getroot():獲取根結點。
- parse(source,parser = None):裝載 XML 物件,source 可以為檔名或檔案型別物件。
寫在之後
更多內容,歡迎關注公眾號「Python空間」,期待和你的交流。