原文鏈接:
XML - Dive Into Python 3
深入探討
本書幾乎所有章節都圍繞一段示例代碼展開,但 XML 并非關于代碼,而是關于數據。
XML 的一個常見用途是 “聚合提要”(syndication feeds),用于列出博客、論壇或其他頻繁更新網站上的最新文章。大多數主流博客軟件都能生成提要,并在發布新文章、討論帖或博文時對其進行更新。你可以通過 “訂閱” 博客的提要來關注該博客,也可以使用專門的 “提要聚合器”(如谷歌閱讀器)同時關注多個博客。
接下來,本章將圍繞以下 XML 數據展開講解,它是一個提要,具體來說,是一個 Atom 聚合提要。
<?xml version='1.0' encoding='utf-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'><title>dive into mark</title><subtitle>currently between addictions</subtitle><id>tag:diveintomark.org,2001-07-29:/</id><updated>2009-03-27T21:56:07Z</updated><link rel='alternate' type='text/html' href='http://diveintomark.org/'/><link rel='self' type='application/atom+xml' href='http://diveintomark.org/feed/'/><entry><author><name>Mark</name><uri>http://diveintomark.org/</uri></author><title>Dive into history, 2009 edition</title><link rel='alternate' type='text/html'href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/><id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id><updated>2009-03-27T21:56:07Z</updated><published>2009-03-27T17:20:42Z</published><category scheme='http://diveintomark.org' term='diveintopython'/><category scheme='http://diveintomark.org' term='docbook'/><category scheme='http://diveintomark.org' term='html'/><summary type='html'> Putting an entire chapter on one page soundsbloated, but consider this &mdash; my longest chapter so farwould be 75 printed pages, and it loads in under 5 seconds&hellip;On dialup. </summary></entry><entry><author><name>Mark</name><uri>http://diveintomark.org/</uri></author><title>Accessibility is a harsh mistress</title><link rel='alternate' type='text/html'href='http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress'/><id>tag:diveintomark.org,2009-03-21:/archives/20090321200928</id><updated>2009-03-22T01:05:37Z</updated><published>2009-03-21T20:09:28Z</published><category scheme='http://diveintomark.org' term='accessibility'/><summary type='html'> The accessibility orthodoxy does not permit people toquestion the value of features that are rarely useful and rarely used. </summary></entry><entry><author><name>Mark</name></author><title>A gentle introduction to video encoding, part 1: container formats</title><link rel='alternate' type='text/html'href='http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats'/><id>tag:diveintomark.org,2008-12-18:/archives/20081218155422</id><updated>2009-01-11T19:39:22Z</updated><published>2008-12-18T15:54:22Z</published><category scheme='http://diveintomark.org' term='asf'/><category scheme='http://diveintomark.org' term='avi'/><category scheme='http://diveintomark.org' term='encoding'/><category scheme='http://diveintomark.org' term='flv'/><category scheme='http://diveintomark.org' term='GIVE'/><category scheme='http://diveintomark.org' term='mp4'/><category scheme='http://diveintomark.org' term='ogg'/><category scheme='http://diveintomark.org' term='video'/><summary type='html'> These notes will eventually become part of atech talk on video encoding. </summary></entry>
</feed>
五分鐘 XML 速成課程
XML 是一種用于描述層級結構化數據的通用方式。
一個 XML 文檔包含一個或多個元素,元素由開始標簽和結束標簽界定。以下是一個完整(盡管內容單調)的 XML 文檔:
<foo> ① </foo> ②
標記 | 說明 |
---|---|
① | 這是foo 元素的開始標簽。 |
② | 這是foo 元素對應的結束標簽。就像寫作、數學運算或代碼中的括號需要配對一樣,每個開始標簽都必須有對應的結束標簽與之匹配(閉合)。 |
元素可以進行任意深度的嵌套。在foo
元素內部的bar
元素,被稱為foo
元素的子元素。
根元素
<foo><bar></bar>
</foo>
每個 XML 文檔中的第一個元素稱為根元素,一個 XML 文檔只能有一個根元素。
以下內容不能稱為 XML 文檔,因為它包含兩個根元素:
<foo></foo>
<bar></bar>
屬性
元素可以擁有屬性,屬性是以 “名稱 - 值” 對形式存在的數據。
屬性列在元素的開始標簽內,通過空格分隔。同一個元素內不能重復出現相同名稱的屬性,且屬性值必須用引號括起來,單引號和雙引號均可使用。
<foo lang='en'> ① <bar id='papayawhip' lang="fr"></bar> ②
</foo>
標記 | 說明 |
---|---|
① | foo 元素有一個屬性,名稱為lang ,其屬性值為en 。 |
② | bar 元素有兩個屬性,名稱分別為id 和lang ,其中lang 屬性的值為fr 。這與foo 元素的lang 屬性并不沖突,因為每個元素都有自己獨立的屬性集合。 |
若一個元素擁有多個屬性,屬性的順序并不重要。
元素的屬性就像 Python 中的字典,是一組無序的 “鍵 - 值” 對,且每個元素可定義的屬性數量沒有限制。
文本內容
元素還可以包含文本內容,示例如下:
<foo lang='en'><bar lang='fr'>PapayaWhip</bar>
</foo>
既不包含文本內容也沒有子元素的元素稱為空元素,示例如下:
空元素
<foo></foo>
空元素有一種簡寫形式,在開始標簽中加入/
字符,就可以省略結束標簽。因此,上面示例中的 XML 文檔也可寫成:
<foo/>
命名空間
就像 Python 函數可以在不同模塊中定義一樣,XML 元素也可以在不同的命名空間中定義。
命名空間通常以 URL 的形式呈現。
可以使用xmlns
聲明來定義默認命名空間,這種聲明形式類似屬性,但用途不同。
<feed xmlns='http://www.w3.org/2005/Atom'> ① <title>dive into mark</title> ②
</feed>
標記 | 說明 |
---|---|
① | feed 元素處于http://www.w3.org/2005/Atom 命名空間中。 |
② | title 元素同樣處于http://www.w3.org/2005/Atom 命名空間中。命名空間聲明不僅對其所在的元素生效,還對該元素的所有子元素生效。 |
也可以使用xmlns:prefix
聲明來定義命名空間,并將其與一個前綴相關聯。
之后,該命名空間下的每個元素都必須顯式地使用這個前綴來聲明。
<atom:feed xmlns:atom='http://www.w3.org/2005/Atom'> ① <atom:title>dive into mark</atom:title> ②
</atom:feed>
標記 | 說明 |
---|---|
① | feed 元素處于http://www.w3.org/2005/Atom 命名空間中。 |
② | title 元素同樣處于http://www.w3.org/2005/Atom 命名空間中。 |
對于 XML 解析器而言,上述兩個 XML 文檔是完全相同的。
在 XML 中,元素的標識由 “命名空間 + 元素名稱” 共同構成。
前綴的作用僅僅是引用命名空間,因此前綴的實際名稱(如這里的atom:
)并不重要。只要兩個 XML 文檔的命名空間匹配、元素名稱匹配、屬性(或無屬性的情況)匹配,并且每個元素的文本內容也匹配,那么這兩個 XML 文檔就是相同的。
字符編碼信息
最后,XML 文檔可以在首行(根元素之前)包含字符編碼信息。(如果您好奇,文檔解析前需要知曉編碼信息,而編碼信息又包含在文檔內,這看似矛盾,XML 規范的 F 部分詳細說明了如何解決這一 “Catch-22” 困境 —— 即左右為難的局面。)
<?xml version='1.0' encoding='utf-8'?>
至此,您對 XML 的了解已足夠應對基礎應用場景!
XML 命名空間(namespace)
所謂的namespace,就是一種已經定義好的規范,我只要加了這個namespace對應的鏈接,我的xml文檔的格式就要遵循這個規范,有什么子標簽,有什么屬性,以及他們的順序,就不能自己發揮了。
1. 命名空間 = “規范的 “身份標識”,關聯它就意味著 “遵循對應的格式規則”
命名空間的核心價值 ——通過一個唯一的 “鏈接(URI)”,把你的 XML 文檔和一份預先定義好的 “官方規范” 綁定。
這個 “鏈接” 本身不是用來 “訪問” 的,而是一個 “身份 ID”,告訴解析工具(如瀏覽器、數據處理程序):“我的文檔遵循的是【這個 ID 對應的規范】,所有標簽和屬性都要按它的規則來校驗”。
比如之前提到的 Atom 提要:
- 你在根標簽
<feed>
里加了xmlns="http://www.w3.org/2005/Atom"
,這個http://www.w3.org/2005/Atom
就是 Atom 規范的 “namespace ID”; - 一旦加了它,你的 XML 就必須符合 W3C 制定的《Atom Syndication Format》規范:必須有
<title>
(標題)、<updated>
(更新時間)等強制子標簽,<author>
標簽下必須包含<name>
(作者名),標簽順序也有明確要求(比如<updated>
要在<entry>
之前)—— 完全不能自己隨便加標簽(比如亂加一個<my-own-tag>
),也不能漏了強制標簽,否則就是 “不符合規范的 Atom 文檔”,解析工具會報錯或無法識別。
2. 命名空間的 “額外功能”—— 解決 “標簽重名沖突”
除了 “綁定規范”,命名空間還有一個重要作用:當一份 XML 文檔需要同時用多個規范時,區分 “同名但來自不同規范的標簽”。
比如你要寫一個既包含 “Atom 提要”(展示文章)、又包含 “SVG 圖標”(展示小圖標)的 XML 文檔:
- Atom 規范里有
<title>
標簽(表示文章標題); - SVG 規范里也有
<title>
標簽(表示圖標的標題);
如果不加命名空間,解析工具根本分不清<title>
是 “文章標題” 還是 “圖標標題”。
這時候就可以用命名空間 “給標簽貼標簽”:
<!-- 聲明兩個命名空間:atom對應Atom規范,svg對應SVG規范 -->
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom" xmlns:svg="http://www.w3.org/2000/svg"><!-- 用“atom:”前綴表示這個<title>屬于Atom規范(文章標題) --><atom:title>我的博客</atom:title><!-- 用“svg:”前綴表示這個<title>屬于SVG規范(圖標標題) --><svg:svg width="100" height="100"><svg:title>首頁圖標</svg:title></svg:svg>
</atom:feed>
這里的命名空間,既各自綁定了 Atom 和 SVG 的規范(標簽仍需遵循對應規則),又解決了 “同名標簽沖突”—— 這是對 “規范綁定” 功能的補充,但核心依然是 “通過 ID 關聯規則”。
3. “遵循規范” 的 “強制性”,依賴 “解析工具的校驗”
“不能自己發揮”,本質上是 “如果自己發揮,解析工具會不識別或報錯”—— 因為規范本身是 “約定”,而 “強制遵循” 的落地,需要工具(如 XML 校驗器、瀏覽器)去 “對照規范檢查你的文檔”。
如果只是加了 namespace 鏈接,但沒有用工具校驗,文檔語法上可能沒報錯,但實際上不符合規范(比如漏了 Atom 的<updated>
標簽),最終還是無法被 Atom 閱讀器正常訂閱 —— 所以 “遵循規范” 的關鍵,是讓文檔符合 namespace 對應的 “官方規則”,而不只是表面加個鏈接。
總結:
namespace 是 “規范的唯一 ID”,加了它既意味著你的 XML 要遵循該 ID 對應的官方規則(子標簽、屬性、順序都有要求),也能解決多規范共存時的標簽重名問題,最終讓你的文檔能被工具正確解析和使用。
namespace示例:Atom 提要的結構
試想一個博客網站,或者任何一個內容頻繁更新的網站(如CNN.com)。
這類網站通常會有標題(例如CNN.com的標題是 “CNN.com”)、副標題(如 “突發新聞、美國新聞、國際新聞、天氣預報、娛樂及視頻新聞”)、最后更新日期(如 “美國東部時間 2009 年 5 月 16 日星期六下午 12:43 更新”),以及一系列在不同時間發布的文章。
每篇文章也都有標題、首次發布日期(若文章后續有更正或修改錯別字,還會有最后更新日期)和唯一的統一資源定位符(URL)。
Atom 聚合格式旨在以一種標準格式整合所有這些信息。
我的博客與CNN.com在設計風格、涵蓋范圍和目標受眾上差異極大,但它們都具備相同的基本結構:CNN.com有標題,我的博客也有標題;CNN.com發布文章,我也發布文章。
Atom 提要的頂層是所有 Atom 提要共有的根元素 —— 處于http://www.w3.org/2005/Atom
命名空間下的feed
元素。
<feed xmlns='http://www.w3.org/2005/Atom' ① xml:lang='en'> ②
標記 | 說明 |
---|---|
① | http://www.w3.org/2005/Atom 是 Atom 命名空間。 |
② | 任何元素都可以包含xml:lang 屬性,該屬性用于聲明元素及其子元素的語言。在本示例中,根元素上聲明了xml:lang 屬性,這意味著整個提要的語言為英語。 |
一個 Atom 提要包含多項關于提要本身的信息,這些信息以頂層feed
元素的子元素形式存在。
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into mark</title> ① <subtitle>currently between addictions</subtitle> ② <id>tag:diveintomark.org,2001-07-29:/</id> ③ <updated>2009-03-27T21:56:07Z</updated> ④ <link rel='alternate' type='text/html' href='http://diveintomark.org/'/> ⑤
標記 | 說明 |
---|---|
① | 該提要的標題為dive into mark 。 |
② | 該提要的副標題為currently between addictions 。 |
③ | 每個提要都需要一個全球唯一的標識符,關于如何創建該標識符,可參考 RFC 4151(一種請求評論文檔,用于定義互聯網標準)。 |
④ | 該提要的最后更新時間為 2009 年 3 月 27 日格林尼治標準時間 21:56,通常這個時間與最新文章的最后修改時間一致。 |
⑤ | 接下來的內容會更有趣。這個link 元素沒有文本內容,但有三個屬性:rel 、type 和href 。rel 屬性的值表明了鏈接的類型,rel='alternate' 表示該鏈接指向該提要的另一種呈現形式;type='text/html' 屬性表明這是一個指向超文本標記語言(HTML)頁面的鏈接;而鏈接的目標地址則由href 屬性給出。 |
至此,我們了解到這個提要對應的網站名為 “dive into mark”,網站地址為http://diveintomark.org/
,且該提要的最后更新時間為 2009 年 3 月 27 日。
需要注意的是,在某些 XML 文檔中元素的順序可能很重要,但在 Atom 提要中,元素的順序無關緊要。
在提要級元數據之后,是最新文章的列表。一篇文章在 Atom 提要中的結構如下所示:
<entry> <author> ①<name>Mark</name><uri>http://diveintomark.org/</uri></author> <title>Dive into history, 2009 edition</title> ② <link rel='alternate' type='text/html' ③href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/> <id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id> ④ <updated>2009-03-27T21:56:07Z</updated> ⑤<published>2009-03-27T17:20:42Z</published> <category scheme='http://diveintomark.org' term='diveintopython'/> ⑥<category scheme='http://diveintomark.org' term='docbook'/><category scheme='http://diveintomark.org' term='html'/> <summary type='html'>Putting an entire chapter on one page sounds ⑦bloated, but consider this &mdash; my longest chapter so farwould be 75 printed pages, and it loads in under 5 seconds&hellip;On dialup. </summary>
</entry> ⑧
標記 | 說明 |
---|---|
① | author 元素表明了該文章的作者信息:作者名為馬克(Mark),其個人網站地址為http://diveintomark.org/ 。(該地址與提要元數據中的備用鏈接地址相同,但這并非必需的情況。許多博客有多位作者,每位作者都可能擁有自己的個人網站。) |
② | title 元素給出了文章的標題 ——“Dive into history, 2009 edition”(《深入探索歷史,2009 年版》)。 |
③ | 與提要級別的備用鏈接類似,這個link 元素提供了該文章 HTML 版本的地址。 |
④ | 和提要一樣,每篇文章(entry )也需要一個唯一的標識符。 |
⑤ | 文章包含兩個日期信息:首次發布日期(由published 元素指定)和最后修改日期(由updated 元素指定)。 |
⑥ | 文章可以被歸類到任意數量的類別中。本文被歸類到 ` |
ElementTree:解析 XML(Parsing XML)
Python 提供了多種解析 XML 文檔的方式,包括傳統的 DOM(文檔對象模型)解析器和 SAX(簡單 API for XML)解析器,但本章將重點介紹另一個名為ElementTree的庫。
parse()
函數:
>>> import xml.etree.ElementTree as etree ①>>> tree = etree.parse('examples/feed.xml') ②
>>> root = tree.getroot() ③
>>> root ④
<Element {http://www.w3.org/2005/Atom}feed at cd1eb0>
標記 | 說明 |
---|---|
① | ElementTree 庫是 Python 標準庫的一部分,位于xml.etree.ElementTree 模塊中。 |
② | ElementTree 庫的主要入口是parse() 函數,該函數可接收文件名或類文件對象作為參數,一次性解析整個 XML 文檔。若內存緊張,也可采用增量方式解析 XML 文檔。 |
③ | parse() 函數返回一個代表整個文檔的對象,該對象并非根元素。需調用getroot() 方法才能獲取根元素的引用。 |
④ | 正如預期,根元素是處于http://www.w3.org/2005/Atom 命名空間下的feed 元素。該對象的字符串表示形式印證了一個重要觀點:XML 元素由其命名空間和標簽名(也稱為本地名)共同構成。由于此文檔中所有元素均處于 Atom 命名空間,因此根元素被表示為{http://www.w3.org/2005/Atom}feed 。 |
在 ElementTree 中,XML 元素以{命名空間}本地名
的格式表示,你會在 ElementTree API 的多個場景中看到并使用這種格式。
元素即列表(Elements Are Lists)
在 ElementTree API 中,一個元素的行為類似列表,列表中的項即為該元素的子元素。
# 承接上一個示例
>>> root.tag ①
'{http://www.w3.org/2005/Atom}feed'>>> len(root) ②
8>>> for child in root: ③
... print(child) ④
...
<Element {http://www.w3.org/2005/Atom}title at e2b5d0>
<Element {http://www.w3.org/2005/Atom}subtitle at e2b4e0>
<Element {http://www.w3.org/2005/Atom}id at e2b6c0>
<Element {http://www.w3.org/2005/Atom}updated at e2b6f0>
<Element {http://www.w3.org/2005/Atom}link at e2b4b0>
<Element {http://www.w3.org/2005/Atom}entry at e2b720>
<Element {http://www.w3.org/2005/Atom}entry at e2b510>
<Element {http://www.w3.org/2005/Atom}entry at e2b750>
標記 | 說明 |
---|---|
① | 承接上一個示例,根元素為{http://www.w3.org/2005/Atom}feed 。 |
② | 根元素的 “長度” 即其包含的子元素數量。 |
③ | 可將元素本身用作迭代器,遍歷其所有子元素。 |
④ | 從輸出可見,根元素確實包含 8 個子元素:所有提要級元數據(title 、subtitle 、id 、updated 和link ), followed by the three?entry ?elements. |
或許你已經猜到,但在此仍需明確說明:元素的子元素列表僅包含直接子元素。
每個entry
元素都有自己的子元素,但這些子元素不會出現在根元素feed
的子元素列表中,它們只會被包含在各自所屬的entry
元素的子元素列表里。
不過,也有方法可以查找任意嵌套層級的元素,本章后續將介紹兩種常用方法。
屬性即字典(Attributes Are Dictionaries)
XML 不僅由元素構成,每個元素還可擁有自己的屬性集合。只要獲取到特定元素的引用,就能輕松將其屬性以 Python 字典的形式獲取。
# 承接上一個示例
>>> root.attrib ①
{'{http://www.w3.org/XML/1998/namespace}lang': 'en'}>>> root[4] ②
<Element {http://www.w3.org/2005/Atom}link at e181b0>>>> root[4].attrib ③
{'href': 'http://diveintomark.org/','type': 'text/html','rel': 'alternate'}>>> root[3] ④
<Element {http://www.w3.org/2005/Atom}updated at e2b4e0>>>> root[3].attrib ⑤
{}
標記 | 說明 |
---|---|
① | attrib 屬性是元素屬性的字典形式。原始標記為<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> ,其中xml: 前綴引用的是一個內置命名空間,所有 XML 文檔無需聲明即可使用該命名空間。 |
② | 第 5 個子元素(在 0 索引列表中為[4] )是link 元素。 |
③ | link 元素有 3 個屬性:href 、type 和rel 。 |
④ | 第 4 個子元素(在 0 索引列表中為[3] )是updated 元素。 |
⑤ | updated 元素沒有任何屬性,因此其.attrib 屬性是空字典。 |
在 XML 文檔中搜索節點
到目前為止,我們對 XML 文檔的操作都是 “自上而下” 的:從根元素開始,獲取其子元素,再依次深入文檔結構。
1、查找特定元素:findall()
方法
但在許多 XML 使用場景中,需要查找特定元素,ElementTree 也支持這一操作。
>>> import xml.etree.ElementTree as etree>>> tree = etree.parse('examples/feed.xml')>>> root = tree.getroot()>>> root.findall('{http://www.w3.org/2005/Atom}entry') ①
[<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>,<Element {http://www.w3.org/2005/Atom}entry at e2b510>,<Element {http://www.w3.org/2005/Atom}entry at e2b540>]>>> root.tag
'{http://www.w3.org/2005/Atom}feed'>>> root.findall('{http://www.w3.org/2005/Atom}feed') ②[]
>>> root.findall('{http://www.w3.org/2005/Atom}author') ③
[]
標記 | 說明 |
---|---|
① | findall() 方法用于查找符合特定查詢條件的子元素(查詢格式詳情稍后介紹)。 |
② | 包括根元素在內的所有元素都擁有findall() 方法,該方法會在元素的子元素中查找所有匹配項。但為何此處沒有返回結果?盡管不那么明顯,但該查詢僅搜索當前元素的直接子元素。由于根元素feed 沒有名為feed 的子元素,因此查詢返回空列表。 |
③ | 這個結果可能也會讓你意外:文檔中確實存在author 元素(每個entry 元素中各有一個,共 3 個),但這些author 元素并非根元素的直接子元素,而是 “孫元素”(即子元素的子元素)。若要查找任意嵌套層級的author 元素,查詢格式需稍作調整。 |
>>> tree.findall('{http://www.w3.org/2005/Atom}entry') ①
[<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>,<Element {http://www.w3.org/2005/Atom}entry at e2b510>,<Element {http://www.w3.org/2005/Atom}entry at e2b540>]>>> tree.findall('{http://www.w3.org/2005/Atom}author') ②
[]
標記 | 說明 |
---|---|
① | 為方便使用,tree 對象(由etree.parse() 函數返回)提供了多個與根元素方法對應的鏡像方法,其結果與調用tree.getroot().findall() 方法完全一致。 |
② | 或許令人意外,該查詢并未找到文檔中的author 元素。原因在于,這只是tree.getroot().findall('{http://www.w3.org/2005/Atom}author') 的簡寫形式,含義是 “查找所有作為根元素子元素的author 元素”。而author 元素是entry 元素的子元素,并非根元素的子元素,因此查詢無匹配結果。 |
2、find()
方法
此外,還有一個find()
方法,該方法返回第一個匹配的元素。
在僅預期一個匹配項,或存在多個匹配項但只需關注第一個時,此方法非常實用。
>>> entries = tree.findall('{http://www.w3.org/2005/Atom}entry') ①
>>> len(entries)
3>>> title_element = entries[0].find('{http://www.w3.org/2005/Atom}title') ②>>> title_element.text
'Dive into history, 2009 edition'>>> foo_element = entries[0].find('{http://www.w3.org/2005/Atom}foo') ③>>> foo_element>>> type(foo_element)
<class 'NoneType'>
標記 | 說明 |
---|---|
① | 如前所述,該語句查找所有atom:entry 元素。 |
② | find() 方法接收一個 ElementTree 查詢條件,返回第一個匹配的元素。 |
③ | 該entry 元素中不存在名為foo 的元素,因此返回None 。 |
【注意】:
find()
方法存在一個容易讓人踩坑的 “陷阱”:
在布爾上下文中,若 ElementTree 元素對象沒有子元素(即len(element)
為 0),其布爾值會被判定為False
。這意味著,if element.find('...')
并非判斷find()
方法是否找到匹配元素,而是判斷找到的匹配元素是否有子元素!
若要判斷find()
方法是否返回了元素,應使用if element.find('...') is not None
。
3、查找后代元素://
ElementTree 也支持查找后代元素(即子元素、孫元素及任意嵌套層級的元素),具體方式如下:
>>> all_links = tree.findall('//{http://www.w3.org/2005/Atom}link') ①
>>> all_links
[<Element {http://www.w3.org/2005/Atom}link at e181b0>,<Element {http://www.w3.org/2005/Atom}link at e2b570>,<Element {http://www.w3.org/2005/Atom}link at e2b480>,<Element {http://www.w3.org/2005/Atom}link at e2b5a0>]>>> all_links[0].attrib ②
{'href': 'http://diveintomark.org/','type': 'text/html','rel': 'alternate'}>>> all_links[1].attrib ③
{'href': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition','type': 'text/html','rel': 'alternate'}>>> all_links[2].attrib
{'href': 'http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress','type': 'text/html','rel': 'alternate'}>>> all_links[3].attrib
{'href': 'http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats','type': 'text/html','rel': 'alternate'}
標記 | 說明 |
---|---|
① | 該查詢//{http://www.w3.org/2005/Atom}link 與之前的示例類似,唯一區別是查詢開頭的兩個斜杠(// )。這兩個斜杠表示 “不局限于直接子元素,查找文檔中任意嵌套層級的匹配元素”,因此結果返回 4 個link 元素,而非僅 1 個。 |
② | 第一個結果是根元素的直接子元素,從其屬性可知,這是提要級別的備用鏈接,指向該提要所描述網站的 HTML 版本。 |
③ | 另外 3 個結果均為條目級別的備用鏈接:每個entry 元素都有一個link 子元素,正是由于查詢開頭的雙斜杠,這些元素才被全部找到。 |
總結:
總體而言,ElementTree 的findall()
方法功能強大,但查詢語言可能存在一些反直覺的設計。官方將其描述為 “對 XPath 表達式的有限支持”——XPath 是 W3C 制定的 XML 文檔查詢標準。ElementTree 的查詢語言與 XPath 足夠相似,可滿足基礎搜索需求,但差異也足夠大,若你已熟悉 XPath,可能會對此感到不便。接下來,我們將介紹一個第三方 XML 庫lxml
,它在 ElementTree API 的基礎上擴展了完整的 XPath 支持。