介紹如何在 Python 中使用 xml.etree.ElementTree 模組來讀取與寫入 XML 格式的資料與檔案。
在 Python 中如果需要讀取 XML 檔案,或是將資料寫入 XML 檔案中,可以使用 xml.etree.ElementTree 模組來處理,以下是此模組的使用方式與範例。
假設有一個 XML 檔案 country_data.xml
內容如下:
<?xml version="1.0"?> <data> <country name="Liechtenstein"> <rank>1</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor name="Austria" direction="E"/> <neighbor name="Switzerland" direction="W"/> </country> <country name="Singapore"> <rank>4</rank> <year>2011</year> <gdppc>59900</gdppc> <neighbor name="Malaysia" direction="N"/> </country> <country name="Panama"> <rank>68</rank> <year>2011</year> <gdppc>13600</gdppc> <neighbor name="Costa Rica" direction="W"/> <neighbor name="Colombia" direction="E"/> </country> </data>
在讀取 XML 格式的資料時,可以直接從 *.xml
檔案載入,或是從字串中取得:
import xml.etree.ElementTree as ET # 從檔案載入並解析 XML 資料 tree = ET.parse('country_data.xml') root = tree.getroot() # 從字串中取得並解析 XML 資料 root = ET.fromstring(country_data_as_string)
這裡產生的 root
是一個 Element
物件,代表 XML 的根節點,每一個 Element
物件都有 tag
與 attrib
兩個屬性:
# 節點 tag 屬性 print(root.tag)
data
# 節點 attrib 屬性 print(root.attrib)
{}
透過 for
迴圈可以列出所有的子節點:
# 子節點與屬性 for child in root: print(child.tag, child.attrib)
country {'name': 'Liechtenstein'} country {'name': 'Singapore'} country {'name': 'Panama'}
也可以使用索引的方式存取任意的節點,透過 text
屬性即可取得節點的內容:
# 使用索引存取節點 print(root[0][1].text)
2008
亦可透過 get
直接取得指定的屬性值:
# 取得指定的屬性值 print(root[0][3].get('name'))
Austria
iter
可以在指定節點之下,以遞迴方式搜尋所有子節點:
# 搜尋所有子節點 for neighbor in root.iter('neighbor'): print(neighbor.attrib)
{'name': 'Austria', 'direction': 'E'} {'name': 'Switzerland', 'direction': 'W'} {'name': 'Malaysia', 'direction': 'N'} {'name': 'Costa Rica', 'direction': 'W'} {'name': 'Colombia', 'direction': 'E'}
findall
與 find
則是只從第一層子節點中搜尋(不包含第二層以下),findall
會傳回所有結果,而 find
則是只傳回第一個找到的節點:
# 只從第一層子節點中搜尋,傳回所有找到的節點 for country in root.findall('country'): # 只從第一層子節點中搜尋,傳回第一個找到的節點 rank = country.find('rank').text # 取得節點指定屬性質 name = country.get('name') print(name, rank)
Liechtenstein 1 Singapore 4 Panama 68
XML 節點的資料可以透過 Element.text
來修改,而屬性值則可以使用 Element.set()
來指定,若要將修改的結果寫入 XML 檔案,則可使用 ElementTree.write()
:
# 尋找 rank 節點 for rank in root.iter('rank'): # 將 rank 的數值加 1 new_rank = int(rank.text) + 1 # 設定新的 rank 值 rank.text = str(new_rank) # 增加一個 updated 屬性值 rank.set('updated', 'yes') # 寫入 XML 檔案 tree.write('output.xml')
編輯之後的 XML 檔案內容會像這樣:
<?xml version="1.0"?> <data> <country name="Liechtenstein"> <rank updated="yes">2</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor name="Austria" direction="E"/> <neighbor name="Switzerland" direction="W"/> </country> <country name="Singapore"> <rank updated="yes">5</rank> <year>2011</year> <gdppc>59900</gdppc> <neighbor name="Malaysia" direction="N"/> </country> <country name="Panama"> <rank updated="yes">69</rank> <year>2011</year> <gdppc>13600</gdppc> <neighbor name="Costa Rica" direction="W"/> <neighbor name="Colombia" direction="E"/> </country> </data>
若要移除 XML 的節點,可以使用 Element.remove()
:
# 在第一層子節點鐘尋找 country 節點 for country in root.findall('country'): # 取得 rank 數值 rank = int(country.find('rank').text) # 若 rank 大於 50,則移除此節點 if rank > 50: root.remove(country) # 寫入 XML 檔案 tree.write('output.xml')
移除節點之後的 XML 檔案內容會像這樣:
<?xml version="1.0"?> <data> <country name="Liechtenstein"> <rank updated="yes">2</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor name="Austria" direction="E"/> <neighbor name="Switzerland" direction="W"/> </country> <country name="Singapore"> <rank updated="yes">5</rank> <year>2011</year> <gdppc>59900</gdppc> <neighbor name="Malaysia" direction="N"/> </country> </data>
若要建立一個全新的 XML 結構,可以使用 Element
建立根節點,再以 SubElement()
加入子節點:
# 建立新的 XML 結構 orders = ET.Element('orders') # 新增節點 order1 = ET.SubElement(orders, 'order') order1.text = "My Order 1" order1.set("new", "yes") # 新增節點 order2 = ET.SubElement(orders, 'order') order2.text = "My Order 2" order2.set("new", "no") # 輸出 XML 原始資料 ET.dump(orders)
<orders><order new="yes">My Order 1</order><order new="no">My Order 2</order></orders>
XPath 可以讓使用者在 XML 結構中以較複雜的條件進行搜尋,以下是一些常見的範例。
# 頂層節點 root.findall(".") # 尋找「頂層節點 => country => neighbor」這樣結構的節點 root.findall("./country/neighbor") # 尋找 name 屬性為 Singapore,且含有 year 子節點的節點 root.findall(".//year/..[@name='Singapore']") # 尋找父節點 name 屬性為 Singapore 的 year 節點 root.findall(".//*[@name='Singapore']/year") # 尋找在同一層 neighbor 節點中排在第二位的那一個 root.findall(".//neighbor[2]")
若要對一般的 XML 檔案內容進行自動排版,可以使用 lxml
模組的 etree
:
import lxml.etree as etree # 讀取 XML 檔案 root = etree.parse("country_data.xml") # 輸出排版的 XML 資料 print(etree.tostring(root, pretty_print=True, encoding="unicode")) # 將排版的 XML 資料寫入檔案 root.write("pretty_print.xml", encoding="utf-8")