Python 中的 Lxml 库与 XPath 用法

发布于:2024-11-28 ⋅ 阅读:(40) ⋅ 点赞:(0)

Python 中的 Lxml 库与 XPath 用法

Lxml 是一个 Python 的第三方库,它提供了一套非常强大且高效的工具,用于处理 HTML 和 XML 文档。Lxml 结合了 libxml2 和 libxslt 库的功能,这意味着它不仅速度快,而且功能全面。对于 Web 抓取、文档解析和转换等工作,Lxml 是一个极佳的选择。

Lxml

安装 Lxml

可以通过 pip 来安装 Lxml:

pip install lxml
基础用法

Lxml 提供两种主要的数据结构: ElementTreeElementElementTree 表示整个文档,而 Element 则代表单个节点。

加载文档

你可以加载本地文件或者字符串形式的 HTML/XML 内容。

from lxml import etree

# 从文件加载
tree = etree.parse('example.xml')

# 从字符串加载
html_str = "<html><body><h1>Hello World!</h1></body></html>"
root = etree.fromstring(html_str)
解析与查询

使用 XPath 查询语法可以轻松提取信息。

# 获取所有 h1 元素
elements = tree.xpath('//h1')

# 输出元素文本
for elem in elements:
    print(elem.text)
创建新的 XML/HTML

Lxml 支持创建全新的 XML 或者 HTML 文件。

# 创建新元素
elem = etree.Element("greeting")
elem.text = "Hello"
sub_elem = etree.SubElement(elem, "name")
sub_elem.text = "World"

# 将元素转化为字符串输出
etree.tostring(elem, pretty_print=True)
高级特性

Lxml 是一个极其丰富的库,为处理 XML 和 HTML 提供了大量的高级功能。以下是 Lxml 提供的一些关键特性和使用案例,这些特性让它成为一个非常强大且多功能的工具包:

1. 复杂的 XPath 查询

除了基本的 XPath 功能外,Lxml 还支持更复杂的 XPath 2.0 特性,比如:

  • 函数调用,如 substring, starts-with, normalize-space
  • 更多的数据类型处理,如日期时间处理
  • 数组和序列的操作
from lxml import etree

# 检查是否以特定字符开头
results = tree.xpath("//element[starts-with(@attr, 'prefix')]")

# 字符串截取
value = tree.xpath("string(/path/to/node)[substring(., 1, 5)]")

# 正则表达式匹配
matches = tree.xpath("//element[re:test(@attr, 'pattern')]")
2. DTD 和 Schema 验证

Lxml 提供了 DTD(Document Type Definition)和 Schema(如 XML Schema, Relax NG)的验证功能,确保你的文档符合预定的标准。

# 创建一个 DTD 或 Schema 验证器
schema = etree.DTD('my.dtd')
schema = etree.XMLSchema(etree.parse('myschema.xsd'))

# 验证文档
if not schema.validate(tree):
    raise ValueError("Document is not valid against the provided DTD or Schema.")
3. XSLT 变换

Lxml 支持使用 XSLT 对 XML 文档进行转换,这是一种将源 XML 转换成另一种格式的有效方式。

# 加载 XSLT 文件
transformer = etree.XSLT(etree.parse('stylesheet.xsl'))

# 应用转换
new_doc = transformer(etree.parse('source.xml'))
4. 自定义命名空间

在处理带有命名空间的 XML 文档时,Lxml 提供了简洁的方式来进行处理。

ns = {'n': 'http://www.example.com/mynamespace'}

# 使用命名空间查询
elements = tree.xpath('//n:element', namespaces=ns)
5. 异常处理

Lxml 包含了丰富的异常类,可以让你更准确地捕捉和处理 XML/HTML 解析过程中的问题。

from lxml.etree import ParseError

try:
    doc = etree.fromstring('<bad_xml>')
except ParseError as e:
    print(f"Parse error at line {e.lineno}: {e.message}")
6. 大文件流式处理

对于大型文件,Lxml 提供了流式处理功能,避免一次性加载整个文档到内存。

context = etree.iterparse('largefile.xml', events=('end',), huge_tree=True)
for event, elem in context:
    # 这里可以安全地处理每个元素
    process_element(elem)
    elem.clear()
    while elem.getprevious() is not None:
        del elem.getparent()[0]

del context
7. 并发和线程安全性

Lxml 在设计上考虑了线程安全,并允许你在多线程环境中使用,这对于大规模数据处理特别有用。

8. 性能优化

Lxml 由 C 实现,在速度和资源管理方面进行了优化,尤其是当与其他纯 Python 解析器比较时更为明显。你还可以通过设置解析器选项来自定义解析行为。

parser = etree.XMLParser(remove_comments=True, recover=True)
tree = etree.parse('large_file.xml', parser=parser)

总之,Lxml 是 Python 生态系统中处理 XML 和 HTML 数据的一个强大库,无论是用于解析、生成、验证还是变换,它都表现出色。通过上述基础和进阶应用的学习,你应该能够灵活运用 Lxml 来解决复杂的数据处理任务。

XPath

XPath 是一种在 XML 文档中查询和定位元素和属性的强大语言,但它同样可以应用于 HTML 文档。XPath 的表达式可以相当复杂,涵盖了广泛的查询需求。下面是 XPath 的一些常用操作和高级用法汇总:

基础 XPath 语法
选择元素
  • //:从根节点开始搜索所有子孙节点
  • /:绝对路径
  • .:当前节点
  • ..:父节点
  • *:任何元素
  • @:属性选择器
  • ancestor:祖先节点
  • attribute:属性
  • child:子节点
  • descendant:后代节点
  • following-sibling:后续同级节点
  • preceding-sibling:前面的同级节点
谓词
  • [1]:选择指定位置的节点
  • [contains(text(), 'search-text')]:包含文本的节点
  • [@class='classname']:具有特定属性的节点
复杂查询
组合条件
  • and / or:逻辑运算符连接多个条件
  • not():否定条件
函数
  • string-length():计算字符串长度
  • normalize-space():移除首尾空格,压缩内部空白
  • concat():拼接字符串
  • contains(string, substring):检查子字符串
  • starts-with(string, prefix):检查字符串前缀
  • ends-with(string, suffix):检查字符串后缀
节点数量
  • count(node-set):统计节点数目
排序
  • sort():按顺序排列结果
实例演示

假设有一个 XML 文档如下:

<books>
  <book genre="fiction">
    <author>John Doe</author>
    <title>The Great Novel</title>
    <year>2020</year>
  </book>
  <book genre="non-fiction">
    <author>Jane Smith</author>
    <title>A Guide to Cooking</title>
    <year>2019</year>
  </book>
</books>

选择所有书名

//book/title

选择第一本书的作者

/book[1]/author

找到年份大于等于 2020 的所有书

//book[year >= 2020]

查找标题包含 “Guide” 的书

//book[contains(title, 'Guide')]

获取具有非小说类型的书的作者名字

//book[@genre != 'fiction']/author

按出版年份降序排序的书籍列表

//book | sort-by(., year descending)
性能注意事项

虽然 XPath 非常强大,但在处理大数据量时,应该谨慎使用,因为某些查询可能涉及遍历大量节点,从而影响性能。为了提高效率,尽量减少重复扫描,使用索引或其他技术,特别是在大型文档中进行搜索时。

总之,XPath 提供了一个功能丰富且直观的方式来操纵和查询 XML 和 HTML 文档。掌握这些基本和高级用法可以使你更加熟练地处理各种结构化文档的分析和检索工作。

Lxml 和 XPath 的示例

from lxml import etree

xml_data = """
<books>
    <book genre="fantasy">
        <author>Tolkien</author>
        <title>The Hobbit</title>
        <year>1937</year>
    </book>
    <book genre="science-fiction">
        <author>Asimov</author>
        <title>I, Robot</title>
        <year>1950</year>
    </book>
    <book genre="horror">
        <author>King</author>
        <title>The Shining</title>
        <year>1977</year>
    </book>
</books>
"""

# 解析 XML 字符串
root = etree.fromstring(xml_data)

现在我们可以编写一系列示例,对应不同的 XPath 查询:

  1. 选择所有 <title> 元素
titles = root.xpath('//title')
for t in titles:
    print(t.text)
  1. 获取第一本书的 <year>
first_year = root.xpath('//book/year')[0].text
print(first_year)
  1. 找到类型为科幻的书
sci_fi_books = root.xpath('//book[@genre="science-fiction"]')
for book in sci_fi_books:
    print(book.find('title').text)
  1. 查找标题包含单词 “The” 的所有书
the_books = root.xpath('//book[title[translate(text(),"THE","the") = "the"]]')
for book in the_books:
    print(book.find('title').text)

注意上面使用 translate() 函数将标题转换成小写以便不区分大小写的比较。

  1. 选择最新的一本书
latest_book = root.xpath('//book[last()]')
print(latest_book[0].find('title').text)
  1. 按照年份升序排序书籍
sorted_books = sorted(root.findall('.//book'), key=lambda b: int(b.find('year').text))
for book in sorted_books:
    print(book.find('title').text)
  1. 获取所有书籍的作者和标题
authors_and_titles = root.xpath('//book/(author|title)')
for i in range(0, len(authors_and_titles), 2):
    author = authors_and_titles[i].text
    title = authors_and_titles[i+1].text
    print(f"{author} wrote {title}")
  1. 获取书籍总数
total_books = len(root.xpath('//book'))
print(total_books)