什么是 elementtree
xml.etree.elementtree(通常简称为 et)是 python 标准库中用于解析和创建 xml 数据的模块。它提供了轻量级、高效的 api,适合处理小到中等规模的 xml 文档。
为什么选择 et?
- ✅ python 内置,无需安装
- ✅ 简单易用的 api 设计
- ✅ 内存效率高(迭代解析)
- ✅ 支持 xpath 表达式查找元素
基础概念:xml 结构
在深入学习之前,先了解 xml 的基本结构:
<?xml version="1.0" encoding="utf-8"?>
<!-- 这是注释 -->
<library> <!-- 根元素 -->
<book id="001"> <!-- 元素 + 属性 -->
<title>python编程</title> <!-- 子元素 -->
<author>张三</author>
<price>59.00</price>
</book>
</library>核心概念:
- element(元素):xml 标签,如
<book> - tag(标签名):元素的名称,如
"book" - attribute(属性):元素的特征,如
id="001" - text(文本内容):元素内的文本,如
"python编程" - tail(尾部文本):结束标签后的文本(较少使用)
解析 xml 文件
1. 基本解析方法
et 提供了两种主要解析方式:
import xml.etree.elementtree as et
# 方法1:解析文件(推荐用于文件)
tree = et.parse('books.xml') # 返回 elementtree 对象
root = tree.getroot() # 获取根元素
# 方法2:解析字符串(用于从网络或内存读取)
xml_string = """<?xml version="1.0"?>
<library>
<book id="001">
<title>python编程</title>
</book>
</library>"""
root = et.fromstring(xml_string) # 直接返回根元素2. 完整解析示例
假设我们有如下 books.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<library location="北京">
<book id="001" category="编程">
<title>python编程:从入门到实践</title>
<author>埃里克·马瑟斯</author>
<price currency="cny">89.00</price>
<publish_date>2020-05</publish_date>
</book>
<book id="002" category="小说">
<title>百年孤独</title>
<author>加西亚·马尔克斯</author>
<price currency="cny">55.00</price>
<publish_date>2011-06</publish_date>
</book>
<book id="003" category="编程">
<title>流畅的python</title>
<author>卢西亚诺·拉马略</author>
<price currency="cny">139.00</price>
<publish_date>2017-05</publish_date>
</book>
</library>解析代码:
import xml.etree.elementtree as et
def parse_library():
"""解析图书馆 xml 文件"""
try:
# 解析 xml 文件
tree = et.parse('books.xml')
root = tree.getroot()
print(f"根元素标签: {root.tag}")
print(f"根元素属性: {root.attrib}")
print(f"子元素数量: {len(root)}")
print("-" * 50)
# 遍历所有 book 元素
for book in root.findall('book'):
book_id = book.get('id') # 获取属性
category = book.get('category')
title = book.find('title').text # 获取文本内容
author = book.find('author').text
price_elem = book.find('price')
price = price_elem.text
currency = price_elem.get('currency')
print(f"图书id: {book_id}")
print(f"类别: {category}")
print(f"书名: {title}")
print(f"作者: {author}")
print(f"价格: {currency} {price}")
print("-" * 50)
except et.parseerror as e:
print(f"xml 解析错误: {e}")
except filenotfounderror:
print("文件未找到,请确保 books.xml 存在")
if __name__ == "__main__":
parse_library()输出结果:
根元素标签: library
根元素属性: {'location': '北京'}
子元素数量: 3
--------------------------------------------------
图书id: 001
类别: 编程
书名: python编程:从入门到实践
作者: 埃里克·马瑟斯
价格: cny 89.00
--------------------------------------------------
...
遍历 xml 树
1. 迭代遍历(内存友好)
对于大型 xml 文件,使用迭代器避免一次性加载所有数据:
import xml.etree.elementtree as et
def iterate_xml():
"""使用迭代器遍历大型 xml"""
# iterparse 在解析时生成事件,适合大文件
context = et.iterparse('books.xml', events=('start', 'end'))
context = iter(context)
event, root = next(context)
book_count = 0
for event, elem in context:
# 'start' 事件:元素开始
# 'end' 事件:元素结束(此时元素已完整)
if event == 'end' and elem.tag == 'book':
book_count += 1
title = elem.find('title').text
print(f"处理第 {book_count} 本书: {title}")
# 处理完后清除元素释放内存
elem.clear()
root.clear()
print(f"总共处理了 {book_count} 本书")
# 递归遍历所有元素
def recursive_walk(element, level=0):
"""递归打印 xml 结构"""
indent = " " * level
print(f"{indent}<{element.tag}> {element.attrib if element.attrib else ''}")
if element.text and element.text.strip():
print(f"{indent} 文本: {element.text.strip()}")
for child in element:
recursive_walk(child, level + 1)
print(f"{indent}</{element.tag}>")
# 使用示例
tree = et.parse('books.xml')
recursive_walk(tree.getroot())2. 按层级遍历
def traverse_by_level(root):
"""按层级遍历(广度优先)"""
from collections import deque
queue = deque([(root, 0)])
while queue:
elem, level = queue.popleft()
indent = " " * level
print(f"{indent}[{level}] {elem.tag}: {elem.attrib}")
for child in elem:
queue.append((child, level + 1))
traverse_by_level(tree.getroot())查找元素
et 支持有限的 xpath 表达式,非常实用:
import xml.etree.elementtree as et
tree = et.parse('books.xml')
root = tree.getroot()
# 1. find() - 查找第一个匹配的直接子元素
first_book = root.find('book')
print(f"第一本书: {first_book.find('title').text}")
# 2. findall() - 查找所有匹配的直接子元素
all_books = root.findall('book')
print(f"图书总数: {len(all_books)}")
# 3. iter() - 递归查找所有指定标签
all_prices = root.iter('price')
print("所有价格:")
for price in all_prices:
print(f" {price.text} {price.get('currency')}")
# 4. 带条件的查找(xpath 语法)
# 查找 category="编程" 的所有图书
programming_books = root.findall(".//book[@category='编程']")
print(f"\n编程类图书数量: {len(programming_books)}")
# 5. 复杂 xpath 示例
# 查找价格大于 100 的图书(需要遍历判断)
expensive_books = []
for book in root.findall('book'):
price = float(book.find('price').text)
if price > 100:
expensive_books.append(book.find('title').text)
print(f"高价图书: {expensive_books}")
# 6. 查找特定路径
# 查找第一个 book 下的 title
title = root.find('./book[1]/title')
print(f"第一本书书名: {title.text}")支持的 xpath 语法:
| 语法 | 说明 |
|---|---|
tag | 选择直接子元素 |
* | 匹配所有子元素 |
. | 当前元素 |
// | 递归查找所有后代 |
.. | 父元素 |
[@attrib] | 有某属性的元素 |
[@attrib='value'] | 属性等于某值的元素 |
[tag] | 有某子元素的元素 |
[position] | 第 n 个元素(从1开始) |
获取元素数据
完整的数据提取工具类
import xml.etree.elementtree as et
from dataclasses import dataclass
from typing import list, optional, dict
@dataclass
class book:
"""图书数据类"""
id: str
category: str
title: str
author: str
price: float
currency: str
publish_date: str
class xmlextractor:
"""xml 数据提取器"""
def __init__(self, xml_file: str):
self.tree = et.parse(xml_file)
self.root = self.tree.getroot()
def get_root_info(self) -> dict:
"""获取根元素信息"""
return {
'tag': self.root.tag,
'attributes': dict(self.root.attrib),
'children_count': len(self.root)
}
def extract_all_books(self) -> list[book]:
"""提取所有图书信息"""
books = []
for book_elem in self.root.findall('book'):
book = book(
id=book_elem.get('id', ''),
category=book_elem.get('category', ''),
title=self._get_text(book_elem, 'title'),
author=self._get_text(book_elem, 'author'),
price=float(self._get_text(book_elem, 'price')),
currency=book_elem.find('price').get('currency', 'cny'),
publish_date=self._get_text(book_elem, 'publish_date')
)
books.append(book)
return books
def _get_text(self, parent: et.element, tag: str, default: str = '') -> str:
"""安全获取子元素文本"""
elem = parent.find(tag)
return elem.text if elem is not none else default
def get_books_by_category(self, category: str) -> list[dict]:
"""按类别筛选图书"""
results = []
xpath = f".//book[@category='{category}']"
for book in self.root.findall(xpath):
results.append({
'id': book.get('id'),
'title': book.find('title').text,
'author': book.find('author').text
})
return results
def get_statistics(self) -> dict:
"""获取统计信息"""
books = self.extract_all_books()
if not books:
return {}
prices = [b.price for b in books]
categories = {}
for b in books:
categories[b.category] = categories.get(b.category, 0) + 1
return {
'total_books': len(books),
'avg_price': sum(prices) / len(prices),
'max_price': max(prices),
'min_price': min(prices),
'categories': categories
}
# 使用示例
if __name__ == "__main__":
extractor = xmlextractor('books.xml')
print("=== 根元素信息 ===")
print(extractor.get_root_info())
print("\n=== 所有图书 ===")
for book in extractor.extract_all_books():
print(f"{book.id}: {book.title} ({book.author}) - {book.currency}{book.price}")
print("\n=== 编程类图书 ===")
print(extractor.get_books_by_category('编程'))
print("\n=== 统计信息 ===")
stats = extractor.get_statistics()
print(f"图书总数: {stats['total_books']}")
print(f"平均价格: ¥{stats['avg_price']:.2f}")
print(f"价格区间: ¥{stats['min_price']:.2f} - ¥{stats['max_price']:.2f}")
print(f"类别分布: {stats['categories']}")修改 xml
1. 修改现有元素
import xml.etree.elementtree as et
def modify_xml():
"""修改 xml 内容"""
tree = et.parse('books.xml')
root = tree.getroot()
# 1. 修改属性
root.set('updated', '2024-01-01')
root.set('location', '上海') # 修改现有属性
# 2. 修改元素文本
for book in root.findall('book'):
price_elem = book.find('price')
old_price = float(price_elem.text)
# 打 8 折
new_price = old_price * 0.8
price_elem.text = f"{new_price:.2f}"
price_elem.set('discount', '0.8')
# 3. 添加新元素
for book in root.findall('book'):
stock = et.subelement(book, 'stock')
stock.text = '100'
stock.set('warehouse', 'a1')
# 4. 删除元素
# 删除第一本书的 publish_date
first_book = root.find('book')
publish_date = first_book.find('publish_date')
if publish_date is not none:
first_book.remove(publish_date)
# 5. 保存修改
tree.write('books_modified.xml',
encoding='utf-8',
xml_declaration=true,
short_empty_elements=false)
print("修改完成,已保存到 books_modified.xml")
modify_xml()2. 批量修改工具
class xmlmodifier:
"""xml 批量修改器"""
def __init__(self, input_file: str):
self.tree = et.parse(input_file)
self.root = self.tree.getroot()
self.modified = false
def update_prices(self, increase_rate: float = 0.1):
"""批量更新价格"""
for price_elem in self.root.iter('price'):
old_price = float(price_elem.text)
new_price = old_price * (1 + increase_rate)
price_elem.text = f"{new_price:.2f}"
price_elem.set('updated', 'true')
self.modified = true
print(f"已更新所有价格,涨幅 {increase_rate*100}%")
def add_element_to_all(self, tag: str, text: str, attrib: dict = none):
"""为所有 book 添加子元素"""
attrib = attrib or {}
for book in self.root.findall('book'):
elem = et.subelement(book, tag, attrib)
elem.text = text
self.modified = true
print(f"已为所有图书添加 <{tag}> 元素")
def remove_element_by_tag(self, tag: str):
"""删除所有指定标签的元素"""
count = 0
for parent in self.root.iter():
for child in list(parent): # 使用 list 避免迭代时修改
if child.tag == tag:
parent.remove(child)
count += 1
self.modified = true
print(f"已删除 {count} 个 <{tag}> 元素")
def save(self, output_file: str = none):
"""保存文件"""
if not self.modified:
print("没有修改需要保存")
return
output = output_file or 'modified.xml'
self.tree.write(output,
encoding='utf-8',
xml_declaration=true)
print(f"已保存到: {output}")
# 使用示例
modifier = xmlmodifier('books.xml')
modifier.update_prices(0.15) # 涨价 15%
modifier.add_element_to_all('status', '在售', {'available': 'true'})
modifier.save('books_updated.xml')创建 xml
1. 从零创建 xml
import xml.etree.elementtree as et
from datetime import datetime
def create_library_xml():
"""创建图书馆 xml 文件"""
# 创建根元素
root = et.element('library')
root.set('version', '1.0')
root.set('created', datetime.now().isoformat())
# 添加注释
comment = et.comment(' 这是一个自动生成的图书馆数据文件 ')
root.append(comment)
# 创建图书数据
books_data = [
{
'id': '004',
'category': '科幻',
'title': '三体',
'author': '刘慈欣',
'price': '98.00',
'currency': 'cny',
'tags': ['雨果奖', '科幻经典', '系列作品']
},
{
'id': '005',
'category': '技术',
'title': '深度学习',
'author': '伊恩·古德费洛',
'price': '168.00',
'currency': 'cny',
'tags': ['ai', '机器学习', '教材']
}
]
for book_data in books_data:
# 创建 book 元素
book = et.subelement(root, 'book')
book.set('id', book_data['id'])
book.set('category', book_data['category'])
# 添加子元素
title = et.subelement(book, 'title')
title.text = book_data['title']
author = et.subelement(book, 'author')
author.text = book_data['author']
price = et.subelement(book, 'price')
price.text = book_data['price']
price.set('currency', book_data['currency'])
# 添加标签列表
tags = et.subelement(book, 'tags')
for tag_text in book_data['tags']:
tag = et.subelement(tags, 'tag')
tag.text = tag_text
# 创建 elementtree 对象
tree = et.elementtree(root)
# 美化输出(缩进)
et.indent(tree, space=' ', level=0)
# 保存到文件
tree.write('new_library.xml',
encoding='utf-8',
xml_declaration=true,
short_empty_elements=false)
print("成功创建 new_library.xml")
# 同时返回字符串形式
return et.tostring(root, encoding='unicode')
xml_string = create_library_xml()
print("\n生成的 xml 内容:")
print(xml_string)生成的 xml 结构:
<?xml version='1.0' encoding='utf-8'?>
<library created="2024-01-15t10:30:00" version="1.0">
<!-- 这是一个自动生成的图书馆数据文件 -->
<book category="科幻" id="004">
<title>三体</title>
<author>刘慈欣</author>
<price currency="cny">98.00</price>
<tags>
<tag>雨果奖</tag>
<tag>科幻经典</tag>
<tag>系列作品</tag>
</tags>
</book>
...
</library>2. 使用 element 工厂函数
def create_element_factory():
"""使用工厂模式创建 xml"""
def create_book(id, title, author, **kwargs):
"""创建图书元素的工厂函数"""
book = et.element('book', {'id': id})
et.subelement(book, 'title').text = title
et.subelement(book, 'author').text = author
for key, value in kwargs.items():
if isinstance(value, dict):
# 带属性的元素
elem = et.subelement(book, key, value.get('attrib', {}))
elem.text = value.get('text', '')
else:
et.subelement(book, key).text = str(value)
return book
# 构建 xml
root = et.element('catalog')
book1 = create_book(
'006',
'python数据科学手册',
'杰克·万托布拉斯',
price={'text': '128.00', 'attrib': {'currency': 'cny'}},
publisher='人民邮电出版社',
year='2020'
)
root.append(book1)
# 保存
tree = et.elementtree(root)
et.indent(tree, space=' ')
tree.write('catalog.xml', encoding='utf-8', xml_declaration=true)
print("创建 catalog.xml 成功")
create_element_factory()实际应用案例
案例1:配置文件管理器
import xml.etree.elementtree as et
import os
class configmanager:
"""xml 配置文件管理器"""
config_file = 'app_config.xml'
def __init__(self):
self.tree = none
self.root = none
self._load_or_create()
def _load_or_create(self):
"""加载或创建配置文件"""
if os.path.exists(self.config_file):
self.tree = et.parse(self.config_file)
self.root = self.tree.getroot()
else:
self.root = et.element('configuration')
self.root.set('version', '1.0')
self.tree = et.elementtree(self.root)
self._save()
def _save(self):
"""保存配置"""
et.indent(self.tree, space=' ')
self.tree.write(self.config_file, encoding='utf-8', xml_declaration=true)
def get(self, section: str, key: str, default=none):
"""获取配置值"""
section_elem = self.root.find(f".//section[@name='{section}']")
if section_elem is none:
return default
item = section_elem.find(f"item[@key='{key}']")
if item is none:
return default
return item.get('value')
def set(self, section: str, key: str, value: str):
"""设置配置值"""
section_elem = self.root.find(f".//section[@name='{section}']")
if section_elem is none:
section_elem = et.subelement(self.root, 'section')
section_elem.set('name', section)
item = section_elem.find(f"item[@key='{key}']")
if item is none:
item = et.subelement(section_elem, 'item')
item.set('key', key)
item.set('value', str(value))
self._save()
def get_database_config(self) -> dict:
"""获取数据库配置"""
return {
'host': self.get('database', 'host', 'localhost'),
'port': int(self.get('database', 'port', '3306')),
'username': self.get('database', 'username', 'root'),
'password': self.get('database', 'password', ''),
'database': self.get('database', 'database', 'test')
}
# 使用示例
config = configmanager()
config.set('database', 'host', '192.168.1.100')
config.set('database', 'port', '5432')
config.set('app', 'debug', 'true')
print(config.get_database_config())案例2:数据转换器(csv 转 xml)
import csv
import xml.etree.elementtree as et
from datetime import datetime
def csv_to_xml(csv_file: str, xml_file: str, root_tag: str = 'data'):
"""将 csv 文件转换为 xml"""
root = et.element(root_tag)
root.set('generated', datetime.now().isoformat())
root.set('source', csv_file)
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.dictreader(f)
for i, row in enumerate(reader, 1):
record = et.subelement(root, 'record')
record.set('id', str(i))
for key, value in row.items():
# 清理标签名(xml 标签不能以数字开头,不能包含空格)
clean_key = key.strip().replace(' ', '_')
if clean_key[0].isdigit():
clean_key = 'f_' + clean_key
field = et.subelement(record, clean_key)
field.text = value
# 美化并保存
tree = et.elementtree(root)
et.indent(tree, space=' ')
tree.write(xml_file, encoding='utf-8', xml_declaration=true)
print(f"成功转换: {csv_file} -> {xml_file}")
# 示例 csv 内容:
# name,age,city
# 张三,28,北京
# 李四,32,上海
# csv_to_xml('data.csv', 'output.xml')常见错误与解决方案
1. 编码问题
# ❌ 错误:默认编码可能不支持中文
tree.write('output.xml')
# ✅ 正确:指定 utf-8 编码
tree.write('output.xml', encoding='utf-8', xml_declaration=true)2. 命名空间处理
# 处理带命名空间的 xml
xml_with_ns = """<?xml version="1.0"?>
<root xmlns:ns="http://example.com/ns">
<ns:item>内容</ns:item>
</root>"""
root = et.fromstring(xml_with_ns)
# 方法1:使用完整标签名
for elem in root.findall('{http://example.com/ns}item'):
print(elem.text)
# 方法2:定义命名空间字典
namespaces = {'ns': 'http://example.com/ns'}
for elem in root.findall('ns:item', namespaces):
print(elem.text)3. 大小写敏感
# xml 是大小写敏感的
root.find('book') # 找不到 <book>
root.find('book') # 正确4. 内存优化
# 大文件处理(>100mb)
# ❌ 错误:一次性加载
tree = et.parse('huge.xml')
# ✅ 正确:迭代解析
for event, elem in et.iterparse('huge.xml', events=('end',)):
if elem.tag == 'record':
process(elem)
elem.clear() # 释放内存5. 特殊字符转义
# et 自动处理特殊字符
root = et.element('test')
root.text = '<特殊内容> & "引号"'
# 输出: <特殊内容> & "引号"总结
| 功能 | 方法 | 说明 |
|---|---|---|
| 解析文件 | et.parse() | 返回 elementtree |
| 解析字符串 | et.fromstring() | 返回根元素 |
| 查找单个 | element.find() | 第一个匹配 |
| 查找多个 | element.findall() | 所有直接子元素 |
| 递归查找 | element.iter() | 所有后代元素 |
| 获取属性 | element.get() | 获取属性值 |
| 获取文本 | element.text | 元素内容 |
| 创建子元素 | et.subelement() | 工厂函数 |
| 保存文件 | tree.write() | 写入文件 |
最佳实践建议:
- 始终指定
encoding='utf-8'保存中文 - 大文件使用
iterparse()迭代处理 - 使用
try-except捕获parseerror - 复杂查询考虑使用
lxml库(支持完整 xpath) - 修改前先备份原文件
通过本教程的学习,您已经掌握了 python et 模块的核心功能,可以处理绝大多数 xml 数据操作需求。建议动手实践每个示例代码,加深理解。
到此这篇关于python et.parse 模块功能详解的文章就介绍到这了,更多相关python et.parse内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论