lxml官方入门教程(The lxml.etree Tutorial)翻译
lxml官方入门教程(The lxml.etree Tutorial)翻译
说明:
- 首次发表日期:2024-09-05
- 官方教程链接: https://lxml.de/tutorial.html
- 使用KIMI和豆包机翻
- 水平有限,如有错误请不吝指出
这是一个关于使用lxml.etree处理XML的教程。它简要概述了ElementTree API的主要概念,以及一些简单的增强功能,这些功能可以让您作为程序员的生活更轻松。
有关API的完整参考,请查看生成的API文档。
导入lxml.etree的常见方式如下:
from lxml import etree
如果你的代码仅使用ElementTree API,并且不依赖于lxml.etree
任何的特有功能,您还可以使用以下导入链来回退到Python标准库中的ElementTree:
try:from lxml import etreeprint("running with lxml.etree")
except ImportError:import xml.etree.ElementTree as etreeprint("running with Python's xml.etree.ElementTree")
为了帮助编写可移植代码,本教程在示例中明确指出了所呈现API的哪一部分是lxml.etree对原始ElementTree API的扩展。
The Element class
元素(Element)是ElementTree API的主要容器对象。大部分XML树功能都是通过这个类访问的。元素(Elements)可以通过Element
factory轻松创建:
root = etree.Element("root")
元素的XML标签名称可以通过tag
属性访问:
print(root.tag)
元素在XML树结构中组织。要创建子元素并将它们添加到父元素,您可以使用append()
方法:
root.append(etree.Element("child"))
然而,这种情况非常常见,因此有一个更简短且效率更高的方法来实现这一点:SubElement
工厂。它接受与Element
工厂相同的参数,但另外需要将父元素作为第一个参数:
child2 = etree.SubElement(root, "child2")
child3 = etree.SubElement(root, "child3")
要确认这确实是XML,您可以序列化您创建的树:
etree.tostring(root)
b'<root><child1/><child2/><child3/></root>'
我们将创建一个小型辅助函数,为我们美观地打印XML:
def prettyprint(element, **kwargs):xml = etree.tostring(element, pretty_print=True, **kwargs)print(xml.decode(), end='')
prettyprint(root)
<root><child1/><child2/><child3/>
</root>
Elements are lists
为了便于直接访问这些子元素,元素尽可能地模仿了普通Python列表的行为:
>>> 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>>> root.insert(0, etree.Element("child0"))
>>> start = root[:1]
>>> end = root[-1:]>>> print(start[0].tag)
child0
>>> print(end[0].tag)
child3
在ElementTree 1.3和lxml 2.0之前,您还可以检查元素的真值,以查看它是否有子元素,即查看子元素列表是否为空:
if root: # this no longer works!print("The root element has children")
这种做法不再被支持,因为人们倾向于期望“某物”(something)evaluates为True
,并期望元素(Elements)是“某物”,无论它们是否有子元素。因此,许多用户发现,任何元素在像上面的if语句中评估为False是令人惊讶的。相反,使用len(element)
,这既更明确,也更少出错。
print(etree.iselement(root)) # test if it's some kind of Element
True
if len(root): # test if it has childrenprint("The root element has children")
在另一个重要的场景下,lxml中元素(从2.0及以后版本)的行为与列表(lists)的行为以及原始ElementTree(1.3版本之前或Python 2.7/3.2之前)的行为有所不同:
for child in root:print(child.tag)
child0
child1
child2
child3
root[0] = root[-1] # this moves the element in lxml.etree!
for child in root:print(child.tag)
child3
child1
child2
在这个例子中,最后一个元素被移动到了一个不同的位置,而不是被复制,也就是说,当它被放到一个不同的位置时,它会自动从之前的位置被移除。在列表中,对象可以同时出现在多个位置,上述赋值操作只会将项目引用复制到第一个位置,因此两者包含完全相同的项目:
>>> l = [0, 1, 2, 3]
>>> l[0] = l[-1]
>>> l
[3, 1, 2, 3]
请注意,在原始的ElementTree中,单个元素对象可以位于任何数量的树中的任何位置,这允许进行与列表相同的复制操作。明显的不足是,对这样的元素进行的修改将应用于它在树中出现的所有位置,这可能是也可能不是预期的。
备注:在lxml中,上述赋值操作会移动元素,与lists和原始的ElementTree中不同。
这种差异的好处是,在lxml.etree中的一个元素总是恰好有一个父元素,这可以通过getparent()方法查询。这在原始的ElementTree中是不支持的。
root is root[0].getparent() # lxml.etree only!
如果您想将元素复制到lxml.etree中的不同位置,请考虑使用Python标准库中的copy模块创建一个独立的深拷贝:
from copy import deepcopyelement = etree.Element("neu")
element.append(deepcopy(root[1]))
print(element[0].tag)
# child1
print([c.tag for c in root])
# ['child3', 'child1', 'child2']
元素的兄弟(或邻居)作为下一个和上一个元素进行访问:
root[0] is root[1].preprevious() # lxml.etree only!
# True
root[1] is root[0].getnext() # lxml.etree only!
Elements carry attributes as a dict
XML元素支持属性(attributes)。您可以直接在Element工厂中创建它们:
root = etree.Element("root", interesting="totally")
etree.tostring(root)
# b'<root interesting="totally"/>'
属性只是无序的名称-值对,因此通过元素的类似字典的接口处理它们非常方便:
print(root.get("interesting"))
# totally
print(root.get("hello"))
# None
root.set("hello", "Huhu")
print(root.get("hello"))
# Huhu
etree.tostring(root)
# b'<root interesting="totally" hello="Huhu"/>'
sorted(root.keys())
# ['hello', 'interesting']
for name, value in sorted(root.items()):print('%s = %r' % (name, value))
# hello = 'Huhu'
# interesting = 'totally'
在您想要进行项目查找或有其他原因需要获取一个“真实”的类似字典的对象的情况下,例如为了传递它,您可以使用attrib
属性:
>>> attributes = root.attrib>>> print(attributes["interesting"])
totally
>>> print(attributes.get("no-such-attribute"))
None>>> attributes["hello"] = "Guten Tag"
>>> print(attributes["hello"])
Guten Tag
>>> print(root.get("hello"))
Guten Tag
请注意,attrib是一个由元素本身支持(backed)的类似字典的对象。这意味着对元素的任何更改都会反映在attrib中,反之亦然。这也意味着只要XML树有一个元素的attrib在使用中,XML树就会在内存中保持活动状态。要获取一个不依赖于XML树的属性的独立快照,将其复制到一个字典中:
d = dict(root.attrib)
sorted(d.items())
# ('hello', 'Guten Tag'), ('interesting', 'totally')]
Elements contain text
元素可以包含文本:
root = etree.Element("root")
root.text = "TEXT"print(root.text)
# TEXTetree.tostring(root)
# b'<root>TEXT</root>'
在许多XML文档(以数据为中心的文档)中,这是唯一可以找到文本的地方。它被树层次结构最底层的一个叶子标签封装。
然而,如果XML用于标记文本文档,如(X)HTML,文本也可以出现在不同元素之间,就在树的中间:
<html><body>Hello<br/>World</body></html>
在这里,<br/>
标签被文本包围。这通常被称为文档样式或混合内容XML。元素通过它们的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>'
.text
和 .tail
这两个属性足以表示XML文档中的任何文本内容。这样,ElementTree API 除了 “Element” 类之外不需要任何特殊的文本节点,这些节点往往经常会常造成阻碍(正如你可能从传统 DOM API 中了解到的那样)。
然而,也有一些情况下,尾随(tail)文本也会碍事。例如,当您序列化树中的一个元素时,您并不总是希望其尾随文本出现在结果中(尽管您仍然希望包含其子元素的尾部文本)。为此,tostring()
函数接受关键字参数 with_tail
:
>>> etree.tostring(br)
b'<br/>TAIL'
>>> etree.tostring(br, with_tail=False) # lxml.etree only!
b'<br/>'
如果你只想读取文本,即不包含任何中间标签,你必须以正确的顺序递归地连接所有的文本和尾部属性。同样,“tostring()
” 函数可以提供帮助,这次使用 “method
” 关键字。
>>> etree.tostring(html, method="text")
b'TEXTTAIL'
Using XPath to find text
提取树文本内容的另一种方式是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']
请注意,XPath返回的字符串结果是一个特殊的“智能”对象,它了解其来源。您可以通过其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
您还可以找出它是普通文本内容还是尾随文本:
>>> print(texts[0].is_text)
True
>>> print(texts[1].is_text)
False
>>> print(texts[1].is_tail)
True
虽然这对text()函数的结果有效,但lxml不会告诉您由XPath函数string()或concat()构造的字符串值的来源:
>>> stringify = etree.XPath("string()")
>>> print(stringify(html))
TEXTTAIL
>>> print(stringify(html).getparent())
None
Tree iteration
对于上述这样的问题,当你想要递归地遍历树并对其元素进行一些操作时,树迭代(tree iteration)是一个非常方便的解决方案。元素(Elements)为此提供了一个树迭代器。它按照文档顺序生成元素,即与将树序列化为XML时其标签出现的顺序一致。
>>> 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">>> prettyprint(root)
<root><child>Child 1</child><child>Child 2</child><another>Child 3</another>
</root>>>> for element in root.iter():
... print(f"{element.tag} - {element.text}")
root - None
child - Child 1
child - Child 2
another - Child 3
如果您知道您只对单个标签感兴趣,可以将标签名称传递给iter()
,让它为您过滤。从lxml 3.0开始,您还可以传递多个标签,在迭代期间拦截多个标签。
>>> for element in root.iter("child"):
... print(f"{element.tag} - {element.text}")
child - Child 1
child - Child 2>>> for element in root.iter("another", "child"):
... print(f"{element.tag} - {element.text}")
child - Child 1
child - Child 2
another - Child 3
默认情况下,迭代会生成树中的所有节点,包括ProcessingInstructions、Comments和Entity实例。如果您想确保只返回Element对象,可以将Element工厂作为标签参数传递:
>>> root.append(etree.Entity("#234"))
>>> root.append(etree.Comment("some comment"))>>> for element in root.iter():
... if isinstance(element.tag, str):
... print(f"{element.tag} - {element.text}")
... else:
... print(f"SPECIAL: {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(f"{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)
ê
请注意,传递通配符“*
”作为标签名也将生成所有的Element
节点(并且只有元素节点)。
for element in root.iter(tag="*"):if isinstance(element.tag, str):print(f"element.tag - {element.text}")else:print(f"SPECIAL: {element} - {element.text}")
element.tag - None
element.tag - Child 1
element.tag - Child 2
element.tag - Child 3
在lxml.etree
中,elements
为树中的所有方向提供了进一步的迭代器:子节点(iterchildren()
)、父节点(或者更确切地说是祖先节点)(iterancestors()
)和兄弟节点(itersiblings()
)。
Serialisation
序列化通常使用tostring()
函数返回字符串,或者使用ElementTree.write()
方法写入文件、类文件对象(file-like object)或URL(通过FTP PUT或HTTP POST)。这两个调用都接受相同的关键字参数,如pretty_print
用于格式化输出,或者encoding
用于选择除纯ASCII之外的特定输出编码:
>>> root = etree.XML('<root><a><b/></a></root>')>>> etree.tostring(root)
b'<root><a><b/></a></root>'>>> xml_string = etree.tostring(root, xml_declaration=True)
>>> print(xml_string.decode(), end='')
<?xml version='1.0' encoding='ASCII'?>
<root><a><b/></a></root>>>> latin1_bytesstring = etree.tostring(root, encoding='iso8859-1')
>>> print(latin1_bytesstring.decode('iso8859-1'), end='')
<?xml version='1.0' encoding='iso8859-1'?>
<root><a><b/></a></root>>>> print(etree.tostring(root, pretty_print=True).decode(), end='')
<root><a><b/></a>
</root>
请注意,美观打印(pretty_print
)会在末尾附加一个新行。因此,我们在这里使用end=''
选项,以防止print()
函数添加另一个换行符。
为了在序列化之前对美观打印(pretty_print
)进行更细粒度的控制,您可以使用indent()
函数(在lxml 4.5中添加)在树中添加空白缩进:
>>> root = etree.XML('<root><a><b/>\n</a></root>')
>>> print(etree.tostring(root).decode())
<root><a><b/>
</a></root>>>> etree.indent(root)
>>> print(etree.tostring(root).decode())
<root><a><b/></a>
</root>>>> root.text
'\n '
>>> root[0].text
'\n '>>> etree.indent(root, space=" ")
>>> print(etree.tostring(root).decode())
<root><a><b/></a>
</root>>>> etree.indent(root, space="\t")
>>> etree.tostring(root)
b'<root>\n\t<a>\n\t\t<b/>\n\t</a>\n</root>'
在lxml 2.0及更高版本以及xml.etree中,序列化函数不仅可以进行XML序列化。您可以通过传递method关键字来序列化为HTML或提取文本内容:
>>> root = etree.XML(
... '<html><head/><body><p>Hello<br/>World</p></body></html>')>>> etree.tostring(root) # default: method = 'xml'
b'<html><head/><body><p>Hello<br/>World</p></body></html>'>>> etree.tostring(root, method='xml') # same as above
b'<html><head/><body><p>Hello<br/>World</p></body></html>'>>> etree.tostring(root, method='html')
b'<html><head></head><body><p>Hello<br>World</p></body></html>'>>> prettyprint(root, method='html')
<html>
<head></head>
<body><p>Hello<br>World</p></body>
</html>>>> etree.tostring(root, method='text')
b'HelloWorld'
与XML序列化一样,纯文本序列化的默认编码是ASCII:
>>> br = next(root.iter('br')) # get first result of iteration
>>> br.tail = 'Wörld'>>> etree.tostring(root, method='text') # doctest: +ELLIPSIS
Traceback (most recent call last):...
UnicodeEncodeError: 'ascii' codec can't encode character '\xf6' ...>>> etree.tostring(root, method='text', encoding="UTF-8")
b'HelloW\xc3\xb6rld'
在这里,将序列化目标设为Python文本字符串(text string)而不是字节字符串(byte string)可能会很方便。只需将’unicode’作为编码传递:
>>> etree.tostring(root, encoding='unicode', method='text')
'HelloWörld'
>>> etree.tostring(root, encoding='unicode')
'<html><head/><body><p>Hello<br/>Wörld</p></body></html>'
W3C有一篇关于Unicode字符集和字符编码的好文章: https://www.w3.org/International/tutorials/tutorial-char-enc/
The ElementTree class
ElementTree
主要是一个围绕具有根节点的树的文档包装器。它提供了一些用于序列化和一般文档处理的方法。
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()
函数解析文件或类文件对象(file-like object)时(见下文的解析部分),您得到的也是一个ElementTree。
一个重要的不同之处在于,ElementTree
类序列化为一个完整的文档,而不是单个Element
。这包括顶级(top-level)处理指令和注释,以及文档中的DOCTYPE和其他DTD内容:
>>> prettyprint(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实现以及直到1.3.3版本的lxml中,输出看起来与仅序列化根元素时相同:
>>> prettyprint(tree.getroot())
<root><a>parsnips</a>
</root>
在lxml 1.3.4中,这种序列化行为发生了变化。以前,树在没有DTD内容的情况下被序列化,这使得lxml在输入输出循环中丢失了DTD信息。
Parsing from strings and files
lxml.etree支持以多种方式解析XML,并且可以从所有重要的源解析,即字符串、文件、URL(http/ftp)和类文件对象(file-like object)。主要的解析函数是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>'
print(type(root))
# <class 'lxml.etree._Element'>
The XML() function
XML()
函数的行为类似于fromstring()
函数,但通常用于将XML字面量直接写入源代码:
>>> root = etree.XML("<root>data</root>")
>>> print(root.tag)
root
>>> etree.tostring(root)
b'<root>data</root>'
print(type(root))
# <class 'lxml.etree._Element'>
还有一个相应的函数HTML()
用于HTML字面量。
>>> root = etree.HTML("<p>data</p>")
>>> etree.tostring(root)
b'<html><body><p>data</p></body></html>'
print(type(root))
# <class 'lxml.etree._Element'>
The parse() function
parse()
函数用于从文件和类文件对象(file-like object)解析。
作为此类类文件对象的一个例子,以下代码使用BytesIO类从字符串而不是外部文件中读取。然而,在现实生活中,您显然会避免这样做,而是使用像上面提到的fromstring()
这样的字符串解析函数。
>>> from io import BytesIO
>>> some_file_or_file_like_object = BytesIO(b"<root>data</root>")>>> tree = etree.parse(some_file_or_file_like_object)>>> etree.tostring(tree)
b'<root>data</root>'
请注意,parse()
返回一个ElementTree
对象,而不是像字符串解析函数那样的Element
对象:
print(type(tree))
# <class 'lxml.etree._ElementTree'>
>>> root = tree.getroot()
>>> print(root.tag)
root
>>> etree.tostring(root)
b'<root>data</root>'
这种差异背后的原因是parse()从文件返回一个完整的文档,而字符串解析函数通常用于解析XML片段。
parse()
函数支持以下任何来源:
- 一个打开的文件对象(确保以二进制模式打开)
- 一个具有
.read(byte_count)
方法的类文件对象,每次调用都返回一个字节字符串 - 一个文件名字符串
- 一个HTTP或FTP URL字符串
请注意,传递文件名或URL通常比传递打开的文件或类文件对象更快。然而,libxml2中的HTTP/FTP客户端相当简单,因此像HTTP认证这样的事情需要一个专门的URL请求库,例如urllib2或requests。这些库通常提供一个类文件对象作为结果,您可以在响应流式传输时从中解析。
Parser objects
默认情况下,lxml.etree使用具有默认设置的标准解析器。如果您想配置解析器,可以创建一个新实例:
parser = etree.XMLParser(remove_blank_text=True) # lxml.etree only!
这创建了一个解析器,在解析时删除标签之间的空白文本,这可以减少树的大小,并避免在您知道空白内容对您的数据没有意义时出现悬挂的尾随文本。例如:
>>> root = etree.XML("<root> <a/> <b> </b> </root>", parser)>>> etree.tostring(root)
b'<root><a/><b> </b></root>'
请注意,<b>
标签内的空白内容没有被移除,因为叶元素中的内容往往是数据内容(即使是空白的)。您可以通过遍历树来轻松地移除它:
for element in root.iter("*"):if element.text is not None and not element.text.strip():element.text = Noneetree.tostring(root)
b'<root><a/><b/></root>'
请参阅 help(etree.XMLParser)
以了解有关可用解析器选项的信息。
help(etree.XMLParser)
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>'
第二种方法是通过parser提供的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通过两种事件驱动的解析器接口支持这种用例,一种在构建树时生成解析器事件(iterparse
),另一种根本不构建树,而是以类似SAX的方式在目标对象上调用反馈方法。
这里有一个简单的iterparse()
示例:
some_file_like = BytesIO(b"<root><a>data</a></root>")for event, element in etree.iterparse(some_file_like):print(f"{event}, {element.tag:>4}, {element.text}")
end, a, data
end, root, None
默认情况下,iterparse()只在解析完一个元素时才生成一个事件,但您可以通过events关键字参数控制这一点:
some_file_like = BytesIO(b"<root><a>data</a></root>")for event, element in etree.iterparse(some_file_like,events=("start", "end")):print(f"{event:>5}, {element.tag:>4}, {element.text}")
start, root, None
start, a, dataend, a, dataend, root, None
请注意,在接收start
事件时,元素的文本、尾随文本和子元素不一定已经存在。只有end
事件保证了元素已经被完全解析。
它还允许您使用.clear()
方法或修改元素的内容以节省内存。因此,如果您解析了一个大的树,并且您希望保持内存使用量小,您应该清理不再需要的树的部分。.clear()
方法的keep_tail=True
参数确保当前元素后面的(尾随)文本内容不会被触动。强烈不建议修改解析器可能尚未完全读取的任何内容。
some_file_like = BytesIO(b"<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(keep_tail=True)
data
** cleaning up the subtree
None
** cleaning up the subtree
iterparse()的一个非常重要的用例是解析大型生成的XML文件,例如数据库转储(database dumps)。最常见的情况是,这些XML格式只有一个主要的数据项元素直接挂在根节点下,并且这个元素会重复数千次。在这种情况下,最佳实践是让lxml.etree
进行树的构建,并且只拦截这一个元素,使用正常的树API进行数据提取。
xml_file = BytesIO(b'''
<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(keep_tail=True)
ABC -- abc
MORE DATA -- more data
XYZ -- xyz
如果出于某种原因,根本不希望构建树,可以使用lxml.etree的目标解析器接口(target parser interface)。它通过调用目标对象的方法创建类似SAX的事件。通过实现这些方法中的一些或全部,您可以控制生成哪些事件:
class ParserTarget:events = []close_count = 0def start(self, tag, attrib):self.events.append(('start', tag, attrib))def close(self):events, self.events = self.events, []self.close_count += 1return eventsparser_target = ParserTarget()parser = etree.XMLParser(target=parser_target)
events = etree.fromstring('<root test="true"/>', parser)print(parser_target.close_count)
1
event: start - tag: root* test = true
您可以随心所欲地重用解析器及其目标,因此您应该确保.close()
方法确实将目标重置为可用状态(即使在出错的情况下也是如此!)。
>>> events = etree.fromstring('<root test="true"/>', parser)
>>> print(parser_target.close_count)
2
>>> events = etree.fromstring('<root test="true"/>', parser)
>>> print(parser_target.close_count)
3
>>> events = etree.fromstring('<root test="true"/>', parser)
>>> print(parser_target.close_count)
4>>> for event in events:
... print(f'event: {event[0]} - tag: {event[1]}')
... for attr, value in event[2].items():
... print(f' * {attr} = {value}')
event: start - tag: root* test = true
Namespaces
只要有可能,ElementTree API 都避免使用命名空间前缀,而是使用真实的命名空间(URI):
>>> xhtml = etree.Element("{http://www.w3.org/1999/xhtml}html")
>>> body = etree.SubElement(xhtml, "{http://www.w3.org/1999/xhtml}body")
>>> body.text = "Hello World">>> prettyprint(xhtml)
<html:html xmlns:html="http://www.w3.org/1999/xhtml"><html:body>Hello World</html:body>
</html:html>
ElementTree使用的表示法最初由James Clark提出。它的主要优点是为标签提供了一个通用限定名称(universally qualified name),无论文档中可能已经使用或定义的任何前缀。通过将前缀的间接性(indirection of prefixes)移开,它使命名空间感知的代码更加清晰,更容易正确处理。
正如您从示例中看到的,前缀只在序列化结果时变得重要。然而,由于命名空间名称较长,上述代码看起来有些冗长。而且,一遍又一遍地重新键入或复制字符串容易出错。因此,通常的做法是将命名空间URI存储在全局变量中。为了调整(adapt)用于序列化的命名空间前缀,你也可以将一个映射传递给Element
工厂函数,例如定义默认命名空间:
>>> XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
>>> XHTML = "{%s}" % XHTML_NAMESPACE>>> NSMAP = {None : XHTML_NAMESPACE} # the default namespace (no prefix)>>> xhtml = etree.Element(XHTML + "html", nsmap=NSMAP) # lxml only!
>>> body = etree.SubElement(xhtml, XHTML + "body")
>>> body.text = "Hello World">>> prettyprint(xhtml)
<html xmlns="http://www.w3.org/1999/xhtml"><body>Hello World</body>
</html>
你也可以使用QName
辅助类来构建或拆分限定的标签名称(qualified tag names)。
>>> tag = etree.QName('http://www.w3.org/1999/xhtml', 'html')
>>> print(tag.localname)
html
>>> print(tag.namespace)
http://www.w3.org/1999/xhtml
>>> print(tag.text)
{http://www.w3.org/1999/xhtml}html>>> tag = etree.QName('{http://www.w3.org/1999/xhtml}html')
>>> print(tag.localname)
html
>>> print(tag.namespace)
http://www.w3.org/1999/xhtml>>> root = etree.Element('{http://www.w3.org/1999/xhtml}html')
>>> tag = etree.QName(root)
>>> print(tag.localname)
html>>> tag = etree.QName(root, 'script')
>>> print(tag.text)
{http://www.w3.org/1999/xhtml}script
>>> tag = etree.QName('{http://www.w3.org/1999/xhtml}html', 'script')
>>> print(tag.text)
{http://www.w3.org/1999/xhtml}script
lxml.etree允许您通过.nsmap
属性查找为节点定义的当前命名空间:
>>> xhtml.nsmap
{None: 'http://www.w3.org/1999/xhtml'}
请注意,这包括在元素的上下文中已知的所有前缀,而不仅仅是它自己定义的那些。
root = etree.Element('root', nsmap={'a': 'http://a.b/c'})
child = etree.SubElement(root, 'child',nsmap={'b': 'http://b.c/d'})
print(root.nsmap)
{'a': '[http://a.b/c](http://a.b/c)'}
len(root.nsmap)
# 1
print(child.nsmap)
{'b': '[http://b.c/d](http://b.c/d)', 'a': '[http://a.b/c](http://a.b/c)'}
len(child.nsmap)
child.nsmap['a']
# 'http://a.b/c'
child.nsmap['b']
# 'http://b.c/d'
因此,修改返回的字典对Element(元素)没有任何有意义的影响。对它的任何更改都会被忽略。
属性(attributes)上的命名空间工作方式类似,但自2.3版本起,lxml.etree将确保属性使用带有前缀的命名空间声明。这是因为XML命名空间规范(第6.2节)认为未加前缀的属性名称不处于任何命名空间中,因此即使它们出现在命名空间元素中,它们在序列化-解析循环中可能会丢失其命名空间。
body.set(XHTML + "bgcolor", "#CCFFAA")
prettyprint(xhtml)
<html xmlns="http://www.w3.org/1999/xhtml"><body xmlns:html="http://www.w3.org/1999/xhtml" html:bgcolor="#CCFFAA">Hello World</body>
</html>
# XML命名空间规范认为未加前缀的属性名称不处于任何命名空间中,所以返回None
print(body.get("bgcolor"))
None
# 使用加上前缀的属性名称
body.get(XHTML + "bgcolor")
'#CCFFAA'
您还可以使用完全限定的名称(fully qualified names)来使用XPath:
# 先回顾一下xhtml的内容
print(etree.tostring(xhtml).decode())
<html xmlns="http://www.w3.org/1999/xhtml"><body xmlns:html="http://www.w3.org/1999/xhtml" html:bgcolor="#CCFFAA" bgcolor="#CCFFAA">Hello World</body></html>
>>> find_xhtml_body = etree.ETXPath( # lxml only !
... "//{%s}body" % XHTML_NAMESPACE)
>>> results = find_xhtml_body(xhtml)>>> print(results[0].tag)
{http://www.w3.org/1999/xhtml}body
为了方便,您可以在lxml.etree的所有迭代器中使用"*
"通配符,无论是对于标签名称还是命名空间:
>>> for el in xhtml.iter('*'): print(el.tag) # any element
{http://www.w3.org/1999/xhtml}html
{http://www.w3.org/1999/xhtml}body>>> for el in xhtml.iter('{http://www.w3.org/1999/xhtml}*'): print(el.tag)
{http://www.w3.org/1999/xhtml}html
{http://www.w3.org/1999/xhtml}body>>> for el in xhtml.iter('{*}body'): print(el.tag)
{http://www.w3.org/1999/xhtml}body
要查找没有命名空间的元素,请使用纯标签名称,或明确提供空的命名空间:
>>> [ el.tag for el in xhtml.iter('{http://www.w3.org/1999/xhtml}body') ]
['{http://www.w3.org/1999/xhtml}body']
>>> [ el.tag for el in xhtml.iter('body') ]
[]
>>> [ el.tag for el in xhtml.iter('{}body') ]
[]
>>> [ el.tag for el in xhtml.iter('{}*') ]
[]
The E-factory
E-factory提供了一种简单紧凑的语法,用于生成XML和HTML:
from lxml.builder import Edef CLASS(*args): # class is a reserved word in Pythonreturn {"class":' '.join(args)}html = page = (E.html(E.head(E.title("This is a sample document")),E.body(E.h1("Hello!", CLASS("title")),E.p("This is a paragraph with ", E.b("bold"), " text in it!"),E.p("This is another paragraph, with a", "\n ",E.a("link", href="http://www.python.org"), "."),E.p("Here are some reserved characters: <spam&egg>."),etree.XML("<p>And finally an embedded XHTML fragment.</p>"),))
)prettyprint(page)
<html><head><title>This is a sample document</title></head><body><h1 class="title">Hello!</h1><p>This is a paragraph with <b>bold</b> text in it!</p><p>This is another paragraph, with a<a href="http://www.python.org">link</a>.</p><p>Here are some reserved characters: <spam&egg>.</p><p>And finally an embedded XHTML fragment.</p></body>
</html>
基于属性访问的元素创建使得为XML 语言构建一种简单的词汇表变得容易。
from lxml.builder import ElementMaker # lxml only!E = ElementMaker(namespace="http://my.de/fault/namespace", nsmap={'p': "http://my.de/fault/namespace"})DOC = E.doc
TITLE = E.title
SECTION = E.section
PAR = E.parmy_doc = DOC(TITLE("The dog and the hog"),SECTION(TITLE("The dog"),PAR("Once upon a time, ..."),PAR("And then ...")),SECTION(TITLE("The hog"),PAR("Sooner or later ..."))
)prettyprint(my_doc)
<p:doc xmlns:p="http://my.de/fault/namespace"><p:title>The dog and the hog</p:title><p:section><p:title>The dog</p:title><p:par>Once upon a time, ...</p:par><p:par>And then ...</p:par></p:section><p:section><p:title>The hog</p:title><p:par>Sooner or later ...</p:par></p:section>
</p:doc>
一个这样的例子是模块lxml.html.builder,它为HTML提供了一个词汇表。
当处理多个命名空间时,最佳实践是为每个命名空间URI定义一个ElementMaker。再次注意,上述示例如何在命名常量中预定义了标签构建器(tag builders)。这使得将一个命名空间的所有标签声明放入一个Python模块,并从那里导入/使用标签名称常量变得容易。这避免了诸如拼写错误或意外遗漏命名空间之类的陷阱。
ElementPath
ElementTree库附带了一个简单的类似XPath的路径语言,称为ElementPath。主要区别在于您可以在ElementPath表达式中使用{namespace}tag表示法。然而,高级特性如值比较和函数是不可用的。
除了完整的XPath实现,lxml.etree以与ElementTree相同的方式支持ElementPath语言,甚至使用(几乎)相同的实现。API在这里提供了四种方法,您可以在Elements和ElementTrees上找到这些方法:
iterfind()
遍历所有匹配路径表达式(path expression)的元素。findall()
返回匹配元素的列表。find()
高效地仅返回第一个匹配项。findtext()
返回第一个匹配项的.text
内容。
这里有一些示例:
root = etree.XML("<root><a x='123'>aText<b/><c/><b/></a></root>")
查找元素的子元素:
>>> print(root.find("b"))
None
>>> print(root.find("a").tag)
a
在树中查找元素:
>>> print(root.find(".//b").tag)
b
>>> [ b.tag for b in root.iterfind(".//b") ]
['b', 'b']
查找具有特定属性的元素:
>>> print(root.findall(".//a[@x]")[0].tag)
a
>>> print(root.findall(".//a[@y]"))
[]
在lxml
3.4 版本中,有一个新的辅助函数用于为一个Element
生成结构化的ElementPath
表达式。
>>> tree = etree.ElementTree(root)
>>> a = root[0]
>>> print(tree.getelementpath(a[0]))
a/b[1]
>>> print(tree.getelementpath(a[1]))
a/c
>>> print(tree.getelementpath(a[2]))
a/b[2]
>>> tree.find(tree.getelementpath(a[2])) == a[2]
True
只要树未被修改,这个路径表达式就代表给定元素的标识符,可以稍后在相同树中使用find()找到它。与XPath相比,ElementPath表达式的优势在于即使对于使用命名空间的文档,它们也是自包含的。
.iter()
方法是一个特例,它仅通过名称在树中查找特定标签,而不是基于路径。这意味着在成功的情况下,以下命令是等效的:
>>> print(root.find(".//b").tag)
b
>>> print(next(root.iterfind(".//b")).tag)
b
>>> print(next(root.iter("b")).tag)
b
相关文章:

lxml官方入门教程(The lxml.etree Tutorial)翻译
lxml官方入门教程(The lxml.etree Tutorial)翻译 说明: 首次发表日期:2024-09-05官方教程链接: https://lxml.de/tutorial.html使用KIMI和豆包机翻水平有限,如有错误请不吝指出 这是一个关于使用lxml.et…...

string详解
Golang详解string 文章目录 Golang详解stringGolang中为什么string是只读的?stirng和[]byte的转化原理[]byte转string一定需要内存拷贝吗?字符串拼接性能测试 Golang中为什么string是只读的? 在Go语言中,string其实就是一个结构体…...

基于约束大于规范的想法,封装缓存组件
架构?何谓架构?好像并没有一个准确的概念。以前我觉得架构就是搭出一套完美的框架,可以让其他开发人员减少不必要的代码开发量;可以完美地实现高内聚低耦合的准则;可以尽可能地实现用最少的硬件资源,实现最高的程序效率…...

自动化测试面试真题(附答案)
一、编程语法题 1 、 python 有哪些数据类型 python 数据类型有很多,基本数据类型有整型(数字)、字符串、元组、列表、字典和布尔类型等 2 、怎么将两个字典合并 调用字典的 update 方法,合并 2 个字典。 3 、 json.l python …...

云原生架构概念
云原生架构概念 云原生架构(Cloud Native Architechtrue)作为一种现代软件开发的革新力量,正在逐渐改变企业构建、部署和管理应用程序的方式。它的核心优势在于支持微服务架构,使得应用程序能够分解为独立、松耦合的服务…...

85、 探针
一、pod的进阶 pod的进阶: 1.1、pod的生命周期当中的状态: 1、Running运行中,pod已经分配到节点上且pod内的容器正常运行。正常状态(ready 1/1)。 2、complete:完成之后退出,容器内的返回码…...

2024全国大学省数学建模竞赛A题-原创参考论文(部分+第一问代码)
一问题重述 1.1 问题背景 "板凳龙",又称"盘龙",是浙闽地区的传统地方民俗文化活动。这种独特的表演艺术形式融合了中国传统龙舞的精髓和地方特色,展现了人们对美好生活的向往和对传统文化的传承。 在板凳龙表演中&am…...

在VScode上写网页(html)
一、首先点进VScode,下载3个插件。 VScode安装:VScode 教程 | 菜鸟教程 二、新建 HTML 文件 作者运行的代码来自:http://t.csdnimg.cn/vIAQi 把代码复制粘贴进去,然后点击文件→另存为→选择html格式。 三、运行代码...

C#中LINQ的Cast<T>与OfType<T>
在C#中,Cast() 方法是LINQ(Language Integrated Query)的一部分,它位于 System.Linq 命名空间中。这个方法用于将 IEnumerable 集合(或任何实现了 IEnumerable 接口的集合)的元素转换为指定类型 T 的集合。…...

小阿轩yx-Kubernertes日志收集
小阿轩yx-Kubernertes日志收集 前言 在 Kubernetes 集群中如何通过不同的技术栈收集容器的日志,包括程序直接输出到控制台日志、自定义文件日志等 有哪些日志需要收集 日志收集与分析很重要,为了更加方便的处理异常 简单总结一些比较重要的需要收集…...

0to1使用Redis实现“登录验证”次数限制
1 引言 系统为了避免密码遭到暴力破解,通常情况下需要在登录时,限制用户验证账号密码的次数,当达到一定的验证次数后,在一段时间内锁定该账号,不再验证。本章将用几行代码实现该功能,完整代码链接在文章最…...

ARM----时钟
时钟频率可以是由晶振提供的,我们需要高频率,但是外部接高的晶振会不稳定,所有使用PLL(锁相环)来放大频率。接下来就让我们学习用外部晶振提供的频率来配置时钟频率。 一.时钟源的选择 在这里我们选择外部晶振作为时钟…...

NISP 一级 —— 考证笔记合集
该笔记为导航目录,在接下来一段事件内,我会每天发布我关于考取该证书的相关笔记。 当更新完成后,此条注释会被删除。 第一章 信息安全概述 1.1 信息与信息安全1.2 信息安全威胁1.3 信息安全发展阶段与形式1.4 信息安全保障1.5 信息系统安全保…...

C++三位状态比较排序
数组相同元素个数及按序 void 交换3个数升(int& A, int& B, int& C, bool& k) {int J 0;if (B > A&&A > C)J C, C B, B A, A J, k true;//231else if (C > A&&A > B)J A, A B, B J, k true;//213else if (A > B&a…...

麒麟系统安装GPU驱动
1.nvidia 1.1显卡驱动 本机显卡型号:nvidia rtx 3090 1.1.1下载驱动 打开 https://www.nvidia.cn/geforce/drivers/ 也可以直接使用下面这个地址下载 https://www.nvidia.com/download/driverResults.aspx/205464/en-us/ 1.1.3安装驱动 右击,为run文件添加可…...

IDEA 安装lombok插件不兼容的问题及解决方法
解决:IDEA 安装lombok插件不兼容问题,plugin xxxx is incompatible 一、去官网下载最新的2024版本 地址传送通道: lombok插件官网地址https://plugins.jetbrains.com/plugin/6317-lombok/versions/stable 二、修改参数的配置 在压缩包路径…...

聊聊说话的习惯
1 在日常生活中,每个人都有固定的说话习惯。心理学研究表明,通过一个人的说话习惯,也可以分析出他的性格特点。对于每一个人来讲,说话习惯已经融为他们生活中的一部分。在社交活动中,一些不良的说话习惯很可能会给他们带来麻烦。…...

当水泵遇上物联网:智能水务新时代的浪漫交响
在当代科技的宏伟乐章中,物联网(IoT)技术宛如一位技艺高超的指挥家,引领着各行各业迈向智能化的新纪元。当这股创新浪潮涌向古老的水务行业时,一场前所未有的“智能水务”革命便悄然上演,而水泵——这一传统…...

【Canvas与钟表】干支表盘
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>387.干支表盘</title><style type"text/css">…...

分布式项目中使用雪花算法提前获取对象主键ID
hello,大家好,我是灰小猿! 在做分布式项目开发进行数据表结构设计时,有时候为了提高查询性能,在进行数据库表设计时,会使用自增ID来代替UUID作为数据的主键ID,但是这样就会有一个问题ÿ…...

小程序多个set-cookie无法处理
1、情景: 项目中遇到一个问题,客户的服务器上了华为云的防火墙,导致小程序请求头中携带了3个set- cookie(有两个是华为云给自动加的),而小程序端不知道用哪个来 处理,结果选了个错误的进行处理…...

Mybatis【分页插件,缓存,一级缓存,二级缓存,常见缓存面试题】
文章目录 MyBatis缓存分页延迟加载和立即加载什么是立即加载?什么是延迟加载?延迟加载/懒加载的配置 缓存什么是缓存?缓存的术语什么是MyBatis 缓存?缓存的适用性缓存的分类一级缓存引入案例一级缓存的配置一级缓存的工作流程一级…...

【Qt开发】QT6.5.3安装方法(使用国内源)亲测可行!!!
目录 🌕下载在线安装包🌕 把安装包放到系统盘🌕开始安装🌕参考文章 🌕下载在线安装包 https://mirrors.nju.edu.cn/qt/official_releases/online_installers/ 🌕 把安装包放到系统盘 我的系统盘是G盘&…...

springblade-JWT认证缺陷漏洞CVE-2021-44910
漏洞成因 SpringBlade前端通过webpack打包发布的,可以从其中找到app.js获取大量接口 然后直接访问接口:api/blade-log/api/list 直接搜索“请求未授权”,定位到认证文件:springblade/gateway/filter/AuthFilter.java 后面的代码…...

Chapter 12 Vue CLI脚手架组件化开发
欢迎大家订阅【Vue2Vue3】入门到实践 专栏,开启你的 Vue 学习之旅! 文章目录 前言一、项目目录结构二、组件化开发1. 组件化2. Vue 组件的基本结构3. 依赖包less & less-loader 前言 组件化开发是Vue.js的核心理念之一,Vue CLI为开发者提…...
Ubuntu: 配置OpenCV环境
从从Ubuntu系统安装opencv_ubuntu安装opencv-CSDN博客文章浏览阅读2.3k次,点赞4次,收藏14次。开源计算机视觉(OpenCV)是一个主要针对实时计算机视觉的编程函数库。OpenCV的应用领域包括:2D和3D功能工具包、运动估计、面部识别系统、手势识别、人机交互、…...

芯片解决方案--SL8541e-OpenHarmony适配方案
摘要 本文描述8541E芯片适配OpenHarmony的整体方案。 本文描述的整体方案,不止适用于8541e,也适用于该芯片厂家的其他芯片,如7863、7885,少部分子系统会略有差异。 整体方案架构 整体方案架构如下图,遵循OpenHarmo…...

Spring Boot之数据访问集成入门
Spring Boot中的数据访问和集成支持功能是其核心功能之一,通过提供大量的自动配置和依赖管理,极大地简化了数据访问层的开发。Spring Boot支持多种数据库,包括关系型数据库(如MySQL、Oracle等)和非关系型数据库&#x…...

Learn ComputeShader 09 Night version lenses
这次将要制作一个类似夜视仪的效果 第一步就是要降低图像的分辨率, 这只需要将id.xy除上一个数字然后再乘上这个数字 可以根据下图理解,很明显通过这个操作在多个像素显示了相同的颜色,并且很多像素颜色被丢失了,自然就会有降低分…...

Java学习第七天
成员方法分类: 静态成员方法(有static修饰 属于类)建议用类名访问,也可以用对象访问 实例成员方法(无static修饰 属于对象)只能用对象出发访问 使用static来定义一些工具类 工具类直接使用类名.方法调用即…...