使用XPath查找文本(Using XPath to find text)
另一个在树状结构文档中抽取文本的方式是:XPath,它同样允许你抽取单独的文本块并放到list中。
>>> print(html.xpath("string()")) # lxml.etree only!
TEXTTAIL
>>> print(html.xpath("//text()")) # lxml.etree only!
['TEXT', 'TAIL']
如果你想重复利用上述代码,你可以把它封装到一个函数中:
# 注意,此处使用text(),而不是string()、concat(),后续有说明
>>> build_text_list = etree.XPath("//text()") # lxml.etree only!
>>> print(build_text_list(html))
['TEXT', 'TAIL']
注意,由XPath返回的文本结果是一个非常“智能”的对象,它知道自己的出身。你可以使用getparent()来了解它来自哪里(即获取它的父节点),就像你对Element对象所做的那样。
>>> 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
你还可以知道它到底是普通的文本内容,还是尾部文本(tail text):
>>> print(texts[0].is_text)
True
>>> print(texts[1].is_text)
False
>>> print(texts[1].is_tail)
True
既然text()函数返回的文本对象可以告诉你它的“归属”(即获取它的父节点),当你使用XPath函数string()、concat()时,它将不再告诉你它的“归属”。
>>> stringify = etree.XPath("string()")
>>> print(stringify(html))
TEXTTAIL
>>> print(stringify(html).getparent())
None
树的迭代(Tree iteration)
为了应对上面的问题,你可能需要递归遍历树并使用对它拥有的节点做一些事情,而树的迭代恰好是一个很方便的解决方案。为实现这一目的,Elements提供了树迭代器。就像生成器一样,在有序的文档(document order)返回(yield)节点。
>>> 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
如果你仅仅对某一些标签感兴趣,你可以把它的标签名传递给iter()来为你过滤掉不需要的标签。从lxml 3.0开始,你也可以同时传递多个标签名:
>>> for element in root.iter("child"):
... print("%s - %s" % (element.tag, element.text))
child - Child 1
child - Child 2
>>> for element in root.iter("another", "child"):
... print("%s - %s" % (element.tag, element.text))
child - Child 1
child - Child 2
another - Child 3
默认情况下,迭代器会返回树的所有节点,包括ProcessingInstructions(处理指令),Comments(注释)、Entity instances(实体实例)。如果你想确认只有元素节点(Elements)返回,你可以把Element工厂函数作为tag关键字参数传递给iter():
>>> 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)
ê
注意,传递通配符“*”仍具会返回所有元素节点(仅包含Element)。
序列化(Serialisation)
序列化通常使用tostring()函数,它会返回一个字符串,或者你也可以使用ElementTree.write()方法将其写入到一个文件、类文件对象、一个URL。两种调用方式都接收同样的关键字参数,比如pretty_print(格式化输出),encoding(编码格式)可以指定除ASCII之外的输出编码格式,以及xml_declaration(是否加入XML声明):
>>> root = etree.XML('<root><a><b/></a></root>')
>>> etree.tostring(root)
b'<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>
注意,美化输出(指定pretty_print=True时)会在文件最末位加一空白行。
在lxml 2.0及之后的版本,序列化函数能做的不仅局限于XML序列化。你也可以将其序列化成HTML,或者抽取文本,只需要通过method关键字参数:
>>> 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>'
>>> 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 = next(root.iter('br')) # get first result of iteration
>>> 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'
在下面的例子中,序列化成一个python Unicode字符串,比字节串(byte strings)更加易于处理。只需指定encoding为unicode即可:
>>> etree.tostring(root, encoding='unicode', method='text')
u'HelloW\xf6rld'