python之lxml快速上手_ElementTree(三)

来源:互联网 发布:java语言培训学校 编辑:程序博客网 时间:2024/06/03 23:39

The ElementTree class

ElementTree主要是作为一个包含根节点的“树(tree)”的文档包裹(document wrapper)。它提供了多个成对的序列化和常规文档处理方法:

>>> root = etree.XML('''\... <?xml version="1.0"?>... <!DOCTYPE root SYSTEM "test" [ <!ENTITY tasty "parsnips"> ]>... <root>...   <a>&tasty;</a>... </root>... ''')>>> tree = etree.ElementTree(root)>>> print(tree.docinfo.xml_version)1.0>>> print(tree.docinfo.doctype)<!DOCTYPE root SYSTEM "test">>>> tree.docinfo.public_id = '-//W3C//DTD XHTML 1.0 Transitional//EN'>>> tree.docinfo.system_url = 'file://local.dtd'>>> print(tree.docinfo.doctype)<!DOCTYPE root PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "file://local.dtd">

你调用parse()方法(只需传递参数:文件(files)、或类文件对象(file-like object))所获取的结果也是一个ElementTree
很重要的一个的不同是,ElementTree是作为一个完整的文档来序列化的。这其中包括processing instructions(处理指令)、comments(注释)、以及DOCTYPE (文档声明)和其他DTD的内容。

>>> print(etree.tostring(tree))  # lxml 1.3.4 and later<!DOCTYPE root PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "file://local.dtd" [<!ENTITY tasty "parsnips">]><root>  <a>parsnips</a></root>

在最初的xml.etree.ElementTree实现中,直到lxml 1.3.3,当你仅仅序列化根节点元素时,输出看起来是一样的:

>>> print(etree.tostring(tree.getroot()))<root>  <a>parsnips</a></root>

序列化行为在lxml 1.3.4中已经发生了改变。之前,“树”的序列化是不包含DTD内容的,这也使得lxml在输入输出循环中丢失了DTD信息。

从字符或文件中解析(Parsing from strings and files)

lxml.etree支持多种方式解析XML,包括所有重要的数据源,命名字符、文件、URLs(http/ftp)和类文件对象。主要的解析方法是fromstring()parse(),两者都接受“数据源”作为第一个参数。默认情况下,它使用标准的解析器,但你随时都可以传递一个不同的解析器(作为第二个参数)。

The fromstring() function

fromstring()是从字符串解析的最简单办法:

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

The XML() function

方法XML()行为上类似于fromstring(),但通常用作将XML字面量正确写入“数据源”(如文件):

>>> root = etree.XML("<root>data</root>")>>> print(root.tag)root>>> etree.tostring(root)b'<root>data</root>'

当然,针对html字面量,也存在对应的HTML()方法。

The parse() function

parse()通常用作从文件或类文件对象中解析。
一个使用类文件对象的例子,下面的代码使用BytesIO类从字符中读取数据,而不是外部文件。在python2.6及之后的版本中,你可以在io模块找到这个类。如果是更早的版本,你需要使用StringIO模块的StringIO类。然而,在实际工作中,你应该设法避免这样做,而是像上面一样,使用字符解析方法。

>>> some_file_like_object = BytesIO("<root>data</root>")>>> tree = etree.parse(some_file_like_object)>>> etree.tostring(tree)b'<root>data</root>'

注意,parse()方法返回的是ElementTree对象,而不是像字符解析方法一样返回Element对象:

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

导致上述不同的背后原因是,parse()从文件中返回一个完整的文档对象,而字符解析方法(string functions)一般用来解析XML片段

parse()方法支持以下任意一种“数据源”:
1. 一个打开的文件对象(请确保以二进制方法打开它
2. 一个类文件对象,它需包含一个read(byte_count)方法,每次调用返回一个字节串
3. 一个文件名字符串
4. 一个HTTP或FTP URL字符串

注:

  • 列表内容通常情况下,直接传递文件名、或URL字符串,而不是打开的文件、或类文件对象,作为参数会更加快。
  • 然而,在libxml2中,HTTP/FTP客户端的实现是相当简单的,所以像HTTP授权要依赖专用的URL请求库(urllib2 或 request)。当响应对象是流时,这些库通常会提供一个类文件对象作为结果。

解析器对象(Parser objects)

一般情况下,lxml.etree使用带默认设置的标准解析器(parser)。如果你想配置它,你可以创建一个自己的实例:

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

上面的例子创建了一个解析器,它会移除标签之间的空白文本,这将有效减少文档“树”的大小;同时,如果你确认空白字符对你的数据来说是无意义的,这也可以避免不确定的“尾巴文本”(tail text)的影响。下面是一个例子:

>>> root = etree.XML("<root>  <a/>   <b>  </b>     </root>", parser)>>> etree.tostring(root)b'<root><a/><b>  </b></root>'

注意,在标签b之间的空白字符并没有被移除,就像叶子节点内的内容通常被认为数据(data content),即使它是空白字符。然而,你还是可以轻松地通过遍历“树”移除它:

>>> 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>'

增量解析(Incremental parsing)

lxml.etree提供两种方式用于增量地、一步一步式地解析。其中一种是通过类文件对象,它的read()方法将被重复调用。当数据来自像urllib、或者其他类文件对象时,这也是最好的方式。注意,在下面的例子中,解析器将阻塞,直到数据可用:

>>> 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(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)b'<root><a/></root>'

在上面的例子中,你可以在任意时刻中断解析过程,并在之后通过调用feed()恢复。这也带来一个方便,那就是当你想避免对解析器的阻塞调用,比如在框架Twisted中。或者任何时候,当数据来得很慢,或在等待另一个数据块时,你还想做些其他事情。

在调用close()之后(或者当解析器抛出一个异常时),你可以通过调用feed()方法,再次重用该解析器:

>>> parser.feed("<root/>")>>> root = parser.close()>>> etree.tostring(root)b'<root/>'

事件驱动解析(Event-driven parsing)

有的时候,一个文档中你所需要的不过是一小部分,它位于“树”的某个深处、角落。因此解析整个“树”,把它装进内存,遍历它和丢弃它,都显得有些“过头”了。lxml.etree通过两个事件驱动的接口,来支持这种使用案例。一个(接口)负责在构建树时,生成解析器对象,另一个(接口)则完全不构建树,作为替换,它在目标对象调用feedback方法。

>>> some_file_like = BytesIO("<root><a>data</a></root>")>>> for event, element in etree.iterparse(some_file_like):...     print("%s, %4s, %s" % (event, element.tag, element.text))end,    a, dataend, root, None

默认地,iterparse()方法仅在完成元素(Element)的解析后,才生成事件。但是,你可以通过events关键字参数来控制它:

>>> some_file_like = BytesIO("<root><a>data</a></root>")>>> for event, element in etree.iterparse(some_file_like,...                                       events=("start", "end")):...     print("%5s, %4s, %s" % (event, element.tag, element.text))start, root, Nonestart,    a, data  end,    a, data  end, root, None

注意,当你收到start事件时,文本、tail、以及元素的孩子节点 并不一定会被呈现只有end事件可以保证元素已经被完整地解析。

它同样允许你使用.clear()方法,或者修改元素的内容,然后保存到内存中。因此,如果你正在解析一个巨大的“树”,并且你想让内存占用保持在较低的水平,你应该及时清理“树”中你不再使用的部分

>>> some_file_like = BytesIO(...     "<root><a><b>data</b></a><a><b/></a></root>")>>> for event, element in etree.iterparse(some_file_like):...     if element.tag == 'b':...         print(element.text)...     elif element.tag == 'a':...         print("** cleaning up the subtree")...         element.clear()data** cleaning up the subtreeNone** cleaning up the subtree

iterparse()来说,一个很重要的使用案例就是,解析一个巨大的XML文件,或者是数据库存储文件。大部分情况下,这些XML的格式仅仅有一个主要的数据节点,它们挂载于根节点上,并重复成千上万次。在这种情况下,最佳实践是让lxml.etree执行“树”的构建,仅在这些关键元素上中断,并使用常规的tree API来抽取数据

>>> xml_file = BytesIO('''\... <root>...   <a><b>ABC</b><c>abc</c></a>...   <a><b>MORE DATA</b><c>more data</c></a>...   <a><b>XYZ</b><c>xyz</c></a>... </root>''')>>> for _, element in etree.iterparse(xml_file, tag='a'):...     print('%s -- %s' % (element.findtext('b'), element[1].text))...     element.clear()ABC -- abcMORE DATA -- more dataXYZ -- xyz

如果,出于某些原因,你一点也不希望构建“树”,你可以使用lxml.etree的目标解析器接口。它通过调用目标对象的方法创建SAX-like事件。通过实现一个或所有这些方法,你可以控制哪些事件被生成:

>>> class ParserTarget:...     events = []...     close_count = 0...     def start(self, tag, attrib):...         self.events.append(("start", tag, attrib))...     def close(self):...         events, self.events = self.events, []...         self.close_count += 1...         return events>>> parser_target = ParserTarget()>>> parser = etree.XMLParser(target=parser_target)>>> events = etree.fromstring('<root test="true"/>', parser)>>> print(parser_target.close_count)1>>> for event in events:...     print('event: %s - tag: %s' % (event[0], event[1]))...     for attr, value in event[2].items():...         print(' * %s = %s' % (attr, value))event: start - tag: root * test = true
0 0
原创粉丝点击