Python3 第三方库 xmltodict 与 lxml

🎉摘要:本文详细介绍了Python中两款强大的XML处理第三方库:xmltodict和lxml。xmltodict可将XML与Python字典互转,简化开发;lxml提供高性能XML解析、完整XPath支持及CSS选择器,适合处理大型XML文件。涵盖安装、基础用法、属性处理、命名空间及实战案例。

Python 标准库的 XML 工具已足够应对大多数场景,但在特定需求下,第三方库能显著提升开发效率和处理能力。本章介绍两款常用工具:xmltodict(简化开发)和 lxml(高性能处理)。

xmltodict:XML 与字典的桥梁

xmltodict 将 XML 转换为 Python 字典,利用字典操作处理 XML,大幅降低学习成本。

安装

使用 pip install 命令安装 xmltodict 库,如下:

pip install xmltodict

安装日志:

(base) PS D:\> pip install xmltodict
Defaulting to user installation because normal site-packages is not writeable
Collecting xmltodict
  Downloading xmltodict-1.0.4-py3-none-any.whl.metadata (14 kB)
Downloading xmltodict-1.0.4-py3-none-any.whl (13 kB)
Installing collected packages: xmltodict
Successfully installed xmltodict-1.0.4

使用如下命令验证依赖安装是否正确:

(base) PS D:\$share_dir> python
Python 3.13.9 | packaged by Anaconda, Inc. | (main, Oct 21 2025, 19:09:58) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
Ctrl click to launch VS Code Native REPL
>>> import xmltodict

如果 import 没有抛出任何错误,说明安装成功。下面通过实例来介绍 xmltodict 库的用法

XML 转字典

使用 xmltodict.parse(XML 字符串) 方法快速将 XML 转换为 Python 字典类型,例如:

import xmltodict

xml_data = '''<?xml version="1.0"?>
<library>
    <name>市图书馆</name>
    <books>
        <book id="1">
            <title>Python编程</title>
            <author>张三</author>
            <price>59.00</price>
        </book>
        <book id="2">
            <title>数据分析</title>
            <author>李四</author>
            <price>89.00</price>
        </book>
    </books>
</library>
'''

# 解析为字典
data = xmltodict.parse(xml_data)

# 查看类型
print(type(data))  # <class 'dict'>
# 查看字典的键
print(data.keys())  # dict_keys(['library'])

# 访问数据
library = data['library']
print(library['name'])  # 市图书馆

# 访问书籍列表
books = library['books']['book']
print(f"共 {len(books)} 本书")

for book in books:
    print(f"  {book['title']} ({book['author']}) - ¥{book['price']}")
    print(f"    ID: {book['@id']}")  # 属性以 @ 开头

运行示例,输出如下:

<class 'dict'>
dict_keys(['library'])
市图书馆
共 2 本书
  Python编程 (张三) - ¥59.00
    ID: 1
  数据分析 (李四) - ¥89.00
    ID: 2

字典转 XML

使用 xmltodict.unparse() 方法将 Python 字典对象快速转换为 XML 字符串。

方法定义如下:

xmltodict.unparse(
    data,               # 字典对象
    pretty=True,        # 是否美化缩进
    indent="    ",      # 自定义缩进符,默认4空格
    newl="\n",          # 换行符
    full_document=True, # 是否头部带 <?xml ...> 声明
    encoding="utf-8"    # xml头部编码
)

示例代码:

import xmltodict

# 构建数据结构
data = {
    'order': {
        '@id': 'ORD-2024-001',
        '@date': '2024-01-15',
        'customer': '张三',
        'items': {
            'item': [
                {
                    '@sku': 'ITEM-001',
                    'name': '机械键盘',
                    'qty': 1,
                    'price': 299.00
                },
                {
                    '@sku': 'ITEM-002',
                    'name': '鼠标垫',
                    'qty': 2,
                    'price': 29.00
                }
            ]
        },
        'total': 357.00
    }
}

# 转换为 XML
xml_output = xmltodict.unparse(data, pretty=True)
print(xml_output)

注意,上述字典对象中,以 @ 开头的字段将当做 XML 文档中某个标签的属性。例如,@id、@date 当做 order 标签的属性。

运行示例,输出如下:

<?xml version="1.0" encoding="utf-8"?>
<order id="ORD-2024-001" date="2024-01-15">
        <customer>张三</customer>
        <items>
                <item sku="ITEM-001">
                        <name>机械键盘</name>
                        <qty>1</qty>
                        <price>299.0</price>
                </item>
                <item sku="ITEM-002">
                        <name>鼠标垫</name>
                        <qty>2</qty>
                        <price>29.0</price>
                </item>
        </items>
        <total>357.0</total>
</order>

处理属性与列表

xmltodict 库规定,XML 标签上的属性会统一用“@属性名”作为字典 key,标签中间文本用“#text”作为字典的 KEY。

示例 1:解析带属性单节点 XML

import xmltodict

xml_str = '''
<setting name="theme" type="str">dark</setting>
'''
d = xmltodict.parse(xml_str)
print(d)
# 输出:
# {'setting': {'@name': 'theme', '@type': 'str', '#text': 'dark'}}

# 取值
name = d["setting"]["@name"]
val = d["setting"]["#text"]
print("name:", name, "value:", val)

运行实例,输出:

{'setting': {'@name': 'theme', '@type': 'str', '#text': 'dark'}}
name: theme value: dark

示例 2: 使用 unparse() 将字典对象反向还原为 XML

import xmltodict

data = {
    "setting": {
        "@name": "language",  	# 属性
        "@type": "str",			# 属性
        "#text": "zh-CN"		# 标签内部文本
    }
}
xml_out = xmltodict.unparse(data, pretty=True)
print(xml_out)

运行实例,输出如下:

<?xml version="1.0" encoding="utf-8"?>
<setting name="language" type="str">zh-CN</setting>

列表自动 / 强制规则 force_list

默认情况下,xmltodict 对一个元素和多个元素返回的类型不一致,规则如下:

  • 同层级多个同名标签:自动解析为 list

  • 同层级单个同名标签:默认解析为 dict,不会生成列表

例如,<students>下面存在一个 <student> 时返回字典类型,如果存在多个 <student> 时返回列表类型。这对程序处理不友好,一不小心就会出现错误。为了解决这个问题,可以使用 force_list:

  • force_list=("标签名",):强制该标签无论几条都转为列表,解决单条时类型不统一的坑

示例代码:

import xmltodict

# 默认情况下,单个元素不会变成列表
xml_data = '''<?xml version="1.0"?>
<config>
    <setting name="theme">dark</setting>
</config>
'''
data = xmltodict.parse(xml_data)
print(type(data['config']['setting']))  # <class 'dict'> - 只有一条时不是列表

# 使用 force_list 强制指定标签为列表
data = xmltodict.parse(xml_data, force_list=('setting',))
print(type(data['config']['setting']))  # <class 'list'>

# 多个元素,默认会解析为列表
xml_data = '''<?xml version="1.0"?>
<config>
    <setting name="theme">dark</setting>
    <setting name="language">zh-CN</setting>
</config>
'''
data = xmltodict.parse(xml_data)
print(type(data['config']['setting']))  # <class 'list'>
print(len(data['config']['setting']))   # 2

运行示例,输出如下:

<class 'dict'>
<class 'list'>
<class 'list'>
2

处理命名空间

什么是命名空间前面已经介绍了,不在赘述。默认情况下,直接 xmltodict.parse(xml_str) 得到字典键会带前缀,例如:

import xmltodict

xml_data = '''<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:media="http://search.yahoo.com/mrss/">
    <title>我的博客</title>
    <media:thumbnail url="thumb.jpg"/>
</feed>
'''

data = xmltodict.parse(xml_data)
print(data)

运行示例,输出如下:

{'feed': {'@xmlns': 'http://www.w3.org/2005/Atom', '@xmlns:media': 'http://search.yahoo.com/mrss/', 'title': '我的博客', 'media:thumbnail': {'@url': 'thumb.jpg'}}}

输出的 key 中,“media:thumbnail ”携带了命名空间前缀。使用、取值极麻烦,xmltodict 提供两种方案处理命名空间。

自动剥离前缀(最常用)

在使用 xmltodict.parse() 解析 XML 时添加如下参数:

  • process_namespaces=True:自动移除命名空间前缀,把标签名统一为本地名

  • namespace_collapse:是否合并同名不同命名空间节点,默认 True

例如:

import xmltodict

xml_data = '''<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:media="http://search.yahoo.com/mrss/">
    <title>我的博客</title>
    <media:thumbnail url="thumb.jpg"/>
</feed>
'''

# 定义命名空间映射,值为None代表移除该命名空间
ns_map = {
    "http://www.w3.org/2005/Atom": None,
    "http://search.yahoo.com/mrss/": None
}

# 必须同时开 process_namespaces + 传入 namespaces
data = xmltodict.parse(xml_data, process_namespaces=True, namespaces=ns_map)
print(data)
print(data['feed']['title'])  # 输出: 我的博客

运行示例,输出如下:

{'feed': {'@xmlns': {'': 'http://www.w3.org/2005/Atom', 'media': 'http://search.yahoo.com/mrss/'}, 'title': '我的博客', 'thumbnail': {'@url': 'thumb.jpg'}}}
我的博客

此时,我们就可以直接取值 data['feed']['title']。

使用 namespaces 参数自定义别名映射

如果需要前缀,由于业务需要不能剥离。我们可以使用 namespaces 参数自定义别名映射,即使 XML 文档的命名空间前缀变了也不影响我们解析。

这种方式非常适合需要区分多个同名标签、不想完全去掉命名空间的场景,手动给 URI 绑定短别名。

例如:

import xmltodict

xml_data = '''<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:media="http://search.yahoo.com/mrss/">
    <title>我的博客</title>
    <media:thumbnail url="thumb.jpg"/>
</feed>
'''

# 定义命名空间映射,值为None代表移除该命名空间
ns_map = {
    "http://www.w3.org/2005/Atom": "a",
    "http://search.yahoo.com/mrss/": "m"
}

# 必须同时开 process_namespaces + 传入 namespaces
data = xmltodict.parse(xml_data, process_namespaces=True, namespaces=ns_map)
print(data)
print(data['a:feed']['a:title'])  # 输出: 我的博客

运行示例,输出如下:

{'a:feed': {'@xmlns': {'': 'http://www.w3.org/2005/Atom', 'media': 'http://search.yahoo.com/mrss/'}, 'a:title': '我的博客', 'm:thumbnail': {'@url': 'thumb.jpg'}}}
我的博客

lxml:高性能 XML 处理

lxml 基于 C 语言库 libxml2 和 libxslt,性能远超纯 Python 实现,同时支持 XPath 1.0/2.0、XSLT、Schema 验证等高级功能。

安装

使用 pip install 安装 lxml 库,如下:

pip install lxml

基础解析

下面演示 lxml 库如何快速解析 XML 文档,也分为从字符串解析和从文件解析:

(1)从字符串解析 XML,可用于通过 API 接口获取的 XML 内容,或者从数据库读取出来的 XML 字符串,例如:

from lxml import etree

# 从字符串解析
# 字符串可以来自远程API接口或数据库、文件内容
xml_data = '<root><item id="1">内容</item></root>'
root = etree.fromstring(xml_data.encode('utf-8'))
print(root.tag)  # root
print(root[0].get('id'))  # 1

(2)直接从文件解析 XML,即将 XML 内容保存到 XML 文档中,给一个文档地址,就可以进行解析,不要将文档内容读取出来进行解析。假如存在 simple.xml 文档,内容如下:

<?xml version="1.0"?>
<message id="1">
  <to>张三</to>
  <content>你好</content>
</message>

使用 lxml 解析该文档,如下:

from lxml import etree

# 从文件解析
tree = etree.parse('sample.xml')  # 返回 ElementTree
root = tree.getroot()

print(root.tag)  # message
print(root[0].text)  # 张三

运行示例,输出如下:

message
张三

完整 XPath 支持

lxml 支持完整的 XPath 1.0,功能远超 ElementTree。下面快速熟悉一下 XPath 1.0 的语法:

(1)节点选择语法

表达式含义
/root根节点,绝对路径
//product全局任意位置所有 product 节点
.当前节点
..父节点
./price当前节点下 price 子节点
@id当前节点 id 属性

(2)条件过滤,XPath1.0 支持数字、字符串、数值比较

表达式描述
//product[price > 10]价格大于10的商品
//product[stock = 0]库存等于0
//product[@type='fruit']属性匹配
//product[1]取第1个商品(下标从1开始)
//product[last()]最后一个商品
//name[contains(.,'苹果')]包含文本

(3)XPath 1.0 内置核心函数

字符串函数:

  • string(节点):转为字符串,如://book[string-length(string(price))=5]

  • concat(a,b,c):拼接字符串,如://book[concat(author,price)='曹雪芹29.99']

  • contains(str,sub):是否包含子串,如://book[contains(author,'芹')]

  • starts-with(str,pre):开头匹配,如://book[starts-with(author,'曹')]

  • substring(s,start,len):截取,起始位从 1 开始,如://book[substring(author,2,1)='雪']

  • string-length():字符串长度,例如://book[string-length(author)=3]

数值函数:

  • number(节点):文本转数字,空 / 非数字返回 0。例如://book[number(price)>0]

  • sum(节点集合):求和(只能单字段集合,不能相乘再求和)。例如:sum(//price)

  • floor() / ceiling() / round() 取整,例如:floor(sum(//price))、ceiling(sum(//price))、round(sum(//price))

  • + - * div mod 四则运算(除法用 div,不能用 /),例如:string(//book/year + //book/price)

布尔函数:

  • true() ,例如://book[true()]

  • false(),例如://book[not(false())]

  • not(条件) 取反,例如://book[not(false())]

节点集合函数:

  • last() 最后一个节点下标,如://book[last()][position()=last()]

  • position() 当前节点位置,如://book[1][position()=1]

  • count(节点) 统计总数,如:count(//book)

简单示例:

from lxml import etree

xml_data = '''
<products>
    <product id="1" category="electronics">
        <name>手机</name>
        <price>2999</price>
        <stock>100</stock>
    </product>
    <product id="2" category="clothing">
        <name>T恤</name>
        <price>99</price>
        <stock>500</stock>
    </product>
    <product id="3" category="electronics">
        <name>耳机</name>
        <price>599</price>
        <stock>200</stock>
    </product>
</products>
'''

root = etree.fromstring(xml_data.encode('utf-8'))

# 条件选择:价格大于1000的产品
expensive = root.xpath('//product[price > 1000]/name/text()')
print(f"高价产品: {expensive}")

# 选择具有特定属性的元素
electronics = root.xpath('//product[@category="electronics"]/name/text()')
print(f"电子产品: {electronics}")

# 选择第N个元素
second_product = root.xpath('//product[2]/name/text()')
print(f"第二个产品: {second_product}")

# 使用 contains() 进行模糊匹配
phones = root.xpath('//product[contains(name, "机")]/name/text()')
print(f"含'机'的产品: {phones}")

运行示例,输出如下:

高价产品: ['手机']
电子产品: ['手机', '耳机']
第二个产品: ['T恤']
含'机'的产品: ['手机', '耳机']

CSS 选择器

lxml 支持类似 jQuery 的 CSS 选择器,例如:

from lxml import etree
from lxml.cssselect import CSSSelector

xml_data = '''
<html>
    <body>
        <div class="product" data-id="1">产品A</div>
        <div class="product featured" data-id="2">产品B</div>
        <div class="product" data-id="3">产品C</div>
    </body>
</html>
'''

tree = etree.fromstring(xml_data.encode('utf-8'))

# 使用 CSS 选择器
sel = CSSSelector('.product.featured')
featured = sel(tree)
print(f"精选产品: {[e.text for e in featured]}")

# 多条件选择
all_products = CSSSelector('div.product')
print(f"所有产品数: {len(all_products(tree))}")

运行示例,输出如下:

精选产品: ['产品B']
所有产品数: 3

命名空间处理

和 xmltodict 类似,也支持自定义命名空间前缀,访问时就可以使用自定义的命名空间前缀固定访问,即使后续 XML 文档的前缀变了,命名空间地址没有变依然能够访问。如下:

from lxml import etree

# 注意,<?xml version="1.0"?> 必须放在第一行
# 否则会跑出如下错误:
# lxml.etree.XMLSyntaxError: XML declaration allowed only at the start of the document, line 2, column 6
xml_data = '''<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:m="http://search.yahoo.com/mrss/">
    <title>我的博客</title>
    <m:thumbnail url="thumb.jpg" width="100"/>
</feed>
'''

tree = etree.fromstring(xml_data.encode('utf-8'))

# 定义命名空间前缀
ns = {
    'atom': 'http://www.w3.org/2005/Atom',
    'media': 'http://search.yahoo.com/mrss/'
}

# 使用命名空间查询
title = tree.xpath('//atom:title/text()', namespaces=ns)
print(f"标题: {title}")

thumbnails = tree.xpath('//media:thumbnail/@url', namespaces=ns)
print(f"缩略图: {thumbnails}")

运行示例,输出如下:

标题: ['我的博客']
缩略图: ['thumb.jpg']

大型文件处理

大型文件处理

<?xml version="1.0" encoding="utf-8"?>
<logs>
  <!-- info 普通日志 -->
  <log level="info">系统启动成功,服务端口8080已监听</log>
  <log level="info">用户id=1001发起登录请求</log>
  <log>数据库连接池初始化完成(无level属性,默认info)</log>

  <!-- warning 警告日志 -->
  <log level="warning">内存占用超过80%,建议扩容服务器</log>
  <log level="warning">接口请求耗时大于2000ms,存在性能隐患</log>

  <!-- error 错误日志 -->
  <log level="error">数据库查询异常:Table 'user' not exists</log>
  <log level="error">文件上传失败,存储空间不足</log>
  <log level="error">第三方支付接口超时,重试3次仍失败</log>

  <!-- 可无限复制下方log节点模拟超大文件 -->
  <log level="info">定时任务001执行完成</log>
  <log level="warning">缓存命中率低于60%</log>
  <log level="error">Redis连接断开</log>
</logs>

lxml 同样支持 iterparse,但速度更快:

from lxml import etree

def fast_count_elements(filename, tag):
    """
    快速统计XML文件中指定标签的元素总数(流式解析,低内存占用)
    采用lxml.iterparse 流式逐行读取大文件,无需一次性加载整个XML到内存
    :param filename: str XML文件路径,支持超大XML文件
    :param tag: str 需要统计的目标标签名
    :return: int 匹配该标签的元素总个数
    """
    # 计数器,记录匹配标签的元素数量
    count = 0
    
    # 流式迭代解析XML,仅监听元素结束事件,只捕获指定tag节点
    for event, elem in etree.iterparse(filename, events=('end',), tag=tag):
        # 每匹配到一个目标标签,计数+1
        count += 1
        # 清空当前节点内部数据,释放内存
        elem.clear()
        # 循环删除当前节点之前的所有兄弟节点,彻底释放已解析资源,防止内存持续上涨
        while elem.getprevious() is not None:
            del elem.getparent()[0]
            
    return count


# 统计 huge_log.xml 文件中 <log> 标签的总条数
count = fast_count_elements('huge_log.xml', 'log')
print(f"共有 {count} 条记录")

运行示例,输出如下:

共有 11 条记录

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号