一、首先了解XML
如果你已經瞭解xml,可以跳過這一部分。
xml是一種描述層次結構化數據的通用方法。xml文檔包含由起始和結束標籤(tag)分隔的一個或多個元素(element)。以下也是一個完整的(雖然空洞)xml文件:
<foo> ①
</foo> ②
① |
這是foo 元素的起始標籤。 |
② |
這是foo 元素對應的結束標籤。就如寫作、數學或者代碼中需要平衡括號一樣,每一個起始標籤必須有對應的結束標籤來閉合(匹配)。 |
元素可以嵌套到任意層次。位於foo
中的元素bar
可以被稱作其子元素。
<foo>
<bar></bar>
</foo>
xml文檔中的第一個元素叫做根元素(root element)。並且每份xml文檔只能有一個根元素。以下不是一個xml文檔,因爲它存在兩個“根元素”。
<foo></foo>
<bar></bar>
元素可以有其屬性(attribute),它們是一些名字-值(name-value)對。屬性由空格分隔列舉在元素的起始標籤中。一個元素中屬性名不能重複。屬性值必須用引號包圍起來。單引號、雙引號都是可以。
<foo lang='en'> ①
<bar id='papayawhip' lang="fr"></bar> ②
</foo>
① |
foo 元素有一個叫做lang 的屬性。lang 的值爲en |
② |
bar 元素則有兩個屬性,分別爲id 和lang 。其中lang 屬性的值爲fr 。它不會與foo 的那個屬性產生衝突。每個元素都其獨立的屬性集。 |
如果元素有多個屬性,書寫的順序並不重要。元素的屬性是一個無序的鍵-值對集,跟Python中的列表對象一樣。另外,元素中屬性的個數是沒有限制的。
元素可以有其文本內容(text content)
<foo lang='en'>
<bar lang='fr'>PapayaWhip</bar>
</foo>
如果某一元素既沒有文本內容,也沒有子元素,它也叫做空元素。
<foo></foo>
表達空元素有一種簡潔的方法。通過在起始標籤的尾部添加/
字符,我們可以省略結束標籤。上一個例子中的xml文檔可以寫成這樣:
<foo/>
就像Python函數可以在不同的模塊(modules)中聲明一樣,也可以在不同的名字空間(namespace)中聲明xml元素。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 元素也是。名字空間聲明不僅會作用於當前聲明它的元素,還會影響到該元素的所有子元素。 |
也可以通過xmlns:prefix
聲明來定義一個名字空間並取其名爲prefix。然後該名字空間中的每個元素都必須顯式地使用這個前綴(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 元素也在那個名字空間。 |
對於xml解析器而言,以上兩個xml文檔是一樣的。名字空間 + 元素名 = xml標識。前綴只是用來引用名字空間的,所以對於解析器來說,這些前綴名(atom:
)其實無關緊要的。名字空間相同,元素名相同,屬性(或者沒有屬性)相同,每個元素的文本內容相同,則xml文檔相同。
最後,在根元素之前,字符編碼信息可以出現在xml文檔的第一行。(這裏存在一個兩難的局面(catch-22),直觀上來說,解析xml文檔需要這些編碼信息,而這些信息又存在於xml文檔中,如果你對xml如何解決此問題有興趣,請參閱xml規範中 F 章節)
<?xml version='1.0' encoding='utf-8'?>
1 XML 的聲明
<?xml version=”1.0” standalone=”yes” encoding=”UTF-8”?>這是一個XML處理指令。處理指令以 <? 開始,以 ?> 結束。<? 後的第一個單詞是指令名,如xml, 代表XML聲明。 version, standalone, encoding 是三個特性,特性是由等號分開的名稱-數值對,等號左邊是特性名稱,等號右邊是特性的值,用引號引起來。 幾點解釋:
- version: 說明這個文檔符合1.0規範
- standalone: 說明文檔在這一個文件裏還是需要從外部導入, standalone 的值設爲yes 說明所有的文檔都在這一文件裏完成
- encoding: 指文檔字符編碼
2 XML 根元素定義
XML文檔的樹形結構要求必須有一個根元素。根元素的起始標記要放在所有其它元素起始標記之前,根元素的結束標記根放在其它所有元素的結束標記之後,如
3 XML元素
元素的基本結構由 開始標記,數據內容,結束標記組成,如
需要注意的是:
- 元素標記區分大小寫,<Name> 與 <name>是兩個不同的標記
- 結束標記必須有反斜槓,如 </Name>
XML元素標記命名規則如下:
- 名字中可以包含字母,數字及其它字母
- 名字不能以數字或下劃線開頭
- 名字不能用xml開頭
- 名字中不能包含空格和冒號
4 XML中的註釋
XML中註釋如下:
需要注意的是:
- 註釋中不要出現“--”或“-”
- 註釋不要放在標記中
- 註釋不能嵌套
5 PI (Processing Instruction) PI 指 Processing Instruction, 處理指令。PI以“<?”開頭,以“?>”結束,用來給下游的文檔傳遞信息。
例子表明這個XML文檔用core.css控制顯示。
參考 http://www.cnblogs.com/jb8164/articles/736515.html 簡單講解XML
現在我們已經知道足夠多的xml知識,可以開始探險了!
二、python操作XML
例如
<?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 sounds
bloated, but consider this &mdash; my longest chapter so far
would 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 to
question 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 a
tech talk on video encoding.</summary>
</entry>
</feed><?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 sounds
bloated, but consider this &mdash; my longest chapter so far
would 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 to
question 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 a
tech talk on video encoding.</summary>
</entry>
</feed>
Python可以使用幾種不同的方式解析xml文檔。它包含了dom和sax解析器,但是我們焦點將放在另外一個叫做ElementTree的庫上邊。
>>> 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 。 |
② |
parse() 函數是ElementTree庫的主要入口,它使用文件名或者流對象作爲參數。parse() 函數會立即解析完整個文檔。如果內存資源緊張,也可以增量式地解析xml文檔 |
③ |
parse() 函數會返回一個能代表整篇文檔的對象。這不是根元素。要獲得根元素的引用可以調用getroot() 方法。 |
④ |
如預期的那樣,根元素即http://www.w3.org/2005/Atom 名字空間中的feed 。該字符串表示再次重申了非常重要的一點:xml元素由名字空間和標籤名(也稱作本地名(local
name))組成。這篇文檔中的每個元素都在名字空間Atom中,所以根元素被表示爲{http://www.w3.org/2005/Atom}feed 。 |
☞ElementTree使用
{namespace}localname
來表達xml元素。我們將會在ElementTree的api中多次見到這種形式。
元素即列表#
在ElementTree API中,元素的行爲就像列表一樣。列表中的項即該元素的子元素。
# continued from the previous example >>> 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個子元素:所有feed級的元數據(title ,subtitle ,id ,updated 和link ),還有緊接着的三個entry 元素。 |
也許你已經注意到了,但我還是想要指出來:該列表只包含直接子元素。每一個entry
元素都有其子元素,但是並沒有包括在這個列表中。這些子元素本可以包括在entry
元素的列表中,但是確實不屬於feed
的子元素。但是,無論這些元素嵌套的層次有多深,總是有辦法定位到它們的;在這章的後續部分我們會介紹兩種方法。
屬性即字典#
xml不只是元素的集合;每一個元素還有其屬性集。一旦獲取了某個元素的引用,我們可以像操作Python的字典一樣輕鬆獲取到其屬性。
# continuing from the previous example >>> 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不需要聲明就可以使用它。 |
② |
第五個子元素 — 以0爲起始的列表中即[4] — 爲元素link 。 |
③ |
link 元素有三個屬性:href ,type ,和rel 。 |
④ |
第四個子元素 — [3] — 爲updated 。 |
⑤ |
元素
元素爲列表,可以用片段操作符操作, 屬性爲字典,有鍵和值。 |
⁂
在XML文檔中查找結點#
到目前爲止,我們已經“自頂向下“地從根元素開始,一直到其子元素,走完了整個文檔。但是許多情況下我們需要找到xml中特定的元素。Etree也能完成這項工作。
>>> 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') ③ []
① |
findfall() 方法查找匹配特定格式的子元素。(關於查詢的格式稍後會講到。) |
② |
每個元素 — 包括根元素及其子元素 — 都有findall() 方法。它會找到所有匹配的子元素。但是爲什麼沒有看到任何結果呢?也許不太明顯,這個查詢只會搜索其子元素。由於根元素feed 中不存在任何叫做feed 的子元素,所以查詢的結果爲一個空的列表。 |
③ |
這個結果也許也在你的意料之外。在這篇文檔中確實存在author 元素;事實上總共有三個(每個entry 元素中都有一個)。但是那些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 元素的子元素,所以查詢沒有找到任何匹配的。 |
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作爲參數,返回第一個匹配到的元素。 |
③ |
在entries[0] 中沒有叫做foo 的元素,所以返回值爲None 。 |
☞可逮住你了,在這裏
find()
方法非常容易被誤解。在布爾上下文中,如果ElementTree元素對象不包含子元素,其值則會被認爲是False
(即如果len(element)
等於0)。這就意味着if element.find('...')
並非在測試是否find()
方法找到了匹配項;這條語句是在測試匹配到的元素是否包含子元素!想要測試find()
方法是否返回了一個元素,則需使用if element.find('...') is not None
。
也可以在所有派生(descendant)元素中搜索,即任意嵌套層次的子元素,孫子元素等…
>>> 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 與前一樣例很相似,除了開頭的兩條斜線。這兩條斜線告訴findall() 方法“不要只在直接子元素中查找;查找的範圍可以是任意嵌套層次”。 |
② | 查詢到的第一個結果是根元素的直接子元素。從它的屬性中可以看出,它是一個指向該feed的html版本的備用鏈接。 |
③ |
其他的三個結果分別是低一級的備用鏈接。每一個entry 都有單獨一個link 子元素,由於在查詢語句前的兩條斜線的作用,我們也能定位到他們。 |
總的來說,ElementTree的findall()
方法是其一個非常強大的特性,但是它的查詢語言卻讓人有些出乎意料。官方描述它爲“有限的XPath支持。”XPath是一種用於查詢xml文檔的W3C標準。對於基礎地查詢來說,ElementTree與XPath語法上足夠相似,但是如果已經會XPath的話,它們之間的差異可能會使你感到不快。現在,我們來看一看另外一個第三方xml庫,它擴展了ElementTree的api以提供對XPath的全面支持。
重點:
用根元素,這樣可以查找任意元素。
查找第一個節點,用find
查找一個元素的所有直接子元素findall()
加上兩條斜線。這兩條斜線告訴findall()
方法“不要只在直接子元素中查找;查找的範圍可以是任意嵌套層次”。
Element中的遍歷與查詢
Element.iter(tag=None):遍歷該Element所有後代,也可以指定tag進行遍歷尋找。
Element.findall(path):查找當前元素下tag或path能夠匹配的直系節點。
Element.find(path):查找當前元素下tag或path能夠匹配的首個直系節點。
Element.text: 獲取當前元素的text值。
Element.get(key, default=None):獲取元素指定key對應的屬性值,如果沒有該屬性,則返回default值。
參考:
http://www.cnblogs.com/ifantastic/archive/2013/04/12/3017110.html Python標準庫之xml.etree.ElementTree
http://www.w3school.com.cn/xmldom/dom_methods.asp XML DOM - 屬性和方法
http://blog.csdn.net/yueguanghaidao/article/details/7265246 例子
http://blog.csdn.net/menglei8625/article/details/7494509 Python_使用ElementTree解析xml文件
http://www.cnblogs.com/jb8164/articles/736515.html 簡單講解XML
http://woodpecker.org.cn/diveintopython3/xml.html 系統講XML解析和python解析 不錯