python之lxml快速上手_ElementTree(三)

本文介绍了Python的lxml库解析XML的方式,包括ElementTree类、从字符串或文件解析XML的方法,如fromstring()、XML()和parse()。解析器对象支持增量解析和事件驱动解析,适用于不同场景。增量解析可用于处理大型XML文件,而事件驱动解析则能有效地节省内存,只关注所需的部分数据。
摘要由CSDN通过智能技术生成

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, data
end, 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, None
start,    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 subtree
None
** 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 -- abc
MORE DATA -- more data
XYZ -- 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值