导航菜单

  • 1.vector
  • 2.milvus
  • 3.pymilvus
  • 4.rag
  • 5.rag_measure
  • 7.search
  • ragflow
  • heapq
  • HNSW
  • cosine_similarity
  • math
  • typing
  • etcd
  • minio
  • collections
  • jieba
  • random
  • beautifulsoup4
  • chromadb
  • sentence_transformers
  • numpy
  • lxml
  • openpyxl
  • PyMuPDF
  • python-docx
  • requests
  • python-pptx
  • text_splitter
  • all-MiniLM-L6-v2
  • openai
  • llm
  • BPETokenizer
  • Flask
  • RAGAS
  • BagofWords
  • langchain
  • Pydantic
  • abc
  • faiss
  • MMR
  • scikit-learn
  • 1. 什么是 lxml?
  • 2. 环境准备
    • 2.1 检查 Python 版本
    • 2.2 安装 lxml
    • 2.3 验证安装
  • 3. 核心概念
    • 3.1 Element(元素对象)
    • 3.2 根节点(Root Element)
    • 3.3 父子关系
    • 3.4 XPath
  • 4. 快速体验:解析 XML
    • 4.1 解析 XML 字符串
    • 4.2 解析 HTML
  • 5. 元素导航:访问和修改元素
    • 5.1 访问元素
    • 5.2 修改元素
    • 5.3 遍历元素
  • 6. XPath 查询:强大的元素定位工具
    • 6.1 XPath 基础语法
    • 6.2 常用的 XPath 语法
  • 7. 创建和生成 XML
    • 7.1 创建 XML 元素
    • 7.2 保存 XML 到文件
  • 8. HTML 解析与清洗
    • 8.1 解析 HTML
    • 8.2 HTML 清洗
  • 9. 从文件读取 XML
    • 9.1 读取 XML 文件
  • 10. 实战案例:RSS 解析器
  • 11. 性能优化:流式解析大型文件
    • 11.1 流式解析
  • 12. 常见问题与排查
    • 12.1 XML 语法错误处理
    • 12.2 编码问题
    • 12.3 命名空间处理
    • 12.4 HTML 动态内容
  • 13. 参考

1. 什么是 lxml? #

lxml 是 Python 中最强大的 XML/HTML 解析库之一。它基于 C 语言库 libxml2/libxslt,具有高性能和 Pythonic 的 API。无论你是要解析网页、处理配置文件,还是提取结构化数据,lxml 都能帮你快速完成。

为什么要用 lxml?

想象一下,你有一个 XML 配置文件,需要从中提取某些信息,或者你需要从网页中抓取数据。手动解析太慢,而 lxml 提供了强大的工具,可以快速定位和提取你需要的元素。

lxml 的优势:

  • 速度快:基于 C 语言实现,比纯 Python 库快得多
  • 功能强大:支持 XPath、XSLT 等高级功能
  • 容错性好:可以处理格式不完美的 XML/HTML
  • API 友好:提供了简单易用的 Python API

前置知识补充:

  • XML(可扩展标记语言):XML 是一种用于存储和传输数据的标记语言。它使用标签来标记数据,比如 <book><title>Python 入门</title></book>。XML 是结构化的,便于程序解析。
  • HTML(超文本标记语言):HTML 是网页的标记语言,与 XML 类似但更宽松。HTML 使用标签来定义网页的结构,比如 <div>、<p>、<a> 等。
  • 标签和属性:标签是 <tag>内容</tag> 这样的结构,属性是标签中的额外信息,比如 <book id="1"> 中的 id="1" 就是属性。
  • 元素(Element):一个标签及其内容组成一个元素。比如 <title>Python</title> 就是一个元素。

2. 环境准备 #

在学习 lxml 之前,需要确保你的 Python 环境已经准备好。lxml 支持 Python 3.8 及以上版本。

2.1 检查 Python 版本 #

在安装之前,先检查一下你的 Python 版本是否符合要求。

# Windows PowerShell:查看 Python 版本
python --version
# macOS 终端:查看 Python 版本
python3 --version

2.2 安装 lxml #

lxml 可以直接通过 pip 安装。安装过程会自动下载所需的依赖包。

# 说明:Windows PowerShell 安装 lxml
# 先升级 pip 到最新版本,确保能正常安装依赖
python -m pip install --upgrade pip
# 安装 lxml 库
python -m pip install lxml
# 说明:macOS / Linux 终端安装 lxml
# 先升级 pip 到最新版本
python3 -m pip install --upgrade pip
# 安装 lxml 库
python3 -m pip install lxml

网络加速提示:如果从 PyPI 下载较慢,可以使用国内镜像:

  • Windows: python -m pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple
  • macOS: python3 -m pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple

安装问题提示:

  • 如果出现编译错误,可能是因为系统缺少编译工具。Windows 用户可以尝试安装预编译的二进制轮子。
  • macOS 用户如果遇到编译错误,可以先执行 xcode-select --install 安装 Xcode 命令行工具。

2.3 验证安装 #

安装完成后,验证一下是否安装成功。

# -*- coding: utf-8 -*-
# 说明:验证 lxml 是否安装成功

# 说明:导入 lxml 的 etree 模块
# etree 是 lxml 中用于 XML 解析的核心模块
from lxml import etree

# 说明:创建一个简单的 XML 字符串用于测试
test_xml = "<root><item>测试</item></root>"

# 说明:使用 fromstring() 方法解析 XML 字符串
# fromstring() 将字符串转换为 Element 对象
root = etree.fromstring(test_xml)

# 说明:提取元素的文本内容
# findtext() 方法查找第一个匹配的子元素并返回其文本
text = root.findtext("item")

# 说明:打印结果,验证安装是否成功
print("安装成功!提取的文本:", text)

# 说明:打印 lxml 版本信息
print(f"lxml 版本:{etree.__version__}")

# 说明:如果没有报错并输出了"测试",说明安装成功

3. 核心概念 #

在使用 lxml 之前,需要理解几个核心概念。这些概念是理解 lxml 的基础。

3.1 Element(元素对象) #

Element 是 lxml 的核心对象,代表 XML/HTML 中的一个元素(标签及其内容)。每个 Element 对象都有标签名、文本内容、属性等。

3.2 根节点(Root Element) #

根节点是整个 XML/HTML 文档的最外层元素。所有其他元素都是根节点的子元素或后代元素。

3.3 父子关系 #

XML/HTML 是树状结构,元素之间有父子关系。父元素包含子元素,子元素被父元素包含。

3.4 XPath #

XPath 是一种用于在 XML/HTML 文档中定位元素的查询语言。它使用路径表达式来选择元素或节点。

4. 快速体验:解析 XML #

让我们通过一个最简单的例子来快速体验 lxml 的强大功能。

4.1 解析 XML 字符串 #

# -*- coding: utf-8 -*-
# 说明:快速体验 lxml 的基本用法 - 解析 XML

# 说明:导入 lxml 的 etree 模块
# etree 是 lxml 中用于 XML 解析的核心模块
from lxml import etree

# 说明:准备一个示例 XML 字符串
# 这是一个简单的书籍列表,包含两本书的信息
xml_text = """
<books>
    <book id="1">
        <title>Python 入门</title>
        <price>39.9</price>
    </book>
    <book id="2">
        <title>数据科学</title>
        <price>49.9</price>
    </book>
</books>
"""

# 说明:使用 fromstring() 方法解析 XML 字符串
# fromstring() 将 XML 字符串转换为 Element 对象(根节点)
xml_root = etree.fromstring(xml_text)

# 说明:遍历所有 book 元素
# 可以直接遍历元素的子元素
for book in xml_root:
    # 说明:使用 findtext() 方法查找子元素并获取其文本内容
    # findtext() 返回第一个匹配的子元素的文本,如果找不到返回 None
    title = book.findtext("title")

    # 说明:获取价格文本
    price = book.findtext("price")

    # 说明:打印每本书的信息
    print(f"标题:{title} | 价格:{price}")

# 说明:输出结果应该是:
# 标题:Python 入门 | 价格:39.9
# 标题:数据科学 | 价格:49.9

4.2 解析 HTML #

lxml 也可以解析 HTML,使用 html 模块。

# -*- coding: utf-8 -*-
# 说明:快速体验 lxml 的基本用法 - 解析 HTML

# 说明:导入 lxml 的 html 模块
# html 模块专门用于解析 HTML 文档
from lxml import html

# 说明:准备一个示例 HTML 字符串
# 这是一个简单的 HTML 片段,包含一个段落
html_text = "<div><p class='intro'>欢迎使用 lxml</p></div>"

# 说明:使用 fromstring() 方法解析 HTML 字符串
# html.fromstring() 将 HTML 字符串转换为 Element 对象
html_root = html.fromstring(html_text)

# 说明:使用 XPath 查找所有段落并提取文本
# //p 表示查找所有 p 标签(无论在文档的哪个位置)
# /text() 表示获取标签内的文本内容
# xpath() 方法返回一个列表
paragraphs = html_root.xpath("//p/text()")

# 说明:打印提取的文本
print("段落文本:", paragraphs)

# 说明:输出结果应该是:['欢迎使用 lxml']

5. 元素导航:访问和修改元素 #

lxml 提供了多种方式来访问和修改 XML/HTML 元素。理解这些方法对于数据提取和处理非常重要。

5.1 访问元素 #

方法/属性 功能说明 常用参数 返回值
etree.fromstring() 从字符串解析 XML text:XML 字符串 Element 对象(根节点)
root[index] 通过索引访问子元素 index:子元素索引(从 0 开始) 子 Element 对象
.findtext() 查找第一个匹配的子元素并返回其文本 tag:标签名 文本字符串(找不到返回 None)
.attrib 获取元素的所有属性 无(属性) 字典,包含所有属性
.find() 查找第一个匹配的子元素 tag:标签名 Element 对象(找不到返回 None)
# -*- coding: utf-8 -*-
# 说明:演示如何访问和导航 XML 元素

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备一个示例 XML 字符串
# 这是一个商品列表,包含两个商品的信息
xml = """
<store>
    <item category="tech">
        <name>键盘</name>
        <price>199</price>
    </item>
    <item category="book">
        <name>算法导论</name>
        <price>99</price>
    </item>
</store>
"""

# 说明:使用 fromstring() 解析 XML 字符串
# 返回根节点 Element 对象
root = etree.fromstring(xml)

# 说明:访问第一个 item 元素
# 使用索引 [0] 访问第一个子元素(索引从 0 开始)
first_item = root[0]

# 说明:使用 findtext() 方法查找 name 子元素并获取文本
# findtext() 返回第一个匹配的子元素的文本内容
name = first_item.findtext("name")
print("第一个商品名称:", name)

# 说明:获取元素的属性
# attrib 是一个字典,包含元素的所有属性
attributes = first_item.attrib
print("商品属性:", attributes)

# 说明:访问第二个 item 元素
# 使用索引 [1] 访问第二个子元素
second_item = root[1]

# 说明:获取第二个商品的名称和价格
second_name = second_item.findtext("name")
second_price = second_item.findtext("price")
print(f"\n第二个商品:{second_name},价格:{second_price}")

# 说明:遍历所有 item 元素
print("\n所有商品:")
for item in root:
    name = item.findtext("name")
    price = item.findtext("price")
    category = item.get("category")  # 使用 get() 方法获取属性值
    print(f"  名称:{name},价格:{price},类别:{category}")

5.2 修改元素 #

方法 功能说明 常用参数 返回值
.find() 查找第一个匹配的子元素 tag:标签名 Element 对象(找不到返回 None)
.text 获取或设置元素的文本内容 无(属性),可直接赋值 文本字符串
etree.SubElement() 创建并添加子元素 parent:父元素,tag:标签名,**attrib:属性(可选) 新创建的 Element 对象
etree.tostring() 将 Element 对象转换为 XML 字符串 element:Element 对象,encoding:编码,pretty_print:是否格式化 XML 字符串
# -*- coding: utf-8 -*-
# 说明:演示如何修改 XML 元素

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备一个示例 XML 字符串
xml = """
<store>
    <item category="tech">
        <name>键盘</name>
        <price>199</price>
    </item>
    <item category="book">
        <name>算法导论</name>
        <price>99</price>
    </item>
</store>
"""

# 说明:解析 XML 字符串
root = etree.fromstring(xml)

# 说明:获取第一个商品元素
first_item = root[0]

# 说明:查找 price 元素
# find() 方法返回第一个匹配的子元素对象
price_node = first_item.find("price")

# 说明:修改价格文本
# text 属性可以直接读取和修改元素的文本内容
price_node.text = "188"
print("价格已修改为:", price_node.text)

# 说明:创建新的子元素
# SubElement() 函数创建新的子元素并添加到父元素中
# 参数:父元素、标签名、属性(可选)
new_node = etree.SubElement(first_item, "stock")

# 说明:设置新元素的文本内容
new_node.text = "有货"

# 说明:将 Element 对象转换为格式化的 XML 字符串
# tostring() 将 Element 对象转换回 XML 字符串
# encoding="unicode" 表示返回 Unicode 字符串(而不是字节)
# pretty_print=True 表示格式化输出(添加缩进和换行)
xml_str = etree.tostring(root, encoding="unicode", pretty_print=True)

# 说明:打印修改后的 XML
print("\n修改后的 XML:")
print(xml_str)

5.3 遍历元素 #

方法/属性 功能说明 常用参数 返回值
.tag 获取元素的标签名 无(属性) 标签名字符串
.text 获取元素的文本内容 无(属性) 文本字符串
.get() 获取元素的属性值 key:属性名 属性值字符串(找不到返回 None)
.findall() 查找所有匹配的子元素 tag:标签名 Element 对象列表
# -*- coding: utf-8 -*-
# 说明:演示如何遍历 XML 元素树

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备一个示例 XML 字符串
xml = """
<store>
    <item category="tech">
        <name>键盘</name>
        <price>199</price>
        <stock>10</stock>
    </item>
    <item category="book">
        <name>算法导论</name>
        <price>99</price>
        <stock>5</stock>
    </item>
</store>
"""

# 说明:解析 XML 字符串
root = etree.fromstring(xml)

# 说明:方法1:直接遍历子元素
# 可以直接使用 for 循环遍历元素的直接子元素
print("方法1:直接遍历子元素")
for item in root:
    # 说明:tag 属性返回元素的标签名
    print(f"标签名:{item.tag}")

    # 说明:attrib 返回元素的属性字典
    print(f"属性:{item.attrib}")

    # 说明:遍历子元素的子元素
    for child in item:
        # 说明:tag 获取标签名,text 获取文本内容
        print(f"  {child.tag}:{child.text}")
    print()

# 说明:方法2:使用 findall() 查找所有匹配的元素
# findall() 返回所有匹配的子元素列表
print("方法2:使用 findall() 查找所有 price 元素")
prices = root.findall(".//price")  # .//price 表示查找所有 price 元素(无论在哪个层级)
for price in prices:
    print(f"价格:{price.text}")

# 说明:方法3:递归遍历所有元素
print("\n方法3:递归遍历所有元素")
def print_elements(element, indent=0):
    """
    递归打印所有元素

    参数:
        element: Element 对象
        indent: 缩进级别
    """
    # 说明:打印当前元素的信息
    # "  " * indent 创建缩进字符串
    print("  " * indent + f"<{element.tag}>")

    # 说明:如果有文本内容,打印文本
    if element.text and element.text.strip():
        print("  " * (indent + 1) + element.text.strip())

    # 说明:递归遍历所有子元素
    for child in element:
        print_elements(child, indent + 1)

    # 说明:打印结束标签
    print("  " * indent + f"</{element.tag}>")

# 说明:调用递归函数打印所有元素
print_elements(root)

6. XPath 查询:强大的元素定位工具 #

XPath 是 lxml 最强大的功能之一,它提供了一种简洁的方式来定位和选择 XML/HTML 元素。掌握 XPath 可以大大提高数据提取的效率。

6.1 XPath 基础语法 #

方法 功能说明 常用参数 返回值
.xpath() 使用 XPath 表达式查找元素 xpath:XPath 表达式字符串 结果列表(元素、文本或属性值)
# -*- coding: utf-8 -*-
# 说明:演示 XPath 的基本语法和用法

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备一个示例 XML 字符串
xml = """
<library>
    <book category="programming">
        <title lang="zh">流畅的 Python</title>
        <price>79</price>
    </book>
    <book category="fiction">
        <title lang="en">1984</title>
        <price>59</price>
    </book>
    <book category="programming">
        <title lang="zh">Python 数据分析</title>
        <price>89</price>
    </book>
</library>
"""

# 说明:解析 XML 字符串
root = etree.fromstring(xml)

# 说明:XPath 表达式1:查找所有 title 元素的文本
# //title 表示查找所有 title 元素(无论在文档的哪个位置)
# /text() 表示获取元素的文本内容
titles = root.xpath("//title/text()")
print("所有标题:", titles)

# 说明:XPath 表达式2:按属性筛选
# //title[@lang="zh"] 表示查找 lang 属性值为 "zh" 的 title 元素
# @lang 表示 lang 属性,[@lang="zh"] 是属性条件
cn_titles = root.xpath('//title[@lang="zh"]/text()')
print("\n中文标题:", cn_titles)

# 说明:XPath 表达式3:按元素内容筛选
# //book[price>60] 表示查找 price 子元素的值大于 60 的 book 元素
# [price>60] 是条件表达式
expensive = root.xpath('//book[price>60]/title/text()')
print("\n价格>60 的书:", expensive)

# 说明:XPath 表达式4:查找特定位置的元素
# //book[1] 表示查找第一个 book 元素(索引从 1 开始)
first_book = root.xpath("//book[1]/title/text()")
print("\n第一本书:", first_book)

# 说明:XPath 表达式5:查找所有 book 的 category 属性
# //book/@category 表示查找所有 book 元素的 category 属性
categories = root.xpath("//book/@category")
print("\n所有类别:", categories)

6.2 常用的 XPath 语法 #

下面是一个更全面的 XPath 示例,展示各种常用的查询方式。

XPath 表达式 功能说明 示例
//tag 查找所有 tag 标签(无论在哪个位置) //book 查找所有 book 元素
tag 查找当前元素下的 tag 标签 title 查找直接子元素 title
[@attr="value"] 按属性筛选 [@id="1"] 查找 id 属性为 "1" 的元素
/text() 获取元素的文本内容 //title/text() 获取所有 title 的文本
[@attr] 查找有某个属性的元素 [@class] 查找有 class 属性的元素
[condition] 添加条件 [price>60] 查找 price 值大于 60 的元素
tag[@attr] 查找有特定属性的标签 book[@category] 查找有 category 属性的 book
# -*- coding: utf-8 -*-
# 说明:演示更多 XPath 语法和用法

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备一个更复杂的示例 XML
xml = """
<library>
    <section name="编程">
        <book id="1" category="programming">
            <title lang="zh">流畅的 Python</title>
            <price>79</price>
            <author>Luciano Ramalho</author>
        </book>
        <book id="2" category="programming">
            <title lang="zh">Python 数据分析</title>
            <price>89</price>
            <author>Wes McKinney</author>
        </book>
    </section>
    <section name="小说">
        <book id="3" category="fiction">
            <title lang="en">1984</title>
            <price>59</price>
            <author>George Orwell</author>
        </book>
    </section>
</library>
"""

# 说明:解析 XML 字符串
root = etree.fromstring(xml)

# 说明:查找所有有 id 属性的 book 元素
# //book[@id] 表示查找所有有 id 属性的 book 元素
books_with_id = root.xpath("//book[@id]")
print("有 ID 的书数量:", len(books_with_id))

# 说明:查找 id 属性为 "1" 的 book 的标题
# //book[@id="1"] 表示查找 id 属性为 "1" 的 book 元素
book_title = root.xpath('//book[@id="1"]/title/text()')
print("ID 为 1 的书标题:", book_title)

# 说明:查找所有 section 的 name 属性
# //section/@name 表示查找所有 section 元素的 name 属性
section_names = root.xpath("//section/@name")
print("\n所有章节名称:", section_names)

# 说明:查找第一个 section 下的所有 book
# //section[1]/book 表示查找第一个 section 下的 book 子元素
first_section_books = root.xpath("//section[1]/book/title/text()")
print("\n第一个章节的书:", first_section_books)

# 说明:查找价格在 60 到 90 之间的书
# //book[price>=60 and price<=90] 表示查找 price 在指定范围内的 book
# and 是逻辑与运算符
affordable_books = root.xpath('//book[price>=60 and price<=90]/title/text()')
print("\n价格在 60-90 之间的书:", affordable_books)

# 说明:查找有 author 子元素的书
# //book[author] 表示查找有 author 子元素的 book
books_with_author = root.xpath("//book[author]/title/text()")
print("\n有作者信息的书:", books_with_author)

7. 创建和生成 XML #

除了解析 XML,lxml 还可以从头创建 XML 文档。这在需要生成配置文件或数据文件时非常有用。

7.1 创建 XML 元素 #

方法 功能说明 常用参数 返回值
etree.Element() 创建根元素 tag:标签名,**attrib:属性(可选) Element 对象
etree.SubElement() 创建并添加子元素 parent:父元素,tag:标签名,**attrib:属性(可选) 新创建的 Element 对象
.text 设置元素的文本内容 无(属性),可直接赋值 无
etree.tostring() 将 Element 对象转换为 XML 字符串 element:Element 对象,encoding:编码,pretty_print:是否格式化,xml_declaration:是否包含 XML 声明 XML 字符串
# -*- coding: utf-8 -*-
# 说明:演示如何从零创建 XML 文档

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:创建根元素
# Element() 函数创建根元素,参数是标签名
catalog = etree.Element("catalog")

# 说明:创建第一个 book 子元素并添加属性
# SubElement() 函数创建子元素并自动添加到父元素中
# 参数:父元素、标签名、属性(可选)
book1 = etree.SubElement(catalog, "book", id="1001")

# 说明:创建 title 子元素
title1 = etree.SubElement(book1, "title")

# 说明:设置 title 元素的文本内容
# text 属性可以直接设置元素的文本
title1.text = "爬虫实战"

# 说明:创建 author 子元素并设置文本
author1 = etree.SubElement(book1, "author")
author1.text = "张三"

# 说明:创建第二个 book 子元素
book2 = etree.SubElement(catalog, "book", id="1002")
title2 = etree.SubElement(book2, "title")
title2.text = "数据分析"

author2 = etree.SubElement(book2, "author")
author2.text = "李四"

# 说明:将 Element 对象转换为格式化的 XML 字符串
# tostring() 函数将 Element 对象转换回 XML 字符串
# encoding="unicode" 表示返回 Unicode 字符串(而不是字节)
# pretty_print=True 表示格式化输出(添加缩进和换行,使 XML 更易读)
# xml_declaration=True 表示在开头添加 XML 声明(<?xml version="1.0"?>)
xml_str = etree.tostring(
    catalog,
    encoding="unicode",
    pretty_print=True,
    xml_declaration=True
)

# 说明:打印生成的 XML 字符串
print("生成的 XML:")
print(xml_str)

7.2 保存 XML 到文件 #

方法 功能说明 常用参数 返回值
etree.ElementTree() 创建 ElementTree 对象(用于文件操作) element:根元素 ElementTree 对象
.write() 将 XML 写入文件 file:文件路径或文件对象,encoding:编码,pretty_print:是否格式化,xml_declaration:是否包含 XML 声明 无
# -*- coding: utf-8 -*-
# 说明:演示如何将 XML 保存到文件

# 说明:导入 lxml 的 etree 模块
from lxml import etree
import os

# 说明:创建 XML 结构
catalog = etree.Element("catalog")
book = etree.SubElement(catalog, "book", id="1001")
title = etree.SubElement(book, "title")
title.text = "爬虫实战"
author = etree.SubElement(book, "author")
author.text = "张三"

# 说明:方法1:使用 tostring() 和文件写入
# 先将 Element 转换为字符串,再写入文件
xml_str = etree.tostring(
    catalog,
    encoding="unicode",
    pretty_print=True,
    xml_declaration=True
)

# 说明:打开文件并写入 XML 字符串
# encoding="utf-8" 指定文件编码为 UTF-8
with open("catalog.xml", "w", encoding="utf-8") as f:
    f.write(xml_str)

print("XML 已保存到 catalog.xml(方法1)")

# 说明:方法2:使用 ElementTree 的 write() 方法
# ElementTree 是 Element 的包装类,提供了文件操作方法
tree = etree.ElementTree(catalog)
tree.write(
    "catalog2.xml",
    encoding="utf-8",
    pretty_print=True,
    xml_declaration=True
)

print("XML 已保存到 catalog2.xml(方法2)")

# 说明:验证文件是否创建成功
if os.path.exists("catalog.xml"):
    print("\n✓ catalog.xml 文件创建成功")
    # 说明:读取并显示文件内容
    with open("catalog.xml", "r", encoding="utf-8") as f:
        content = f.read()
        print("文件内容:")
        print(content)

# 说明:清理临时文件(可选)
if os.path.exists("catalog.xml"):
    os.remove("catalog.xml")
if os.path.exists("catalog2.xml"):
    os.remove("catalog2.xml")

8. HTML 解析与清洗 #

lxml 不仅可以解析 XML,还可以解析 HTML。对于处理网页数据,HTML 解析功能非常有用。

8.1 解析 HTML #

方法 功能说明 常用参数 返回值
html.fromstring() 从字符串解析 HTML html_text:HTML 字符串 Element 对象(根节点)
.xpath() 使用 XPath 查询元素 xpath:XPath 表达式 结果列表
html.tostring() 将 Element 对象转换为 HTML 字符串 element:Element 对象,encoding:编码,pretty_print:是否格式化 HTML 字符串
# -*- coding: utf-8 -*-
# 说明:演示如何解析 HTML

# 说明:导入 lxml 的 html 模块
from lxml import html

# 说明:准备一个示例 HTML 字符串
# 这是一个简单的网页片段,包含标题、段落和链接
html_text = """
<html>
<body>
    <h1>欢迎</h1>
    <p class="intro">这是一个示例网页</p>
    <ul>
        <li><a href="/home">首页</a></li>
        <li><a href="/about">关于</a></li>
    </ul>
</body>
</html>
"""

# 说明:使用 fromstring() 解析 HTML 字符串
# html.fromstring() 专门用于解析 HTML,可以处理格式不完美的 HTML
doc = html.fromstring(html_text)

# 说明:使用 XPath 查找标题文本
# //h1 表示查找所有 h1 标签,/text() 表示获取文本内容
h1_text = doc.xpath("//h1/text()")
print("标题:", h1_text)

# 说明:使用 XPath 查找所有链接
# //a 表示查找所有 a 标签
links = doc.xpath("//a")
print("\n所有链接:")
for link in links:
    # 说明:text 属性获取链接文本
    text = link.text
    # 说明:get() 方法获取属性值
    href = link.get("href")
    print(f"  文本:{text},地址:{href}")

# 说明:使用 XPath 查找特定 class 的段落
# //p[@class="intro"] 表示查找 class 属性为 "intro" 的 p 标签
intro_paragraph = doc.xpath('//p[@class="intro"]/text()')
print("\n介绍段落:", intro_paragraph)

# 说明:将 HTML 转换为字符串
html_str = html.tostring(doc, encoding="unicode", pretty_print=True)
print("\n转换后的 HTML:")
print(html_str)

8.2 HTML 清洗 #

HTML 清洗可以去除不需要的内容(如脚本、样式),让 HTML 更干净、更安全。

方法 功能说明 常用参数 返回值
Cleaner() 创建 HTML 清洗器 scripts:是否移除脚本,javascript:是否移除 JavaScript,style:是否移除样式 Cleaner 对象
.clean_html() 清洗 HTML 元素 doc:Element 对象 清洗后的 Element 对象
# -*- coding: utf-8 -*-
# 说明:演示如何清洗 HTML(移除脚本和样式)

# 说明:导入 lxml 的 html 模块和 Cleaner 类
from lxml import html
from lxml.html.clean import Cleaner

# 说明:准备一个包含脚本和不完整标签的 HTML
# 这个 HTML 包含可能不安全的脚本和格式问题
dirty_html = """
<html>
<body>
    <h1>示例网页</h1>
    <p>这是一段内容</p>
    <script>alert('这是恶意脚本')</script>
    <style>body { color: red; }</style>
    <p>这是另一段内容
    <div>未闭合的 div
</body>
</html>
"""

# 说明:创建 HTML 清洗器
# Cleaner 可以移除 HTML 中的不安全内容和格式问题
cleaner = Cleaner(
    scripts=True,      # 移除 <script> 标签及其内容
    javascript=True,   # 移除 JavaScript 代码(包括内联的)
    style=True,        # 移除 <style> 标签及其内容
    comments=True,     # 移除 HTML 注释
    page_structure=True,  # 修复页面结构问题
    safe_attrs_only=True  # 只保留安全的属性
)

# 说明:先解析 HTML
# fromstring() 可以自动修复一些格式问题
doc = html.fromstring(dirty_html)

# 说明:清洗 HTML
# clean_html() 方法返回清洗后的 Element 对象
clean_doc = cleaner.clean_html(doc)

# 说明:将清洗后的 HTML 转换为字符串并打印
# tostring() 将 Element 对象转换回 HTML 字符串
clean_html_str = html.tostring(clean_doc, encoding="unicode", pretty_print=True)
print("清洗后的 HTML:")
print(clean_html_str)

# 说明:验证脚本和样式是否被移除
# 检查清洗后的 HTML 中是否还有 script 和 style 标签
if "script" not in clean_html_str.lower() and "style" not in clean_html_str.lower():
    print("\n✓ 脚本和样式已成功移除")

9. 从文件读取 XML #

在实际应用中,XML 数据通常存储在文件中。lxml 提供了多种方式来读取文件。

9.1 读取 XML 文件 #

方法 功能说明 常用参数 返回值
etree.parse() 从文件解析 XML source:文件路径或文件对象 ElementTree 对象
.getroot() 获取根元素 无 Element 对象(根节点)
# -*- coding: utf-8 -*-
# 说明:演示如何从文件读取和解析 XML

# 说明:导入 lxml 的 etree 模块
from lxml import etree
import os

# 说明:先创建一个示例 XML 文件
xml_content = """<?xml version="1.0" encoding="utf-8"?>
<books>
    <book id="1">
        <title>Python 入门</title>
        <price>39.9</price>
    </book>
    <book id="2">
        <title>数据科学</title>
        <price>49.9</price>
    </book>
</books>
"""

# 说明:将 XML 内容写入文件
# 创建或覆盖文件并写入 XML 内容
with open("books.xml", "w", encoding="utf-8") as f:
    f.write(xml_content)

print("XML 文件已创建:books.xml")

# 说明:方法1:使用 parse() 从文件解析 XML
# parse() 函数从文件路径或文件对象解析 XML
# 返回 ElementTree 对象
tree = etree.parse("books.xml")

# 说明:获取根元素
# getroot() 方法返回根元素对象
root = tree.getroot()

# 说明:提取所有书籍信息
print("\n所有书籍:")
for book in root.findall("book"):
    title = book.findtext("title")
    price = book.findtext("price")
    book_id = book.get("id")
    print(f"  ID:{book_id},标题:{title},价格:{price}")

# 说明:方法2:先读取文件内容,再用 fromstring() 解析
# 这种方式适合需要预处理文件内容的情况
with open("books.xml", "r", encoding="utf-8") as f:
    xml_text = f.read()

# 说明:从字符串解析 XML
root2 = etree.fromstring(xml_text)

# 说明:使用 XPath 提取所有标题
titles = root2.xpath("//title/text()")
print(f"\n所有标题:{titles}")

# 说明:清理临时文件(可选)
if os.path.exists("books.xml"):
    os.remove("books.xml")
    print("\n临时文件已清理")

10. 实战案例:RSS 解析器 #

下面是一个完整的实战案例,封装成一个 RSS 解析器类,可以解析 RSS 格式的订阅源。

方法 功能说明 常用参数 返回值
etree.fromstring() 从字符串解析 XML text:XML 字符串 Element 对象
.find() 查找第一个匹配的子元素 tag:标签名 Element 对象
.findtext() 查找第一个匹配的子元素并返回其文本 tag:标签名 文本字符串
.findall() 查找所有匹配的子元素 tag:标签名 Element 对象列表
# -*- coding: utf-8 -*-
# 说明:实战案例 - RSS 解析器
# 封装一个完整的 RSS 解析器类,可以解析 RSS 订阅源

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:定义 RSS 解析器类
class SimpleRSSParser:
    """
    简单的 RSS 解析器

    功能:
    - 解析 RSS 2.0 格式的订阅源
    - 提取频道信息和文章列表
    """

    # 说明:初始化方法
    def __init__(self):
        """
        初始化解析器
        """
        # 说明:命名空间字典(用于处理带命名空间的 XML)
        # 目前为空,如果遇到带命名空间的 RSS 可以添加
        self.ns = {}

    # 说明:解析 RSS 字符串的方法
    def parse(self, rss_text: str):
        """
        解析 RSS 字符串并返回结构化数据

        参数:
            rss_text (str): RSS 格式的 XML 字符串

        返回:
            dict: 包含频道信息和文章列表的字典
        """
        # 说明:使用 fromstring() 解析 RSS 字符串
        # 返回根元素对象
        root = etree.fromstring(rss_text)

        # 说明:查找 channel 元素
        # RSS 2.0 格式中,channel 元素包含所有信息
        channel = root.find("channel")

        # 说明:提取频道基本信息
        # 使用 findtext() 方法查找并提取文本
        result = {
            "title": channel.findtext("title"),      # 频道标题
            "link": channel.findtext("link"),        # 频道链接
            "description": channel.findtext("description"),  # 频道描述
            "items": []  # 文章列表
        }

        # 说明:遍历所有 item 元素(文章条目)
        # findall() 方法返回所有匹配的子元素列表
        for item in channel.findall("item"):
            # 说明:提取每篇文章的信息
            # 使用 findtext() 提取文本内容
            article = {
                "title": item.findtext("title"),      # 文章标题
                "link": item.findtext("link"),        # 文章链接
                "description": item.findtext("description"),  # 文章描述
                "pubDate": item.findtext("pubDate")   # 发布日期
            }

            # 说明:将文章信息添加到列表中
            result["items"].append(article)

        # 说明:返回解析结果
        return result

    # 说明:获取文章数量的方法
    def get_item_count(self, rss_text: str):
        """
        获取 RSS 中的文章数量

        参数:
            rss_text (str): RSS 格式的 XML 字符串

        返回:
            int: 文章数量
        """
        # 说明:解析 RSS
        root = etree.fromstring(rss_text)

        # 说明:使用 XPath 统计 item 元素的数量
        # count() 是 XPath 函数,用于统计元素数量
        count = root.xpath("count(//item)")

        # 说明:返回整数形式的数量
        return int(count)


# 说明:主程序入口,演示如何使用
if __name__ == "__main__":
    # 说明:创建解析器实例
    parser = SimpleRSSParser()

    # 说明:示例 RSS 内容(RSS 2.0 格式)
    rss_demo = """<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>示例订阅</title>
    <link>https://example.com</link>
    <description>这是一个示例 RSS 订阅源</description>
    <item>
      <title>第一篇文章</title>
      <link>https://example.com/post1</link>
      <description>这是第一篇文章的内容</description>
      <pubDate>Mon, 01 Jan 2024 12:00:00 GMT</pubDate>
    </item>
    <item>
      <title>第二篇文章</title>
      <link>https://example.com/post2</link>
      <description>这是第二篇文章的内容</description>
      <pubDate>Tue, 02 Jan 2024 12:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>
"""

    # 说明:使用解析器解析 RSS
    feed = parser.parse(rss_demo)

    # 说明:打印频道信息
    print("频道信息:")
    print(f"  标题:{feed['title']}")
    print(f"  链接:{feed['link']}")
    print(f"  描述:{feed['description']}")

    # 说明:打印文章列表
    print(f"\n文章列表(共 {len(feed['items'])} 篇):")
    for idx, item in enumerate(feed["items"], 1):
        print(f"\n文章 {idx}:")
        print(f"  标题:{item['title']}")
        print(f"  链接:{item['link']}")
        print(f"  描述:{item['description']}")
        print(f"  发布日期:{item['pubDate']}")

    # 说明:使用 get_item_count() 方法获取文章数量
    count = parser.get_item_count(rss_demo)
    print(f"\n文章总数:{count}")

11. 性能优化:流式解析大型文件 #

当处理大型 XML 文件时,一次性加载整个文件可能会占用大量内存。lxml 提供了流式解析功能,可以逐元素处理,大大降低内存占用。

11.1 流式解析 #

方法 功能说明 常用参数 返回值
etree.iterparse() 流式解析 XML 文件(逐元素处理) source:文件路径或文件对象,tag:要处理的标签名(可选) 迭代器,返回 (事件, 元素) 元组
.clear() 清空元素及其子元素(释放内存) 无 无
# -*- coding: utf-8 -*-
# 说明:演示如何使用流式解析处理大型 XML 文件

# 说明:导入 lxml 的 etree 模块
from lxml import etree
import os

# 说明:先创建一个示例 XML 文件(模拟大型文件)
# 在实际场景中,这个文件可能包含数千或数万个元素
xml_content = """<?xml version="1.0" encoding="utf-8"?>
<books>
    <book>
        <title>Python 入门</title>
        <price>39.9</price>
    </book>
    <book>
        <title>数据科学</title>
        <price>49.9</price>
    </book>
    <book>
        <title>机器学习</title>
        <price>59.9</price>
    </book>
</books>
"""

# 说明:将 XML 内容写入文件
with open("large_books.xml", "w", encoding="utf-8") as f:
    f.write(xml_content)

print("XML 文件已创建:large_books.xml")

# 说明:定义流式解析函数
def stream_books(path: str):
    """
    流式解析 XML 文件中的 book 元素

    参数:
        path (str): XML 文件路径
    """
    # 说明:使用 iterparse() 流式解析 XML 文件
    # iterparse() 返回一个迭代器,每次返回 (事件, 元素) 元组
    # tag="book" 指定只处理 book 元素(提高效率)
    # events=("end",) 指定只处理元素结束事件(此时元素已完全解析)
    for event, elem in etree.iterparse(path, tag="book", events=("end",)):
        # 说明:提取元素信息
        # 此时 elem 是一个完整的 book 元素
        data = {
            "title": elem.findtext("title"),  # 查找 title 子元素并获取文本
            "price": elem.findtext("price")   # 查找 price 子元素并获取文本
        }

        # 说明:处理数据(这里只是打印,实际使用时可以进行数据库写入等操作)
        print("流式读取:", data)

        # 说明:清空元素(释放内存)
        # clear() 方法清空元素及其所有子元素和属性
        # 这对于流式解析非常重要,可以防止内存占用不断增长
        elem.clear()

# 说明:调用流式解析函数
print("\n开始流式解析:")
stream_books("large_books.xml")

# 说明:清理临时文件(可选)
if os.path.exists("large_books.xml"):
    os.remove("large_books.xml")
    print("\n临时文件已清理")

12. 常见问题与排查 #

在使用 lxml 时,可能会遇到一些常见问题。本节提供解决方案。

12.1 XML 语法错误处理 #

问题:XML 格式不正确导致解析失败。

解决方案:

方法 功能说明 常用参数 返回值
etree.XMLParser() 创建 XML 解析器 recover:是否尝试修复错误(True/False) XMLParser 对象
.parse() 使用解析器解析文件 source:文件路径或文件对象,parser:解析器对象 ElementTree 对象
# -*- coding: utf-8 -*-
# 说明:演示如何处理 XML 语法错误

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备一个有错误的 XML(缺少结束标签)
bad_xml = """
<books>
    <book id="1">
        <title>Python 入门</title>
        <price>39.9</price>
    <!-- 缺少 </book> 标签 -->
</books>
"""

# 说明:方法1:使用 recover=True 尝试自动修复
# XMLParser(recover=True) 创建一个会尝试修复错误的解析器
parser = etree.XMLParser(recover=True)

try:
    # 说明:使用 recover 解析器解析 XML
    # recover=True 会尝试修复一些常见的 XML 错误
    root = etree.fromstring(bad_xml, parser=parser)
    print("XML 已修复并解析成功")

    # 说明:检查是否有错误日志
    # error_log 包含解析过程中遇到的错误信息
    if parser.error_log:
        print("解析过程中发现的错误:")
        for error in parser.error_log:
            print(f"  {error}")

except etree.XMLSyntaxError as e:
    # 说明:捕获 XML 语法错误
    print(f"XML 语法错误:{e}")

# 说明:方法2:先验证 XML 格式
good_xml = """
<books>
    <book id="1">
        <title>Python 入门</title>
        <price>39.9</price>
    </book>
</books>
"""

# 说明:使用标准解析器解析正确的 XML
try:
    root = etree.fromstring(good_xml)
    print("\n正确的 XML 解析成功")
except etree.XMLSyntaxError as e:
    print(f"XML 语法错误:{e}")

12.2 编码问题 #

问题:解析 XML/HTML 时出现乱码。

解决方案:

# -*- coding: utf-8 -*-
# 说明:演示如何处理编码问题

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备包含中文的 XML
xml_utf8 = """<?xml version="1.0" encoding="utf-8"?>
<books>
    <book>
        <title>Python 入门教程</title>
        <price>39.9</price>
    </book>
</books>
"""

# 说明:方法1:直接解析(lxml 会自动处理编码)
# fromstring() 会自动识别 XML 声明中的编码
root = etree.fromstring(xml_utf8)
title = root.findtext("book/title")
print("标题(方法1):", title)

# 说明:方法2:如果 XML 声明中没有指定编码,可以手动指定
xml_no_encoding = """
<books>
    <book>
        <title>Python 入门教程</title>
    </book>
</books>
"""

# 说明:先解码为字符串,再解析
# 如果是字节串,需要先解码
xml_bytes = xml_no_encoding.encode("utf-8")
root2 = etree.fromstring(xml_bytes.decode("utf-8"))
title2 = root2.findtext("book/title")
print("标题(方法2):", title2)

12.3 命名空间处理 #

问题:XML 中包含命名空间,XPath 查询找不到元素。

解决方案:

方法 功能说明 常用参数 返回值
.xpath() 使用 XPath 查询元素 xpath:XPath 表达式,namespaces:命名空间字典 结果列表
# -*- coding: utf-8 -*-
# 说明:演示如何处理带命名空间的 XML

# 说明:导入 lxml 的 etree 模块
from lxml import etree

# 说明:准备带命名空间的 XML
# xmlns="http://example.com/ns" 定义了默认命名空间
xml_with_ns = """<?xml version="1.0" encoding="utf-8"?>
<books xmlns="http://example.com/ns">
    <book id="1">
        <title>Python 入门</title>
        <price>39.9</price>
    </book>
    <book id="2">
        <title>数据科学</title>
        <price>49.9</price>
    </book>
</books>
"""

# 说明:解析 XML
root = etree.fromstring(xml_with_ns)

# 说明:定义命名空间字典
# 键是命名空间前缀(可以是任意名称),值是命名空间 URI
namespaces = {
    "bk": "http://example.com/ns"  # 定义 bk 前缀指向命名空间 URI
}

# 说明:使用命名空间查询元素
# //bk:book 表示查找命名空间为 "http://example.com/ns" 的 book 元素
# namespaces=namespaces 指定命名空间字典
books = root.xpath("//bk:book", namespaces=namespaces)
print(f"找到 {len(books)} 本书")

# 说明:提取书名
titles = root.xpath("//bk:title/text()", namespaces=namespaces)
print("所有书名:", titles)

# 说明:获取命名空间 URI
# namespace-uri() 是 XPath 函数,用于获取元素的命名空间
ns_uri = root.xpath("namespace-uri(.)")
print(f"\n根元素的命名空间:{ns_uri}")

12.4 HTML 动态内容 #

问题:网页内容由 JavaScript 动态生成,lxml 无法获取。

说明:lxml 只能解析静态 HTML,无法执行 JavaScript。如果需要获取动态内容,需要配合 Selenium 或 Playwright 等工具。

# -*- coding: utf-8 -*-
# 说明:说明 HTML 动态内容的问题和解决方案

# 说明:导入 lxml 的 html 模块
from lxml import html

# 说明:示例:静态 HTML(可以直接解析)
static_html = """
<html>
<body>
    <div id="content">这是静态内容</div>
</body>
</html>
"""

# 说明:解析静态 HTML
doc = html.fromstring(static_html)

# 说明:提取内容
content = doc.xpath('//div[@id="content"]/text()')
print("静态内容:", content)

# 说明:说明动态内容的问题
print("\n注意:如果网页内容由 JavaScript 动态生成,")
print("lxml 无法获取这些内容,因为 lxml 不执行 JavaScript。")
print("解决方法:使用 Selenium 或 Playwright 先渲染页面,再获取 HTML。")

# 说明:示例代码结构(需要安装 selenium)
print("\n使用 Selenium 的示例代码结构:")
print("""
from selenium import webdriver
from lxml import html

# 启动浏览器并加载页面
driver = webdriver.Chrome()
driver.get("https://example.com")

# 获取渲染后的 HTML
html_text = driver.page_source

# 使用 lxml 解析
doc = html.fromstring(html_text)
# 现在可以提取动态生成的内容了
""")

13. 参考 #

  • lxml 官方文档
  • XPath 教程
  • BeautifulSoup 教程
  • requests 教程

访问验证

请输入访问令牌

Token不正确,请重新输入