鍍金池/ 教程/ Python/ xml
標(biāo)準(zhǔn)庫 (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫 (6)
標(biāo)準(zhǔn)庫 (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫 (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個簡單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫 (1)
生成器
mysql 數(shù)據(jù)庫 (1)
第三方庫
實戰(zhàn)
運算符
類(3)
字典(2)
語句(1)
數(shù)和四則運算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫 (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫 (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫 (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運算優(yōu)先級
字符串(3)
為計算做準(zhǔn)備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫 (2)
列表(2)
元組

xml

xml

xml 在軟件領(lǐng)域用途非常廣泛,有名人曰:

“當(dāng) XML(擴展標(biāo)記語言)于 1998 年 2 月被引入軟件工業(yè)界時,它給整個行業(yè)帶來了一場風(fēng)暴。有史以來第一次,這個世界擁有了一種用來結(jié)構(gòu)化文檔和數(shù)據(jù)的通用且適應(yīng)性強的格式,它不僅僅可以用于 WEB,而且可以被用于任何地方。”

---《Designing With Web Standards Second Edition》, Jeffrey Zeldman

對于 xml 如果要做一個定義式的說明,就不得不引用 w3school 里面簡潔而明快的說明:

  • XML 指可擴展標(biāo)記語言(EXtensible Markup Language)
  • XML 是一種標(biāo)記語言,很類似 HTML
  • XML 的設(shè)計宗旨是傳輸數(shù)據(jù),而非顯示數(shù)據(jù)
  • XML 標(biāo)簽沒有被預(yù)定義。您需要自行定義標(biāo)簽。
  • XML 被設(shè)計為具有自我描述性。
  • XML 是 W3C 的推薦標(biāo)準(zhǔn)

如果讀者要詳細(xì)了解和學(xué)習(xí)有關(guān) xml,可以閱讀w3school的教程

xml 的重要,關(guān)鍵在于它是用來傳輸數(shù)據(jù),因為傳輸數(shù)據(jù),特別是在 web 編程中,經(jīng)常要用到的。有了這樣一種東西,就讓數(shù)據(jù)傳輸變得簡單了。對于這么重要的,Python 當(dāng)然有支持。

一般來講,一個引人關(guān)注的東西,總會有很多人從不同側(cè)面去關(guān)注。在編程語言中也是如此,所以,對 xml 這個明星式的東西,Python 提供了多種模塊來處理。

  • xml.dom.* 模塊:Document Object Model。適合用于處理 DOM API。它能夠?qū)?xml 數(shù)據(jù)在內(nèi)存中解析成一個樹,然后通過對樹的操作來操作 xml。但是,這種方式由于將 xml 數(shù)據(jù)映射到內(nèi)存中的樹,導(dǎo)致比較慢,且消耗更多內(nèi)存。
  • xml.sax.* 模塊:simple API for XML。由于 SAX 以流式讀取 xml 文件,從而速度較快,切少占用內(nèi)存,但是操作上稍復(fù)雜,需要用戶實現(xiàn)回調(diào)函數(shù)。
  • xml.parser.expat:是一個直接的,低級一點的基于 C 的 expat 的語法分析器。 expat 接口基于事件反饋,有點像 SAX 但又不太像,因為它的接口并不是完全規(guī)范于 expat 庫的。
  • xml.etree.ElementTree (以下簡稱 ET):元素樹。它提供了輕量級的 Python 式的 API,相對于 DOM,ET 快了很多 ,而且有很多令人愉悅的 API 可以使用;相對于 SAX,ET 也有 ET.iterparse 提供了 “在空中” 的處理方式,沒有必要加載整個文檔到內(nèi)存,節(jié)省內(nèi)存。ET 的性能的平均值和 SAX 差不多,但是 API 的效率更高一點而且使用起來很方便。

所以,我用 xml.etree.ElementTree

ElementTree 在標(biāo)準(zhǔn)庫中有兩種實現(xiàn)。一種是純 Python 實現(xiàn):xml.etree.ElementTree ,另外一種是速度快一點:xml.etree.cElementTree 。

如果讀者使用的是 Python2.x,可以像這樣引入模塊:

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

如果是 Python3.3 以上,就沒有這個必要了,只需要一句話 import xml.etree.ElementTree as ET 即可,然后由模塊自動來尋找適合的方式。顯然 Python3.x 相對 Python2.x 有了很大進(jìn)步。但是,本教程礙于很多工程項目還沒有升級換代,暫且忍受了。

遍歷查詢

先要搞一個 xml 文檔。為了圖省事,我就用 w3school 中的一個例子:

http://wiki.jikexueyuan.com/project/start-learning-python/images/22601.jpg" alt="" />

這是一個 xml 樹,只不過是用圖來表示的,還沒有用 ET 解析呢。把這棵樹寫成 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 保存為名為 22601.xml 的文件,然后對其進(jìn)行如下操作:

>>> import xml.etree.cElementTree as ET

為了簡化,我用這種方式引入,如果在編程實踐中,推薦讀者使用 try...except...方式。

>>> tree = ET.ElementTree(file="22601.xml")
>>> tree
<ElementTree object at 0xb724cc2c>

建立起 xml 解析樹。然后可以通過根節(jié)點向下開始讀取各個元素(element 對象)。

在上述 xml 文檔中,根元素是,它沒有屬性,或者屬性為空。

>>> 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èi)容
'\n        '

再深點,就有感覺了:

>>> root[0][0].tag
'title'
>>> root[0][0].attrib
{'lang': 'en'}
>>> root[0][0].text
'Everyday Italian'

對于 ElementTree 對象,有一個 iter 方法可以對指定名稱的子節(jié)點進(jìn)行深度優(yōu)先遍歷。例如:

>>> for ele in tree.iter(tag="book"):        #遍歷名稱為 book 的節(jié)點
...     print ele.tag, ele.attrib
... 
book {'category': 'COOKING'} 
book {'category': 'CHILDREN'} 
book {'category': 'WEB'} 

>>> for ele in tree.iter(tag="title"):        #遍歷名稱為 title 的節(jié)點
...     print ele.tag, ele.attrib, ele.text
... 
title {'lang': 'en'} Everyday Italian
title {'lang': 'en'} Harry Potter
title {'lang': 'en'} Learning XML

如果不指定元素名稱,就是將所有的元素遍歷一邊。

>>> 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 {}

除了上面的方法,還可以通過路徑,搜索到指定的元素,讀取其內(nèi)容。這就是 xpath。此處對 xpath 不詳解,如果要了解可以到網(wǎng)上搜索有關(guān)信息。

>>> for ele in tree.iterfind("book/title"):
...     print ele.text
... 
Everyday Italian
Harry Potter
Learning XML

利用 findall() 方法,也可以是實現(xiàn)查找功能:

>>> for ele in tree.findall("book"):
...     title = ele.find('title').text
...     price = ele.find('price').text
...     lang = ele.find('title').attrib
...     print title, price, lang
... 
Everyday Italian 30.00 {'lang': 'en'}
Harry Potter 29.99 {'lang': 'en'}
Learning XML 39.95 {'lang': 'en'}

編輯

除了讀取有關(guān)數(shù)據(jù)之外,還能對 xml 進(jìn)行編輯,即增刪改查功能。還是以上面的 xml 文檔為例:

>>> root[1].tag
'book'
>>> del root[1]
>>> for ele in root:
...     print ele.tag
... 
book
book

如此,成功刪除了一個節(jié)點。原來有三個 book 節(jié)點,現(xiàn)在就還剩兩個了。打開源文件再看看,是不是正好少了第二個節(jié)點呢?一定很讓你失望,源文件居然沒有變化。

的確如此,源文件沒有變化,這就對了。因為至此的修改動作,還是停留在內(nèi)存中,還沒有將修改結(jié)果輸出到文件。不要忘記,我們是在內(nèi)存中建立的 ElementTree 對象。再這樣做:

>>> import os
>>> outpath = os.getcwd()
>>> file = outpath + "/22601.xml"

把當(dāng)前文件路徑拼裝好。然后:

>>> tree.write(file)

再看源文件,已經(jīng)變成兩個節(jié)點了。

除了刪除,也能夠修改:

>>> for price in root.iter("price"):        #原來每本書的價格
...     print price.text
... 
30.00
39.95
>>> for price in root.iter("price"):        #每本上漲 7 元,并且增加屬性標(biāo)記
...     new_price = float(price.text) + 7
...     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">37.0</price> 
    </book>
    <book category="WEB">
        <title lang="en">Learning XML</title> 
        <author>Erik T. Ray</author> 
        <year>2003</year> 
        <price updated="up">46.95</price> 
    </book>
</bookstore>

不僅價格修改了,而且在 price 標(biāo)簽里面增加了屬性標(biāo)記。干得不錯。

上面用 del 來刪除某個元素,其實,在編程中,這個用的不多,更喜歡用 remove() 方法。比如我要刪除 price > 40 的書。可以這么做:

>>> for book in root.findall("book"):
...     price = book.find("price").text
...     if float(price) > 40.0:
...         root.remove(book)
... 
>>> tree.write(file)

于是就這樣了:

<bookstore>
    <book category="COOKING">
        <title lang="en">Everyday Italian</title> 
        <author>Giada De Laurentiis</author> 
        <year>2005</year> 
        <price updated="up">37.0</price> 
    </book>
</bookstore>

接下來就要增加元素了。

>>> import xml.etree.cElementTree as ET
>>> tree = ET.ElementTree(file="22601.xml")
>>> root = tree.getroot()
>>> ET.SubElement(root, "book")        #在 root 里面添加 book 節(jié)點
<Element 'book' at 0xb71c7578>
>>> for ele in root:
...    print ele.tag
... 
book
book
>>> b2 = root[1]                      #得到新增的 book 節(jié)點
>>> b2.text = "Python"                #添加內(nèi)容
>>> tree.write("22601.xml")

查看源文件:

<bookstore>
    <book category="COOKING">
        <title lang="en">Everyday Italian</title> 
        <author>Giada De Laurentiis</author> 
        <year>2005</year> 
        <price updated="up">37.0</price> 
    </book>
    <book>python</book>
</bookstore>

常用屬性和方法總結(jié)

ET 里面的屬性和方法不少,這里列出常用的,供使用中備查。

Element 對象

常用屬性:

  • tag:string,元素數(shù)據(jù)種類
  • text:string,元素的內(nèi)容
  • attrib:dictionary,元素的屬性字典
  • tail:string,元素的尾形

針對屬性的操作

  • clear():清空元素的后代、屬性、text 和 tail 也設(shè)置為 None
  • get(key, default=None):獲取 key 對應(yīng)的屬性值,如該屬性不存在則返回 default 值
  • items():根據(jù)屬性字典返回一個列表,列表元素為(key, value)
  • keys():返回包含所有元素屬性鍵的列表
  • set(key, value):設(shè)置新的屬性鍵與值

針對后代的操作

  • append(subelement):添加直系子元素
  • extend(subelements):增加一串元素對象作為子元素
  • find(match):尋找第一個匹配子元素,匹配對象可以為 tag 或 path
  • findall(match):尋找所有匹配子元素,匹配對象可以為 tag 或 path
  • findtext(match):尋找第一個匹配子元素,返回其 text 值。匹配對象可以為 tag 或 path
  • insert(index, element):在指定位置插入子元素
  • iter(tag=None):生成遍歷當(dāng)前元素所有后代或者給定 tag 的后代的迭代器
  • iterfind(match):根據(jù) tag 或 path 查找所有的后代
  • itertext():遍歷所有后代并返回 text 值
  • remove(subelement):刪除子元素

ElementTree 對象

  • find(match)
  • findall(match)
  • findtext(match, default=None)
  • getroot():獲取根節(jié)點.
  • iter(tag=None)
  • iterfind(match)
  • parse(source, parser=None):裝載 xml 對象,source 可以為文件名或文件類型對象.
  • write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml") 

一個實例

最后,提供一個參考,這是一篇來自網(wǎng)絡(luò)的文章:Python xml 屬性、節(jié)點、文本的增刪改,本文的源碼我也復(fù)制到下面,請讀者參考:

實現(xiàn)思想:

使用 ElementTree,先將文件讀入,解析成樹,之后,根據(jù)路徑,可以定位到樹的每個節(jié)點,再對節(jié)點進(jìn)行修改,最后直接將其輸出.

#!/usr/bin/Python  
# -*- coding=utf-8 -*-  
# author : wklken@yeah.net  
# date: 2012-05-25  
# version: 0.1  

from xml.etree.ElementTree import ElementTree,Element  

def read_xml(in_path):  
    '''
        讀取并解析 xml 文件 
        in_path: xml 路徑 
        return: ElementTree
    '''  
    tree = ElementTree()  
    tree.parse(in_path)  
    return tree  

def write_xml(tree, out_path):  
    '''
        將 xml 文件寫出 
        tree: xml 樹 
        out_path: 寫出路徑
    '''  
    tree.write(out_path, encoding="utf-8",xml_declaration=True)  

def if_match(node, kv_map):  
    '''
        判斷某個節(jié)點是否包含所有傳入?yún)?shù)屬性 
        node: 節(jié)點 
        kv_map: 屬性及屬性值組成的 map
    '''  
    for key in kv_map:  
        if node.get(key) != kv_map.get(key):  
            return False  
    return True  

#---------------search -----  

def find_nodes(tree, path):  
    '''
        查找某個路徑匹配的所有節(jié)點 
        tree: xml 樹 
        path: 節(jié)點路徑
    '''  
    return tree.findall(path)  

def get_node_by_keyvalue(nodelist, kv_map):  
    '''
        根據(jù)屬性及屬性值定位符合的節(jié)點,返回節(jié)點 
        nodelist: 節(jié)點列表 
        kv_map: 匹配屬性及屬性值 map
    '''  
    result_nodes = []  
    for node in nodelist:  
        if if_match(node, kv_map):  
            result_nodes.append(node)  
    return result_nodes  

#---------------change -----  

def change_node_properties(nodelist, kv_map, is_delete=False):  
    '''
        修改/增加 /刪除 節(jié)點的屬性及屬性值 
        nodelist: 節(jié)點列表 
        kv_map:屬性及屬性值 map
    '''  
    for node in nodelist:  
        for key in kv_map:  
            if is_delete:   
                if key in node.attrib:  
                    del node.attrib[key]  
            else:  
                node.set(key, kv_map.get(key))  

def change_node_text(nodelist, text, is_add=False, is_delete=False):  
    '''
        改變/增加/刪除一個節(jié)點的文本 
        nodelist:節(jié)點列表 
        text : 更新后的文本
    '''  
    for node in nodelist:  
        if is_add:  
            node.text += text  
        elif is_delete:  
            node.text = ""  
        else:  
            node.text = text  

def create_node(tag, property_map, content):  
    '''
        新造一個節(jié)點 
        tag:節(jié)點標(biāo)簽 
        property_map:屬性及屬性值 map 
        content: 節(jié)點閉合標(biāo)簽里的文本內(nèi)容 
        return 新節(jié)點
    '''  
    element = Element(tag, property_map)  
    element.text = content  
    return element  

def add_child_node(nodelist, element):  
    '''
        給一個節(jié)點添加子節(jié)點 
        nodelist: 節(jié)點列表 
        element: 子節(jié)點
    '''  
    for node in nodelist:  
        node.append(element)  

def del_node_by_tagkeyvalue(nodelist, tag, kv_map):  
    '''
        同過屬性及屬性值定位一個節(jié)點,并刪除之 
        nodelist: 父節(jié)點列表 
        tag:子節(jié)點標(biāo)簽 
        kv_map: 屬性及屬性值列表
    '''  
    for parent_node in nodelist:  
        children = parent_node.getchildren()  
        for child in children:  
            if child.tag == tag and if_match(child, kv_map):  
                parent_node.remove(child)  

if __name__ == "__main__":  

    #1. 讀取 xml 文件  
    tree = read_xml("./test.xml")  

    #2. 屬性修改  
    #A. 找到父節(jié)點  
    nodes = find_nodes(tree, "processers/processer")  

    #B. 通過屬性準(zhǔn)確定位子節(jié)點  
    result_nodes = get_node_by_keyvalue(nodes, {"name":"BProcesser"})  

    #C. 修改節(jié)點屬性  
    change_node_properties(result_nodes, {"age": "1"})  

    #D. 刪除節(jié)點屬性  
    change_node_properties(result_nodes, {"value":""}, True)  

    #3. 節(jié)點修改  
    #A.新建節(jié)點  
    a = create_node("person", {"age":"15","money":"200000"}, "this is the firest content")  

    #B.插入到父節(jié)點之下  
    add_child_node(result_nodes, a)  

    #4. 刪除節(jié)點  
    #定位父節(jié)點  
    del_parent_nodes = find_nodes(tree, "processers/services/service")  

    #準(zhǔn)確定位子節(jié)點并刪除之  
    target_del_node = del_node_by_tagkeyvalue(del_parent_nodes, "chain", {"sequency" : "chain1"})  

    #5. 修改節(jié)點文本  
    #定位節(jié)點  
    text_nodes = get_node_by_keyvalue(find_nodes(tree, "processers/services/service/chain"), {"sequency":"chain3"})  
    change_node_text(text_nodes, "new text")  

    #6. 輸出到結(jié)果文件  
    write_xml(tree, "./out.xml")  

操作對象(原始 xml 文件):

<?xml version="1.0" encoding="UTF-8"?>  
<framework>  
    <processers>  
        <processer name="AProcesser" file="lib64/A.so"  
            path="/tmp">  
        </processer>  
        <processer name="BProcesser" file="lib64/B.so" value="fordelete">  
        </processer>  
        <processer name="BProcesser" file="lib64/B.so2222222"/>  

        <services>  
            <service name="search" prefix="/bin/search?"  
                output_formatter="OutPutFormatter:service_inc">  

                <chain sequency="chain1"/>  
                <chain sequency="chain2"></chain>  
            </service>  
            <service name="update" prefix="/bin/update?">  
                <chain sequency="chain3" value="fordelete"/>  
            </service>  
        </services>  
    </processers>  
</framework> 

執(zhí)行程序之后,得到的結(jié)果文件:

<?xml version='1.0' encoding='utf-8'?>  
<framework>  
    <processers>  
        <processer file="lib64/A.so" name="AProcesser" path="/tmp">  
        </processer>  
        <processer age="1" file="lib64/B.so" name="BProcesser">  
            <person age="15" money="200000">this is the firest content</person>  
        </processer>  
        <processer age="1" file="lib64/B.so2222222" name="BProcesser">  
            <person age="15" money="200000">this is the firest content</person>  
        </processer>  

        <services>  
            <service name="search" output_formatter="OutPutFormatter:service_inc"  
                prefix="/bin/search?">  

                <chain sequency="chain2" />  
            </service>  
            <service name="update" prefix="/bin/update?">  
                <chain sequency="chain3" value="fordelete">new text</chain>  
            </service>  
        </services>  
    </processers>  
</framework>  

總目錄   |   上節(jié):標(biāo)準(zhǔn)庫(6)   |   下節(jié):標(biāo)準(zhǔn)庫(8)

如果你認(rèn)為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。