作者: 出处:
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/>’