LXML操作

lxml takes all the pain out of XML.
Stephan Richter

    lxml是Python語言裏和XML以及HTML工作的功能最豐富和最容易使用的庫。lxml是爲libxml2和libxslt庫的一個Python化的綁定。它與衆不同的地方是它兼顧了這些庫的速度和功能完整性,以及純Python API的簡潔性,大部分與熟知的ElementTree API兼容但比之更優越。

安裝lxml:

要求:需要Python2.3或更後的版本

使用easy_install工具,以超級用戶或管理員的角色run下面的命令:

easy_install lxml

在windows下,最好指定版本號:easy_install lxml==2.2.6

使用lxml進行開發

lxml.etree指南

通常使用lxml.etree的方式

>>> from lxml import etree

 

Element類,一個Element是ElementTree API的主要容器類,大部分的XML tree功能都是通過這個類來訪問的。Elements可以非常容易地通過Element工廠方法來創建。

>>> root = etree.Element("root")

元素的XML tag名字是通過tag屬性來訪問的

>>> print root.tag # root

Elements是在XML樹狀結構中組織的,爲創建子元素並將它們加到父元素上,可以使用append()方法。

>>> root.append( etree.Element("child1") )

我們還有更高效的方法:SubElement工廠方法,它使用和Element工廠方法相同的參數,不過額外需要父節點作第一個參數:

>>> child2 = etree.SubElement(root, "child2")
>>> child3 = etree.SubElement(root, "child3")

可以使用tostring()方法來看得到的XML

>>> print etree.tostring(root, pretty_print=True)
<root>
<child1/>
<child2/>
<child3/>
</root>

元素是列表

>>> child = root[0]
>>> print child.tag
child1

>>> print len(root)
3

>>> root.index(root[1]) # lxml.etree only!
1

打印所有子節點:

>>> children = list(root)

>>> for child in root:

... print(child.tag)
child1
child2
child3

可以使用insert()方法插入新的子節點:

>>> root.insert(0, etree.Element("child0"))
刪除子節點:

>>> root[0] = root[-1] # this moves the element!
>>> for child in root:
... print(child.tag)
child3
child1
child2

如果想把一個元素拷貝到不同的地方,需要創建一個獨立的deep copy。

>>> from copy import deepcopy
>>> element = etree.Element("neu")
>>> element.append( deepcopy(root[1]) )
>>> print(element[0].tag)
child1
>>> print([ c.tag for c in root ])
[’child3’, ’child1’, ’child2’]

getparent()返回父節點:
>>> root is root[0].getparent() # lxml.etree only!
True

元素的兄弟或鄰居節點是通過next和previous屬性來訪問的
The siblings (or neighbours) of an element are accessed as next and previous elements:
>>> root[0] is root[1].getprevious() # lxml.etree only!
True
>>> root[1] is root[0].getnext() # lxml.etree only!
True

帶屬性的元素

XML元素支持屬性,可以用Element工廠方法直接創建。

>>> root = etree.Element("root", interesting="totally")
>>> etree.tostring(root)
b’<root interesting="totally"/>’

可以使用set和get方法訪問這些屬性:

>>> print root.get("interesting")
totally
>>> root.set("interesting", "somewhat")
>>> print root.get("interesting")
somewhat

也可以使用attrib性質的字典接口

>>> attributes = root.attrib
>>> print(attributes["interesting"])
somewhat
>>> print(attributes.get("hello"))
None
>>> attributes["hello"] = "Guten Tag"
>>> print(attributes.get("hello"))
Guten Tag
>>> print(root.get("hello"))
Guten Tag

 

元素可以包含文字:

>>> root = etree.Element("root")
>>> root.text = "TEXT"
>>> print(root.text)
TEXT
>>> etree.tostring(root)
’<root>TEXT</root>’

如果XML用在(X)HTML中,文本也可以在不同的元素中顯示:
<html><body>Hello<br/>World</body></html>
元素有tail屬性,它包含XML 樹中元素直接跟的,直到下個元素的文本。

>>> html = etree.Element("html")
>>> body = etree.SubElement(html, "body")
>>> body.text = "TEXT"
>>> etree.tostring(html)
b’<html><body>TEXT</body></html>’
>>> br = etree.SubElement(body, "br")
>>> etree.tostring(html)
b’<html><body>TEXT<br/></body></html>’
>>> br.tail = "TAIL"
>>> etree.tostring(html)
b’<html><body>TEXT<br/>TAIL</body></html>’

 

使用XPath查找文本

另一個抽取XML樹的文本內容是XPath,
>>> print(html.xpath("string()")) # lxml.etree only!
TEXTTAIL
>>> print(html.xpath("//text()")) # lxml.etree only!
[’TEXT’, ’TAIL’]

如果經常使用,可以包裝成一個方法:

>>> build_text_list = etree.XPath("//text()") # lxml.etree only!
>>> print(build_text_list(html))
[’TEXT’, ’TAIL’]

也可以通過getparent方法得到父節點

>>> texts = build_text_list(html)
>>> print(texts[0])
TEXT
>>> parent = texts[0].getparent()
>>> print(parent.tag)
body
>>> print(texts[1])
TAIL
>>> print(texts[1].getparent().tag)
br
You can also find out if it’s normal text content or tail text:
>>> print(texts[0].is_text)
True
>>> print(texts[1].is_text)
False
>>> print(texts[1].is_tail)
True

 

樹的迭代:

Elements提供一個樹的迭代器可以迭代訪問樹的元素。

>>> root = etree.Element("root")
>>> etree.SubElement(root, "child").text = "Child 1"
>>> etree.SubElement(root, "child").text = "Child 2"
>>> etree.SubElement(root, "another").text = "Child 3"
>>> print(etree.tostring(root, pretty_print=True))
<root>
<child>Child 1</child>
<child>Child 2</child>
<another>Child 3</another>
</root>

>>> for element in root.iter():
... print("%s - %s" % (element.tag, element.text))
root – None
child - Child 1
child - Child 2
another - Child 3

如果知道感興趣的tag,可以把tag的名字傳給iter方法,起到過濾作用。

>>> for element in root.iter("child"):
... print("%s - %s" % (element.tag, element.text))
child - Child 1
child - Child 2

默認情況下,迭代器得到一個樹的所有節點,包括ProcessingInstructions, Comments and Entity的實例。如果想確認只有Elements對象返回,可以把Element factory作爲參數傳入。

>>> root.append(etree.Entity("#234"))
>>> root.append(etree.Comment("some comment"))
>>> for element in root.iter():
... if isinstance(element.tag, basestring):
... print("%s - %s" % (element.tag, element.text))
... else:
... print("SPECIAL: %s - %s" % (element, element.text))
root - None
child - Child 1
child - Child 2
another - Child 3
SPECIAL: ê - ê
SPECIAL: <!--some comment--> - some comment


>>> for element in root.iter(tag=etree.Element):
... print("%s - %s" % (element.tag, element.text))
root - None
child - Child 1
child - Child 2
another - Child 3
>>> for element in root.iter(tag=etree.Entity):
... print(element.text)

 

序列化:

序列化通常使用tostring()方法來返回一個字符串,或者ElementTree.write()方法來寫入一個文件,一個類文件的對象,或者一個URL(通過FTP的PUT或者HTTP的POST)。二者都使用相同的關鍵字參數比如pretty_print來格式化輸出或者encoding來選擇一個特定的輸出編碼而不是簡單的ASCII。
>>> root = etree.XML("<root><a><b/></a></root>")
>>> etree.tostring(root)
’<root><a><b/></a></root>’

>>> print etree.tostring(root, xml_declaration=True)
<?xml version=’1.0’ encoding=’ASCII’?>
<root><a><b/></a></root>

>>> print etree.tostring(root, encoding="iso-8859-1")
<?xml version=’1.0’ encoding=’iso-8859-1’?>
<root><a><b/></a></root>

>>> print etree.tostring(root, pretty_print=True)
<root>
<a>
<b/>
</a>
</root>

Note that pretty printing appends a newline at the end.

注意pretty打印在末尾添加一個新行。

從lxml2.0起,serialisation可以做的不止XML序列化,可以序列化到HTML或者通過傳遞函數關鍵字來提取文本內容。

>>> root = etree.XML("<html><head/><body><p>Hello<br/>World</p></body></html>")
>>> etree.tostring(root) # default: method = ’xml’
’<html><head/><body><p>Hello<br/>World</p></body></html>’
>>> etree.tostring(root, method="xml") # same as above
’<html><head/><body><p>Hello<br/>World</p></body></html>’
>>> etree.tostring(root, method="html")
’<html><head></head><body><p>Hello<br>World</p></body></html>’

>>> print etree.tostring(root, method="html", pretty_print=True)
<html>
<head></head>
<body><p>Hello<br>World</p></body>
</html>

>>> etree.tostring(root, method="text")
b’HelloWorld’

對XML序列化而言,默認的文本編碼是ASCII

>>> br = root.find(".//br")
>>> br.tail = u"W/xf6rld"
>>> etree.tostring(root, method="text") # doctest: +ELLIPSIS
Traceback (most recent call last):
...
UnicodeEncodeError: ’ascii’ codec can’t encode character u’/xf6’ ...
>>>etree.tostring(root, method="text", encoding="UTF-8")
b’HelloW/xc3/xb6rld’

>>> etree.tostring(root, encoding=unicode, method="text")
u’HelloW/xf6rld’

ElementTree類:

一個ElementTree主要是圍繞在一個有根節點的樹的文檔包裝類。它提供了很多方法來解析,序列化以及一般的文檔處理。一個最大的區別是它作爲一個整體文檔來序列化。與之相對的是序列化成單個的元素。

>>> tree = etree.parse(StringIO("""/
 <?xml version="1.0"?>
 <!DOCTYPE root SYSTEM "test" [ <!ENTITY tasty "eggs"> ]>
 <root>
 <a>&tasty;</a>
 </root>
 """))
>>> print(tree.docinfo.doctype)
<!DOCTYPE root SYSTEM "test">

>>> # lxml 1.3.4 and later
>>> print(etree.tostring(tree))
<!DOCTYPE root SYSTEM "test" [
<!ENTITY tasty "eggs">
]>
<root>
<a>eggs</a>
</root>

>>> # lxml 1.3.4 and later
>>> print(etree.tostring(etree.ElementTree(tree.getroot())))
<!DOCTYPE root SYSTEM "test" [
<!ENTITY tasty "eggs">
]>
<root>
<a>eggs</a>
</root>

>>> # ElementTree and lxml <= 1.3.3
>>> print(etree.tostring(tree.getroot()))
<root>
<a>eggs</a>
</root>

從字符串和文件中解析:

fromstring()是解析字符串最容易的方法

>>> some_xml_data = "<root>data</root>"
>>> root = etree.fromstring(some_xml_data)
>>> print root.tag
root
>>> etree.tostring(root)
’<root>data</root>’

XML()方法和fromstring()方法類似,但它主要用來把XML文字寫入源文件。
>>> root = etree.XML("<root>data</root>")
>>> print root.tag
root
>>> etree.tostring(root)
’<root>data</root>’

parse()方法用來從文件或者類文件對象中解析
>>> some_file_like = StringIO.StringIO("<root>data</root>")
>>> tree = etree.parse(some_file_like)
>>> etree.tostring(tree)
’<root>data</root>’

注意parse()返回的是一個ElementTree對象,而不是字符串解析方法的Element對象。

>>> root = tree.getroot()
>>> print root.tag
root
>>> etree.tostring(root)
’<root>data</root>’

 

解析器對象:lxml.etree在默認情況下使用帶默認配置的標準解析器,如果想配置解析器,可以創建自己的實例。

>>> parser = etree.XMLParser(remove_blank_text=True) # lxml.etree only!

本例在解析的時候創建了一個移除tags之間的空的文本的解析器,這可以減少tree的大小以及避免不定的tail,如果你知道空白內容對你來說是沒有任何意義的話。

>>> root = etree.XML("<root> <a/> <b> </b> </root>", parser)
>>> etree.tostring(root)
b’<root><a/><b> </b></root>’
>>> for element in root.iter("*"):
... if element.text is not None and not element.text.strip():
... element.text = None
>>> etree.tostring(root)
b’<root><a/><b/></root>’

遞增解析:

lxml.etree提供了兩種方法來實現遞增的逐步的解析。一個方法是通過類文件對象,它重複調用read() 方法。
>>> class DataSource:
... data = [ b"<roo", b"t><", b"a/", b"><", b"/root>" ]
... def read(self, requested_size):
... try:
... return self.data.pop(0)
... except IndexError:
... return b’’
>>> tree = etree.parse(DataSource())
>>> etree.tostring(tree)
b’<root><a/></root>’

第二個方法是通過feed解析器接口,由feed(data) 和 close() 方法提供

>>> parser = etree.XMLParser()
>>> parser.feed("<roo")
>>> parser.feed("t><")
>>> parser.feed("a/")
>>> parser.feed("><")
>>> parser.feed("/root>")

>>> root = parser.close()
>>> etree.tostring(root)
’<root><a/></root>’

在調用close() 方法(或者當有exception發生的時候),可以通過調用feed() 方法重新使用parser:
>>> parser.feed("<root/>")
>>> root = parser.close()
>>> etree.tostring(root)
b’<root/>’


http://blog.sina.com.cn/s/blog_6f97247e01011afg.html


下面要用到的html必須是一個字符串,使用urlopen返回的對象的read方法即可獲得,read之後記得要close。

1. 

解析html並建立dom
import lxml.etree as etree
dom = etree.fromstring(html)
etree.tostring(dom)

如果用beautifulsoup的解析器,則:
import lxml.html.soupparser as soupparser
import lxml.etree as etree
dom = soupparser.fromstring(html)
etree.tostring(dom)

強烈建議使用soupparser,因爲其處理不規範的html的能力比etree強太多。
可以看一下我的另一篇文章,裏面有對比:幾段python解析HTML的代碼

2.  按照Dom訪問Element

子元素長度
>>> len(dom)
4

訪問子元素
>>> dom[0].tag
'html'

循環訪問
>>>for child indom:
...    print child.tag
html
script
script
<built-in function Comment>

查看節點索引
>>>body = dom[1]
>>>dom.index(body)
1
這裏要注意,索引包括了頁面所有的註釋,javascript,而不僅僅只是HTML標籤。

顯示節點
printetree.tostring(body)   #普通顯示
printetree.tostring(body,pretty_print=True)   #美化顯示

子節點獲取父節點
>>>body.getparent().tag
'html'

訪問所有子節點
>>> for ele indom.iter():
...    print ele.tag
...
html
body
div
div

3. 訪問節點屬性
>>>body.get('id')
'1'

也可以這樣
>>> attrs =body.attrib
>>>attrs.get('id')
'1'

4. 訪問Element的內容
>>> body.text
'abc'
>>> body.tail
text只是從本節點開始到第一個字節點結束;tail是從最後一個字節結束到本節點未知。

訪問本節點所有文本信息
>>>body.xpath('text()')
['abc', 'def', 'ghi']

訪問本節點和子節點所有文本信息
>>>body.xpath('//text()')
['abc', '123', 'def', '456', 'ghi']

貌似返回本文檔中所有文字信息
body.text_content()返回本節點所有文本信息。

5.Xpath的支持
所有的div元素
>>> for ele indom.xpath('//div'):
...    print ele.tag
...
div
div

id=“1”的元素
>>>dom.xpath('//*[@id="1"]')[0].tag
'body'

body下的第1個div

>>>dom.xpath('body/div[1]')[0].tag
'div'

參考:

lxml的官方文檔:http://codespeak.net/lxml/
HtmlParser的性能:http://blog.ianbicking.org/2008/03/30/python-html-parser-performance/
本段原文:http://blog.csdn.net/marising/article/details/5821090 略作修改
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章