大家好,我是科雷! 我们在编写程序或者编写自动化用例时,经常会用到XML配置文件,而lxml作为性能卓越的解析库,凭借其高效、灵活的特性,成为众多开发者的首选工具。本文主要介绍如何使用lxml库来解析xml文件,帮助我们了解xml文件的结构和解析方式。
1.安装lxml库
通过pip命令安装:pip install lxml -i
https://mirrors.aliyun.com/pypi/simple/
2.XML解析函数
核心函数:
- etree.XML(text):解析XML文本,返回Element对象。
- etree.parse(file_or_url):从文件或URL解析XML文档。
案例:解析XML内容
from lxml import etree
xml = """
<config>
<server>
<host>localhost</host>
<port>8080</port>
</server>
<database>
<name>mydb</name>
<user>root</user>
<password>secret</password>
</database>
</config>
"""
# 解析XML
root = etree.XML(xml)
print(root.tag)
#输出: config
# 获取config的子元素
for child in root:
print(child.tag)
# 输出:server database
3.XPath选择器
XPath是XML路径语言,用于在XML文档中定位元素。在lxml中,通过Element.xpath()方法使用 XPath表达式,返回匹配的元素对象列表,匹配不到返回空列表。
基础语法:节点选择
表达式 | 功能描述 | 案例(基于上述 XML) | 结果 |
tag | 选择当前节点下的子元素 | root.xpath('server') | 匹配<server>元素(1 个) |
//tag | 选择文档中所有的tag元素(不管层级) | root.xpath('//port') | 匹配<port>元素(1 个) |
/ | 从根节点开始选择 | root.xpath('/config/server') | 从根节点匹配<server>元素 |
. | 选择当前节点 | server.xpath('./host') | 从<server>节点匹配子元素<host> |
.. | 选择当前节点的父节点 | host.xpath('..') | 匹配<host>的父节点<server> |
案例:基础节点选择
# 选择所有server元素
servers = root.xpath('//server')
print(len(servers)) # 输出:1
# 选择server的直接子元素host
hosts = root.xpath('//server/host')
print(hosts[0].text) # 输出:localhost
# 从根节点选择database
databases = root.xpath('/config/database')
print(databases[0].tag) # 输出:database
属性过滤:精确匹配元素
通过[@属性名=值]过滤带特定属性的元素,支持多条件组合。
表达式 | 功能描述 | 案例(基于上述XML) | 结果 |
tag[@attr] | 选择带attr属性的tag元素 | root.xpath('server[@id]') | 匹配带id属性的<server> |
tag[@attr='value'] | 选择attr属性为value的tag元素 | root.xpath('server[@id="1"]') | 匹配id="1"的<server> |
tag[@attr1][@attr2] | 多属性过滤(同时满足) | root.xpath('port[@protocol][@debug]') | 匹配同时带protocol和debug的<port> |
tag[@attr!='value'] | 排除属性值为value的元素 | root.xpath('server[@id!="2"]') | 排除id="2"的<server> |
案例:属性过滤
xml = """
<config>
<server id="1">
<host>localhost</host>
<port protocol="tcp">8080</port>
</server>
<server id="2">
<host>localhost</host>
<port protocol="udp">8081</port>
</server>
<database>
<name>mydb</name>
<user>root</user>
<password>secret</password>
</database>
</config>
"""
# 选择带protocol属性的port元素
ports = root.xpath('//port[@protocol]')
print(ports[0].text) # 输出:8080
# 选择protocol="tcp"的port元素
tcp_ports = root.xpath('//port[@protocol="udp"]')
print(tcp_ports[0].text) # 输出:8081
# 多条件过滤(假设port有debug属性)
# debug_ports = root.xpath('//port[@protocol="tcp"][@debug="true"]')
位置过滤:按索引选择
通过索引或逻辑条件选择特定位置的元素(XPath索引从1开始)。
表达式 | 功能描述 | 案例(假设有多个 server) | 结果 |
tag[n] | 选择第n个tag元素 | root.xpath('//server[1]') | 第一个<server> |
tag[last()] | 选择最后一个tag元素 | root.xpath('//server[last()]') | 最后一个<server> |
tag[position()<n] | 选择前n-1个tag元素 | root.xpath('//server[position()<3]') | 前2个<server> |
tag[last()-1] | 选择倒数第二个tag元素 | root.xpath('//server[last()-1]') | 倒数第二个<server> |
案例:位置过滤
# 假设XML中有多个server:<server id="1">、<server id="2">
# 选择第一个server
first_server = root.xpath('//server[1]')
print(first_server[0].get('id')) # 输出:1
# 选择最后一个server
last_server = root.xpath('//server[last()]')
print(last_server[0].get('id')) # 输出:2
文本过滤:按内容匹配
通过text()函数过滤元素文本内容,支持模糊匹配。
表达式 | 功能描述 | 案例(基于上述 XML) | 结果 |
tag[text()='value'] | 文本完全匹配value | root.xpath('//host[text()="localhost"]') | 匹配文本为localhost的<host> |
tag[contains(text(), 'sub')] | 文本包含sub子串 | root.xpath('//password[contains(text(), "sec")]') | 匹配文本含sec的<password> |
tag[starts-with(text(), 'pre')] | 文本以pre开头 | root.xpath('//name[starts-with(text(), "my")]') | 匹配文本以my开头的<name> |
案例:文本过滤
# 匹配host文本为localhost的元素
local_hosts = root.xpath('//host[text()="localhost"]')
print(local_hosts[0].text) # 输出:localhost
# 匹配name中包含"db"的元素
db_names = root.xpath('//name[contains(text(), "db")]')
print(db_names[0].text) # 输出:mydb
获取属性值与文本
通过@属性名提取属性值,通过text()提取文本内容。
表达式 | 功能描述 | 案例(基于上述 XML) | 结果 |
tag/@attr | 提取tag元素的attr属性值 | root.xpath('//server/@id') | 输出:['1'] |
tag/text() | 提取tag元素的文本内容 | root.xpath('//port/text()') | 输出:['8080'] |
//@attr | 提取文档中所有attr属性值 | root.xpath('//@protocol') | 输出:['tcp'] |
案例:提取属性与文本
# 提取所有server的id属性
server_ids = root.xpath('//server/@id')
print(server_ids) # 输出:['1', '2']
# 提取所有port的文本内容
port_values = root.xpath('//port/text()')
print(port_values) # 输出:['8080', '8081']
# 提取所有protocol属性值
protocols = root.xpath('//@protocol')
print(protocols) # 输出:['tcp', 'udp']
组合条件与运算符
支持and/or逻辑运算,以及>/<等比较运算。
表达式 | 功能描述 | 案例(假设 port 有数值) | 结果 |
tag[@a='x' and @b='y'] | 多属性同时满足 | root.xpath('port[@protocol="tcp" and @port>8000]') | 匹配协议为tcp且端口 > 8000 的 port |
tag[text()!='x' or @a='y'] | 文本不匹配或属性匹配 | root.xpath('host[text()!="localhost" or @active="true"]') | 匹配非localhost或active=true的host |
tag[@num>100] | 数值比较(>、<、>=、<=) | root.xpath('//port[@num>8080]') | 匹配num>8080的port |
案例:组合条件
# 选择protocol为tcp且端口文本为8080的port
tcp_8080 = root.xpath('//port[@protocol="tcp" and text()="8080"]')
print(len(tcp_8080)) # 输出:1
# 选择id为1或包含host子元素的server
servers = root.xpath('//server[@id="1" or host]')
print(len(servers)) # 输出:1(满足id="1")
4.Element对象详解
etree.XML(text)返回的Element对象是lxml库的核心数据结构,代表XML文档中的一个节点。它提供了丰富的属性和方法,用于操作XML数据。
Element对象的核心属性
属性 | 描述 | 示例 |
tag | 元素的标签名 | root.tag → 'config' |
text | 元素的文本内容 | host.text → 'localhost' |
tail | 元素结束标签后的文本 | 通常为空白或注释 |
attrib | 元素的属性字典 | server.attrib → {'id': '1'} |
sourceline | 元素在原XML中的行号 | host.sourceline → 3 |
Element 对象的常用方法
1. 获取元素信息
# 获取属性值
print(root.get('id')) # 输出:None(无此属性)
# 获取父元素
server = port.getparent()
print(server.tag) # 输出:server
# 获取子元素列表
children = server.getchildren() # 等价于 list(server)
for child in children:
print(child.tag) # 输出:host port
2. 修改元素内容
# 修改文本内容
host = root.xpath('//host')[0]
host.text = '127.0.0.1'
# 添加/修改属性
server.set('enabled', 'true')
server.set('version', '2.0')
# 删除属性
del server.attrib['version']
# 添加子元素
new_child = etree.SubElement(server, 'timeout')
new_child.text = '300'
3. 元素遍历与选择
# 遍历某个元素的子元素
for child in server:
print(child.tag)
# 递归遍历某个元素的所有子元素
for element in server.iter():
print(element.tag)
4.创建与删除元素
# 创建新元素
new_db = etree.Element('database')
etree.SubElement(new_db, 'name').text = 'newdb'
etree.SubElement(new_db, 'user').text = 'admin'
# 添加到根元素
root.append(new_db)
# 删除元素
root.remove(new_db)
ElementPath:简化选择语法
Element 对象支持直接使用 XPath-like 语法进行选择
# 等价于 root.xpath('//database/name/text()')
db_names = root.findall('.//database/name')
for name in db_names:
print(name.text)
# 获取单个元素
first_db = root.find('.//database')
print(first_db.xpath('user/text()')[0])
五、以事件驱动的方式逐段解析XML
使用etree.iterparse以事件驱动的方式逐段解析XML,无需一次性加载整个文档到内存,适合处理GB级大型文件。
基本语法
from lxml import etree
for event, element in etree.iterparse(source, events=('start', 'end'),
tag='目标标签'):
# 处理元素
pass
关键参数
参数 | 说明 |
source | 可传入文件路径、文件对象或字节流(如open('large.xml', 'rb')) |
events | 监听的事件列表,常用('start', 'end'): |
tag | 只监听指定标签的元素(可选,指定后可提升效率) |
案例:解析大型XML中的指定元素
假设有一个包含 100 万条item记录的large.xml:
<data>
<item id="1">
<name>产品A</name>
<price>99.9</price>
</item>
<!-- 更多item... -->
</data>
使用iterparse提取所有item的name和price:
from lxml import etree
# 打开大型XML文件(使用二进制模式)
with open('large.xml', 'rb') as f:
# 只监听item元素的end事件(内容已完整)
for event, elem in etree.iterparse(f, events=('end',), tag='item'):
# 提取数据
item_id = elem.get('id')
name = elem.xpath('./name/text()')[0]
price = elem.xpath('./price/text()')[0]
print(f"ID: {item_id}, 名称: {name}, 价格: {price}")
# 关键:清除已处理元素,释放内存
elem.clear()
# 移除父节点引用(彻底释放)
while elem.getprevious() is not None:
del elem.getparent()[0]
总结:掌握上述语法能让你快速定位和提取数据。建议结合实际 XML 结构多练习,熟练后可大幅提升解析效率!
关注我的头条号,获取更多Python高效技巧!
最近的5篇文章:
保姆级教程:使用Python实现OCR图片识别文字(环境搭建和使用)