python解析xml之lxml


雖然python解析xml的庫很多,但是,由於lxml在底層是用C語言實現的,所以lxml在速度上有明顯優勢。除了速度上的優勢,lxml在使用方面,易用性也非常好。這里將以下面的xml數據為例,介紹lxml的簡單使用。

 

例子:dblp.xml(dblp數據的片段)
<?xml version='1.0' encoding='utf-8'?>  
<dblp>
       <article mdate="2012-11-28" key="journals/entropy/BellucciFMY08">  
        <author>Stefano Bellucci</author>  
        <author>Sergio Ferrara</author>  
        <author>Alessio Marrani</author>  
        <author>Armen Yeranyan</author>  
        <title>ES<sup>2</sup>: A cloud data storage system for supporting both OLTP and OLAP.</title>
        <pages>507-555</pages>  
        <year>2008</year>  
        <volume>10</volume>  
        <journal>Entropy</journal>  
        <number>4</number>  
        <ee>http://dx.doi.org/10.3390/e10040507</ee>  
        <url>db/journals/entropy/entropy10.html#BellucciFMY08</url>  
    </article>  
    <article mdate="2013-03-04" key="journals/entropy/Knuth13">  
        <author>Kevin H. Knuth</author>  
        <title><i>Entropy</i> Best Paper Award 2013.</title>  
        <pages>698-699</pages>  
        <year>2013</year>  
        <volume>15</volume>  
        <journal>Entropy</journal>  
        <number>2</number>  
        <ee>http://dx.doi.org/10.3390/e15020698</ee>  
        <url>db/journals/entropy/entropy15.html#Knuth13</url>  
    </article>  
</dblp>

 

 

1、將xml解析為樹結構,並得到該樹的根。

為了將xml解析為樹結構,並得到該樹的根,要進行如下的操作:

 

1 #!/usr/bin/python
2 #-*-coding:utf-8-*-
3 from lxml import etree#導入lxml庫
4 tree = etree.parse("dblp.xml")#將xml解析為樹結構
5 root = tree.getroot()#獲得該樹的樹根

 

另外,如果xml數據中出現了關於dtd的聲明(如下面的例子),那樣的話,必須在使用lxml解析xml的時候,進行相應的聲明。

 

xml文件中含有dtd聲明的例子:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE dblp SYSTEM "dblp.dtd">
<dblp>
<article mdate="2002-01-03" key="persons/Codd71a">
<author>E. F. Codd</author>
<title>Further Normalization of the Data Base Relational Model.</title>
<journal>IBM Research Report, San Jose, California</journal>
<volume>RJ909</volume>
<month>August</month>
<year>1971</year>
<a href="http://lib.csdn.net/base/20" class="replace_word" title="Hadoop知識庫" target="_blank" style="color:#df3434; font-weight:bold;">hadoop</a>@hadoop:~/20130722dblpxml$ head -15 dblp.xml 
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE dblp SYSTEM "dblp.dtd">
<dblp>
<article mdate="2002-01-03" key="persons/Codd71a">
<author>E. F. Codd</author>
<title>Further Normalization of the Data Base Relational Model.</title>
<journal>IBM Research Report, San Jose, California</journal>
<volume>RJ909</volume>
<month>August</month>
<year>1971</year>
<cdrom>ibmTR/rj909.pdf</cdrom>
<ee>db/labs/ibm/RJ909.html</ee>
</article>
</dblp>

 

這時候,要想將xml數據解析為樹結構並得到該樹的樹根,必須進行如下的操作:

 

1 #!/usr/bin/python
2 #-*-coding:utf-8-*-
3 from lxml import etree#導入lxml庫
4 parser=etree.XMLParser(load_dtd= True)#首先根據dtd得到一個parser(注意dtd文件要放在和xml文件相同的目錄)
5 tree = etree.parse("dblp.xml",parser)#用上面得到的parser將xml解析為樹結構
6 root = tree.getroot()#獲得該樹的樹根

 

 

2、遍歷樹結構,獲得各元素的屬性及其子元素。

1 for article in root:#這樣便可以遍歷根元素的所有子元素(這里是article元素)
2     print "元素名稱:",article.tag#用.tag得到該子元素的名稱
3     for field in article:#遍歷article元素的所有子元素(這里是指article的author,title,volume,year等)
4         print field.tag,":",field.text#同樣地,用.tag可以得到元素的名稱,而.text可以得到元素的內容
5     mdate=article.get("mdate")#用.get("屬性名")可以得到article元素相應屬性的值
6     key=article.get("key")
7     print "mdate:",mdate
8     print "key",key
9     print ""#隔行分開不同的article元素

到這里,便可以進行簡單的xml數據的解析了。

 

3、解析xml數據的例子

用下面的代碼解析文章開頭的名為dblp.xml數據。

 

 1 #!/usr/bin/python
 2 #-*-coding:utf-8-*-
 3 from lxml import etree#導入lxml庫
 4 tree = etree.parse("dblp.xml")#將xml解析為樹結構
 5 root = tree.getroot()#獲得該樹的樹根
 6 
 7 for article in root:#這樣便可以遍歷根元素的所有子元素(這里是article元素)
 8     print "元素名稱:",article.tag#用.tag得到該子元素的名稱
 9     for field in article:#遍歷article元素的所有子元素(這里是指article的author,title,volume,year等)
10         print field.tag,":",field.text#同樣地,用.tag可以得到元素的名稱,而.text可以得到元素的內容
11     mdate=article.get("mdate")#用.get("屬性名")可以得到article元素相應屬性的值
12     key=article.get("key")
13     print "mdate:",mdate
14     print "key",key
15     print ""#隔行分開不同的article元素

 

 

便可以得到輸出如下:

 1 元素名稱: article
 2 author : Stefano Bellucci
 3 author : Sergio Ferrara
 4 author : Alessio Marrani
 5 author : Armen Yeranyan
 6 title : ES
 7 pages : 507-555
 8 year : 2008
 9 volume : 10
10 journal : Entropy
11 number : 4
12 ee : http://dx.doi.org/10.3390/e10040507
13 url : db/journals/entropy/entropy10.html#BellucciFMY08
14 mdate: 2012-11-28
15 key: journals/entropy/BellucciFMY08
16 
17 
18 元素名稱: article
19 author : Kevin H. Knuth
20 title : None
21 pages : 698-699
22 year : 2013
23 volume : 15
24 journal : Entropy
25 number : 2
26 ee : http://dx.doi.org/10.3390/e15020698
27 url : db/journals/entropy/entropy15.html#Knuth13
28 mdate: 2013-03-04
29 key: journals/entropy/Knuth13

 

4、元素既有sub-element,又有text的處理

可以看到在上面的例子中,title元素的內容是不正確的。由於title元素及包含sub-element,又有text內容(如下),這時簡單的用.text,並不能正確的得到title元素的內容。上面的例子中,第一個article元素的title只取到了ES,而第二個article元素的title則什么都沒取到,None。

1 <title>ES<sup>2</sup>: A cloud data storage system for supporting both OLTP and OLAP.</title>
2 <title><i>Entropy</i> Best Paper Award 2013.</title> 

 

由於在這個例子中,子元素比較簡單,這里就簡單的采取將子元素和text一起打印的方法來解決這一問題。代碼如下:

 1 #!/usr/bin/python
 2 #-*-coding:utf-8-*-
 3 from lxml import etree#導入lxml庫
 4 tree = etree.parse("dblp.xml")#將xml解析為樹結構
 5 root = tree.getroot()#獲得該樹的樹根
 6 
 7 for article in root:#這樣便可以遍歷根元素的所有子元素(這里是article元素)
 8     print "元素名稱:",article.tag#用.tag得到該子元素的名稱
 9     for field in article:#遍歷article元素的所有子元素(這里是指article的author,title,volume,year等)
10         if field.tag=="title":
11             print field.tag,":",etree.tostring(field,encoding='utf-8',pretty_print=False)#將元素text連同sub_element一起打印
12         else:
13             print field.tag,":",field.text#同樣地,用.tag可以得到元素的名稱,而.text可以得到元素的內容
14     mdate=article.get("mdate")#用.get("屬性名")可以得到article元素相應屬性的值
15     key=article.get("key")
16     print "mdate:",mdate
17     print "key:",key
18     print ""#隔行分開不同的article元素

 

輸出如下:

 1 元素名稱: article
 2 author : Stefano Bellucci
 3 author : Sergio Ferrara
 4 author : Alessio Marrani
 5 author : Armen Yeranyan
 6 title : <title>ES<sup>2</sup>: A cloud data storage system for supporting both OLTP and OLAP.</title>
 7         
 8 pages : 507-555
 9 year : 2008
10 volume : 10
11 journal : Entropy
12 number : 4
13 ee : http://dx.doi.org/10.3390/e10040507
14 url : db/journals/entropy/entropy10.html#BellucciFMY08
15 mdate: 2012-11-28
16 key: journals/entropy/BellucciFMY08
17 
18 元素名稱: article
19 author : Kevin H. Knuth
20 title : <title><i>Entropy</i> Best Paper Award 2013.</title>  
21         
22 pages : 698-699
23 year : 2013
24 volume : 15
25 journal : Entropy
26 number : 2
27 ee : http://dx.doi.org/10.3390/e15020698
28 url : db/journals/entropy/entropy15.html#Knuth13
29 mdate: 2013-03-04
30 key: journals/entropy/Knuth13

當然,不難看出這個問題用這種方法解決比較傻,后面還得將title內容中的tag等不需要部分通過各種字符串的處理將其去掉。最好的方法是能有比較簡單的方法,分別獲取到一個元素的text和sub_element。下面就講一下如何實現這個需求:

 

5、sub_element和text優雅實現版

假設xml文件paper.xml內容如下:

<?xml version="1.0" encoding="ISO-8859-1"?>
<dblp>
    <article mdate="2002-01-03" key="persons/Codd71a">
        <author>E. F. Codd</author>
        <title>ES<sup>2</sup>: A cloud data storage system for supporting both OLTP and OLAP.</title>
        <journal>IBM Research Report, San Jose, California</journal>
        <volume>RJ909</volume>
        <month>August</month>
        <year>1971</year>
    </article>
    <article mdate="2002-01-03" key="persons/Codd71a">
        <author>E. F. Codd</author>
        <title><i>Entropy</i> Best Paper Award 2013.</title>
        <journal>IBM Research Report, San Jose, California</journal>
        <volume>RJ909</volume>
        <month>August</month>
        <year>1971</year>
        <cdrom>ibmTR/rj909.pdf</cdrom>
        <ee>db/labs/ibm/RJ909.html</ee>
    </article>
</dblp>

可以看到,上面的文件中title字段中,既有子元素,也有嵌套。所以,為了同時取到text和子元素中的text,要單獨地為取該字段的text寫一個函數,下面是兩個具體的實現。

 

5.1 v1.0

首先考慮的是遞歸讀取各個元素的text,然后將它們拼起來,代碼如下:

 1 from lxml import etree#paper2.py
 2 
 3 def node_text(node):
 4     result = node.text.strip() if node.text else ''
 5     for child in node:
 6         child_text = node_text(child)
 7         if child_text:
 8             result = result + ' %s' % child_text if result else child_text
 9     return result
10 
11 if __name__ == '__main__':
12     parser = etree.XMLParser()
13     root = etree.parse('paper.xml', parser).getroot()
14     for element in root:
15         category = element.tag
16         for attribute in element:
17             if attribute.tag == "title":
18                 print "title:", node_text(attribute)
19             else:
20                 print attribute.tag+":",attribute.text.strip()
21         print ""

 

運行結果如下:

 1 $ python paper2.py 
 2 author: E. F. Codd
 3 title: ES 2
 4 journal: IBM Research Report, San Jose, California
 5 volume: RJ909
 6 month: August
 7 year: 1971
 8 
 9 author: E. F. Codd
10 title: Entropy
11 journal: IBM Research Report, San Jose, California
12 volume: RJ909
13 month: August
14 year: 1971
15 cdrom: ibmTR/rj909.pdf
16 ee: db/labs/ibm/RJ909.html

顯然,這個方法只能夠取到各個子元素的text,然后將它們拼起來,因此,這並不是我們想要的。不知道當時怎么想的,我居然就直接這樣用了。現在看來too young, too simple, always naive。

 

5.2 v2.0

數據都上線快一年了,發現了這個問題。簡直不更sb了,這樣,我們就要重新寫上面去取得xml一個節點中所有text的函數(現在看來,當初將這一個功能寫成一個函數還算是比較科學的),下面是現在的方案:

 1 from lxml import etree#paper.py
 2 
 3 def node_text(node):
 4     result = ""
 5     for text in node.itertext():
 6         result = result + text
 7     return result
 8 
 9 if __name__ == '__main__':
10     parser = etree.XMLParser()
11     root = etree.parse('paper.xml', parser).getroot()
12     for element in root:
13         category = element.tag
14         for attribute in element:
15             if attribute.tag == "title":
16                 print "title:", node_text(attribute)
17             else:
18                 print attribute.tag+":",attribute.text.strip()
19         print ""

 

運行之后得到下面的結果:

 1 $ python paper.py 
 2 author: E. F. Codd
 3 title: ES2: A cloud data storage system for supporting both OLTP and OLAP.
 4 journal: IBM Research Report, San Jose, California
 5 volume: RJ909
 6 month: August
 7 year: 1971
 8 
 9 author: E. F. Codd
10 title: Entropy Best Paper Award 2013.
11 journal: IBM Research Report, San Jose, California
12 volume: RJ909
13 month: August
14 year: 1971
15 cdrom: ibmTR/rj909.pdf
16 ee: db/labs/ibm/RJ909.html

 

這樣,這個問題總算是解決了。下面的問題就是如何將線上的數據更改過來,當然,這又是另外的一個問題了。

 

 

 

0


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM