Etree 支持从所有重要来源(即字符串、文件、 url (http / ftp)和类似文件的对象)以多种方式解析 XML。 主要的解析函数是 fromstring ()和 parse () ,它们都是以 source 作为第一个参数来调用的。 默认情况下,它们使用标准的解析器,但是您总是可以将不同的解析器作为第二个参数传递。
函数是解析字符串最简单的方法:
>>> some_xml_data = "data "
>>> root = etree.fromstring(some_xml_data)
>>> print(root.tag)
root
>>> etree.tostring(root)
b'data
函数的行为类似于 fromstring ()函数,但通常用于将 XML 文本直接写入源代码中
>>> root = etree.XML("data ")
>>> print(root.tag)
root
>>> etree.tostring(root)
b'data '
Html 文本还有一个相应的函数 HTML ()。
>>> root = etree.HTML("data
")
>>> etree.tostring(root)
b'data
'
作为这种类文件对象的示例,下面的代码使用 BytesIO 类从字符串而不是外部文件进行读取。 这个类来自 Python 2.6及更高版本中的 io 模块。 在较早的 Python 版本中,必须使用 StringIO 模块中的 StringIO 类。 但是,在现实生活中,您显然应该避免一起执行这些操作,而是使用上面提到的字符串解析函数。
>>> from io import BytesIO
>>> some_file_or_file_like_object = BytesIO(b"data ")
>>> tree = etree.parse(some_file_or_file_like_object)
>>> etree.tostring(tree)
b'data '
注意 parse ()返回一个 ElementTree 对象,而不是作为字符串解析器函数的 Element 对象:
>>> root = tree.getroot()
>>> print(root.tag)
root
>>> etree.tostring(root)
b'data '
这种差异背后的原因是 parse ()从文件返回一个完整的文档,而字符串解析函数通常用于解析 XML 片段。
函数支持以下任何来源:
注意,传递文件名或 URL 通常比传递打开的文件或类似文件的对象更快。 然而,libxml2中的 HTTP / ftp 客户端相当简单,因此像 HTTP 身份验证这样的事情需要一个专用的 URL 请求库,例如 urllib2或请求。 这些库通常为结果提供一个类似于文件的对象,当响应流输入时可以从中进行分析。
默认情况下,lxml.etree 使用默认设置的标准解析器。 如果你想配置解析器,你可以创建一个新的实例:
>>> parser = etree.XMLParser(remove_blank_text=True) # lxml.etree only!
这将创建一个解析器,在解析时移除标记之间的空文本,这样可以减小树的大小,并避免在知道纯空格内容对数据没有意义时悬空尾文本。 举个例子:
>>> root = etree.XML(" ", parser)
>>> etree.tostring(root)
b' '
注意,b 标记中的空白内容没有被删除,因为 leaf 元素中的内容往往是数据内容(即使是空白)。 你可以通过遍历这棵树来轻松地移除它:
>>> for element in root.iter("*"):
... if element.text is not None and not element.text.strip():
... element.text = None
>>> etree.tostring(root)
b'
查看 help (etree.XMLParser)以了解可用的解析器选项。
Etree 提供了两种逐步增量解析的方法。 一种是通过类似于文件的对象,其中它反复调用 read ()方法。 这最好用于数据从诸如 urllib 这样的源或任何其他类似文件的对象(可以根据请求提供数据)到达的地方。 注意,在这种情况下,解析器会阻塞并等待,直到数据可用:
>>> class DataSource:
... data = [ b"<", 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' '
第二种方法是通过 feed 解析器接口,由 feed (data)和 close ()方法提供:
>>> parser = etree.XMLParser()
>>> parser.feed(">> parser.feed("t><")
>>> parser.feed("a/")
>>> parser.feed("><")
>>> parser.feed("/root>")
>>> root = parser.close()
>>> etree.tostring(root)
b' '
在这里,您可以随时中断解析过程,稍后通过调用 feed ()方法继续解析过程。 如果你想避免阻塞对解析器的调用,比如像 Twisted 这样的框架,或者当数据缓慢或者大块地进入时,你想在等待下一个数据块的同时做其他事情,这就很方便了。
在调用 close ()方法之后(或者当解析器引发异常时) ,您可以通过再次调用解析器的 feed ()方法来重用解析器:
>>> parser.feed(" ")
>>> root = parser.close()
>>> etree.tostring(root)
b' '
有时候,您只需要从一个文档中获得一小部分内容,因此将整个树解析到内存中、遍历和删除它的开销可能太大。 Etree 通过两个事件驱动的解析器接口支持这个用例,一个在构建树(iterparse)时生成解析器事件,另一个根本不构建树,而是以类似 sax 的方式对目标对象调用反馈方法。
下面是一个简单的 iterparse ()示例:
>>> some_file_like = BytesIO(b"data ")
>>> 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 ()只在解析一个元素时生成事件,但是你可以通过 events 关键字参数来控制:
>>> some_file_like = BytesIO(b"data ")
>>> 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
请注意,接收开始事件时,Element 的文本、尾部和子元素不一定存在。 只有结束事件才能保证元素被完全解析。
它还允许你。 清除()或修改元素的内容以节省内存。 因此,如果您解析一个大的树并希望保持内存使用量较小,那么您应该清理树中不再需要的部分。 保持尾部为真的参数。 Clear ()确保当前元素后面的(tail)文本内容不会被修改。 对于解析器可能还没有完全读完的任何内容,最好不要修改。
>>> some_file_like = BytesIO(
... b"data ")
>>> 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(keep_tail=True)
data
** cleaning up the subtree
None
** cleaning up the subtree
Iterparse ()的一个非常重要的用例是解析生成的大型 XML 文件,例如数据库转储。 通常,这些 XML 格式只有一个主数据项元素,该元素直接挂在根节点的下方,并且要重复数千次。 在这种情况下,最好的做法是让 lxml.etree 构建树,并且只截取这一个 Element,使用普通的树 API 进行数据提取。
>>> xml_file = BytesIO(b'''\
...
... ABCabc
... MORE DATAmore data
... XYZxyz
... ''')
>>> for _, element in etree.iterparse(xml_file, tag='a'):
... print('%s -- %s' % (element.findtext('b'), element[1].text))
... element.clear(keep_tail=True)
ABC -- abc
MORE DATA -- more data
XYZ -- xyz
如果出于某种原因,根本不需要构建树,那么可以使用 lxml.etree 的目标解析器接口。 它通过调用目标对象的方法来创建类似 sax 的事件。 通过实现部分或全部这些方法,您可以控制生成哪些事件:
>>> 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(' ', 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
您可以随心所欲地重用解析器及其目标,因此应该注意。 Close ()方法实际上将目标重置为可用的状态(在出错的情况下也是如此) .
>>> events = etree.fromstring(' ', parser)
>>> print(parser_target.close_count)
2
>>> events = etree.fromstring(' ', parser)
>>> print(parser_target.close_count)
3
>>> events = etree.fromstring(' ', parser)
>>> print(parser_target.close_count)
4
>>> 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