一、什么是 XML?
XML (eXtensible Markup Language,可扩展标记语言)是一种用来存储和传输数据的文本格式。
1.1 生活比喻
把 XML 想象成一个带标签的收纳盒:
- 每个盒子有名字(标签名)
- 盒子里可以放东西(文本内容)
- 盒子里还可以套小盒子(子元素)
- 每个盒子可以贴便签(属性)
1.2 XML 长什么样?
python
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="编程">
<title>Python入门</title>
<author>张三</author>
<price>59.9</price>
<year>2025</year>
</book>
<book category="文学">
<title>红楼梦</title>
<author>曹雪芹</author>
<price>39.0</price>
<year>1791</year>
</book>
</bookstore>
1.3 XML 的基本结构
python
<?xml version="1.0" encoding="UTF-8"?> ← XML 声明(可选)
<bookstore> ← 根元素(必须有且只有一个)
<book category="编程"> ← 子元素,category 是"属性"
<title>Python入门</title> ← 子元素,"Python入门"是"文本内容"
<author>张三</author>
<price>59.9</price>
</book>
</bookstore>
1.4 XML 的术语对照表
python
<person name="张三" age="25"> ← "person" 是元素(标签)
<city>北京</city> ← "name"、"age" 是属性
</person> ← "北京" 是文本内容
| 术语 | 英文 | 示例 | 说明 |
|---|---|---|---|
| 元素 | Element | <book>...</book> |
一对标签包裹的内容 |
| 标签 | Tag | book、title |
尖括号里的名字 |
| 属性 | Attribute | category="编程" |
写在开始标签里的键值对 |
| 文本 | Text | Python入门 |
标签之间的文字 |
| 根元素 | Root | <bookstore> |
最外层的元素 |
| 子元素 | Child | <book> 是 <bookstore> 的子元素 |
嵌套关系 |
| 父元素 | Parent | <bookstore> 是 <book> 的父元素 |
上一级 |
| 兄弟元素 | Sibling | 两个 <book> 互为兄弟 |
同级 |
1.5 XML 的规则
python
✅ 必须有且只有一个根元素
✅ 标签必须成对关闭:<title>...</title>
✅ 标签必须正确嵌套:<a><b></b></a> ✅
<a><b></a></b> ❌
✅ 属性值必须加引号:name="张三" ✅
name=张三 ❌
✅ 标签名区分大小写:<Title> 和 <title> 是不同的
二、Python 解析 XML 的方式概览
Python 提供了多种解析 XML 的方式:
| 方式 | 模块 | 特点 | 适合场景 |
|---|---|---|---|
| ElementTree | xml.etree.ElementTree |
简单、轻量、Pythonic | ✅ 新手首选,日常使用 |
| minidom | xml.dom.minidom |
DOM方式,功能全 | 需要完整DOM操作 |
| SAX | xml.sax |
事件驱动,流式解析 | 超大文件,内存敏感 |
| lxml | lxml(第三方) |
功能最强,速度快 | 复杂XPath、性能要求高 |
本教程重点讲 ElementTree(最常用),其他方式也会介绍。
三、ElementTree ------ 最推荐的方式
3.1 导入模块
python
import xml.etree.ElementTree as ET
约定俗成:用
ET作为别名。
3.2 准备一个 XML 文件
创建文件 books.xml:
python
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book id="1" category="编程">
<title>Python编程:从入门到实践</title>
<author>Eric Matthes</author>
<price currency="CNY">89.00</price>
<year>2020</year>
<tags>
<tag>Python</tag>
<tag>入门</tag>
</tags>
</book>
<book id="2" category="文学">
<title>百年孤独</title>
<author>加西亚·马尔克斯</author>
<price currency="CNY">55.00</price>
<year>1967</year>
<tags>
<tag>小说</tag>
<tag>魔幻现实主义</tag>
</tags>
</book>
<book id="3" category="编程">
<title>算法导论</title>
<author>Thomas H. Cormen</author>
<price currency="USD">128.00</price>
<year>2009</year>
<tags>
<tag>算法</tag>
<tag>计算机</tag>
</tags>
</book>
</bookstore>
3.3 解析 XML 文件(从文件读取)
python
import xml.etree.ElementTree as ET
# ============ 第一步:解析文件,得到"树"对象 ============
tree = ET.parse("books.xml")
# ============ 第二步:获取根元素 ============
root = tree.getroot()
# 查看根元素的信息
print(f"根元素标签:{root.tag}") # bookstore
print(f"根元素属性:{root.attrib}") # {}(根元素没有属性)
print(f"子元素数量:{len(root)}") # 3(有3个book)
输出:
python
根元素标签:bookstore
根元素属性:{}
子元素数量:3
3.4 也可以从字符串解析
python
import xml.etree.ElementTree as ET
xml_string = """
<student>
<name>小明</name>
<age>20</age>
<grade>大三</grade>
</student>
"""
# fromstring() 直接从字符串解析
root = ET.fromstring(xml_string)
print(root.tag) # student
print(root.find("name").text) # 小明
print(root.find("age").text) # 20
3.5 遍历所有子元素
python
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
# ============ 方法1:直接遍历(只遍历直接子元素) ============
print("=== 方法1:遍历直接子元素 ===")
for book in root:
print(f" 标签:{book.tag},属性:{book.attrib}")
# 输出:
# 标签:book,属性:{'id': '1', 'category': '编程'}
# 标签:book,属性:{'id': '2', 'category': '文学'}
# 标签:book,属性:{'id': '3', 'category': '编程'}
# ============ 方法2:iter() 遍历所有后代元素(包括子元素的子元素) ============
print("\n=== 方法2:遍历所有后代 ===")
for elem in root.iter():
print(f" {elem.tag}: {elem.text.strip() if elem.text and elem.text.strip() else '(无文本)'}")
# 输出(会列出所有层级的元素):
# bookstore: (无文本)
# book: (无文本)
# title: Python编程:从入门到实践
# author: Eric Matthes
# ...
# ============ 方法3:iter() 只遍历指定标签 ============
print("\n=== 方法3:只遍历所有 title ===")
for title in root.iter("title"):
print(f" 📖 {title.text}")
# 输出:
# 📖 Python编程:从入门到实践
# 📖 百年孤独
# 📖 算法导论
3.6 查找元素:find() 和 findall()
这是最常用的操作!
python
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
# ============ find():找第一个匹配的元素 ============
# 找第一个 <book>
first_book = root.find("book")
print(f"第一本书的标签:{first_book.tag}")
print(f"第一本书的属性:{first_book.attrib}")
# 找第一个 book 下的 title
title = first_book.find("title")
print(f"书名:{title.text}")
# ============ findall():找所有匹配的元素 ============
# 找所有 <book>
all_books = root.findall("book")
print(f"\n共有 {len(all_books)} 本书")
# 找所有 book 下的 title
all_titles = root.findall("book/title") # 路径写法!
print("所有书名:")
for t in all_titles:
print(f" - {t.text}")
输出:
python
第一本书的标签:book
第一本书的属性:{'id': '1', 'category': '编程'}
书名:Python编程:从入门到实践
共有 3 本书
所有书名:
- Python编程:从入门到实践
- 百年孤独
- 算法导论
3.7 路径查找语法(重要!)
find() 和 findall() 支持简单的路径表达式:
| 路径 | 含义 | 示例 |
|---|---|---|
"book" |
直接子元素中找 book | root.find("book") |
"book/title" |
book 下的 title | root.find("book/title") |
".//title" |
任意深度的 title | root.findall(".//title") |
"book[@id='1']" |
id属性为1的book | root.find("book[@id='1']") |
"book[title='百年孤独']" |
title文本为百年孤独的book | root.find("book[title='百年孤独']") |
"book[1]" |
第2个book(索引从0开始) | root.find("book[1]") |
"book[last()]" |
最后一个book | root.find("book[last()]") |
python
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
# 示例1:找任意深度的所有 tag 元素
print("=== 所有 tag ===")
for tag in root.findall(".//tag"):
print(f" 🏷️ {tag.text}")
# 示例2:找 id="2" 的 book
print("\n=== 找 id=2 的书 ===")
book2 = root.find("book[@id='2']")
print(f" 书名:{book2.find('title').text}")
print(f" 作者:{book2.find('author').text}")
# 示例3:找 category="编程" 的所有书
print("\n=== 所有编程类书籍 ===")
for book in root.findall("book[@category='编程']"):
print(f" 📗 {book.find('title').text}")
# 示例4:找价格大于某个值的(ElementTree不直接支持数值比较,需要手动过滤)
print("\n=== 价格 > 60 的书 ===")
for book in root.findall("book"):
price = float(book.find("price").text)
if price > 60:
print(f" 💰 {book.find('title').text} - ¥{price}")
输出:
python
=== 所有 tag ===
🏷️ Python
🏷️ 入门
🏷️ 小说
🏷️ 魔幻现实主义
🏷️ 算法
🏷️ 计算机
=== 找 id=2 的书 ===
书名:百年孤独
作者:加西亚·马尔克斯
=== 所有编程类书籍 ===
📗 Python编程:从入门到实践
📗 算法导论
=== 价格 > 60 的书 ===
💰 Python编程:从入门到实践 - ¥89.0
💰 算法导论 - ¥128.0
3.8 获取元素的详细信息
python
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
book = root.find("book") # 第一个 book
# ============ 获取标签名 ============
print(f"标签名:{book.tag}") # book
# ============ 获取属性 ============
print(f"所有属性:{book.attrib}") # {'id': '1', 'category': '编程'}
print(f"id属性:{book.get('id')}") # 1
print(f"category属性:{book.get('category')}") # 编程
print(f"不存在的属性:{book.get('xxx', '默认值')}") # 默认值
# ============ 获取文本内容 ============
title = book.find("title")
print(f"title的文本:{title.text}") # Python编程:从入门到实践
# ============ 获取子元素列表 ============
children = list(book)
print(f"book的子元素数量:{len(children)}") # 5 (title, author, price, year, tags)
for child in children:
print(f" 子元素:{child.tag} = {child.text}")
# ============ 获取父元素(ElementTree 不直接支持,需要自己建映射) ============
# 方法:遍历建立 parent 映射
parent_map = {child: parent for parent in root.iter() for child in parent}
title_elem = root.find(".//title")
parent = parent_map[title_elem]
print(f"\ntitle 的父元素是:{parent.tag}") # book
3.9 处理属性(Attribute)
python
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
# ============ 读取属性 ============
for book in root.findall("book"):
book_id = book.get("id") # 获取单个属性
category = book.get("category")
print(f" ID={book_id}, 类别={category}")
# ============ 读取子元素的属性 ============
for book in root.findall("book"):
price_elem = book.find("price")
currency = price_elem.get("currency") # price 标签上的 currency 属性
value = price_elem.text
print(f" 价格:{value} {currency}")
# 输出:
# 价格:89.00 CNY
# 价格:55.00 CNY
# 价格:128.00 USD
3.10 处理命名空间(Namespace)
有些 XML 带有命名空间,解析时会比较麻烦:
python
<?xml version="1.0"?>
<root xmlns:ns="http://example.com/schema">
<ns:item>
<ns:name>测试</ns:name>
</ns:item>
</root>
python
import xml.etree.ElementTree as ET
xml_string = """<?xml version="1.0"?>
<root xmlns:ns="http://example.com/schema">
<ns:item>
<ns:name>测试数据</ns:name>
</ns:item>
</root>
"""
root = ET.fromstring(xml_string)
# ❌ 这样找不到(因为标签名实际是 "{http://example.com/schema}item")
# item = root.find("ns:item") # 返回 None
# ✅ 方法1:使用完整命名空间
ns = {"ns": "http://example.com/schema"}
item = root.find("ns:item", ns)
name = item.find("ns:name", ns)
print(f"方法1:{name.text}") # 测试数据
# ✅ 方法2:用通配符(忽略命名空间)
item = root.find("{*}item")
name = item.find("{*}name")
print(f"方法2:{name.text}") # 测试数据
# 查看实际的标签名
print(f"实际标签名:{item.tag}")
# 输出:{http://example.com/schema}item
四、创建和修改 XML
4.1 从零创建 XML
python
import xml.etree.ElementTree as ET
# ============ 创建根元素 ============
root = ET.Element("students")
# ============ 添加子元素 ============
# 创建第一个学生
student1 = ET.SubElement(root, "student", attrib={"id": "1", "class": "三班"})
name1 = ET.SubElement(student1, "name")
name1.text = "张三"
age1 = ET.SubElement(student1, "age")
age1.text = "20"
score1 = ET.SubElement(student1, "score")
score1.text = "95"
# 创建第二个学生
student2 = ET.SubElement(root, "student", attrib={"id": "2", "class": "一班"})
name2 = ET.SubElement(student2, "name")
name2.text = "李四"
age2 = ET.SubElement(student2, "age")
age2.text = "21"
score2 = ET.SubElement(student2, "score")
score2.text = "88"
# ============ 写入文件 ============
tree = ET.ElementTree(root)
# 方法1:简单写入
tree.write("students.xml", encoding="utf-8", xml_declaration=True)
# 方法2:格式化写入(Python 3.9+)
ET.indent(tree, space=" ") # 添加缩进
tree.write("students_formatted.xml", encoding="utf-8", xml_declaration=True)
print("✅ XML 文件创建成功!")
生成的 students_formatted.xml:
python
<?xml version='1.0' encoding='utf-8'?>
<students>
<student id="1" class="三班">
<name>张三</name>
<age>20</age>
<score>95</score>
</student>
<student id="2" class="一班">
<name>李四</name>
<age>21</age>
<score>88</score>
</student>
</students>
4.2 修改现有 XML
python
import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
# ============ 修改文本 ============
# 把第一本书的价格改为 99.00
first_book = root.find("book")
price = first_book.find("price")
print(f"修改前价格:{price.text}") # 89.00
price.text = "99.00"
print(f"修改后价格:{price.text}") # 99.00
# ============ 修改属性 ============
first_book.set("category", "热门编程") # 修改已有属性
first_book.set("recommended", "true") # 添加新属性
print(f"修改后属性:{first_book.attrib}")
# ============ 添加新元素 ============
# 给第一本书添加 <publisher> 元素
publisher = ET.SubElement(first_book, "publisher")
publisher.text = "人民邮电出版社"
# ============ 删除元素 ============
# 删除第一本书的 <year> 元素
year = first_book.find("year")
first_book.remove(year)
# ============ 保存修改 ============
ET.indent(tree, space=" ")
tree.write("books_modified.xml", encoding="utf-8", xml_declaration=True)
print("✅ 修改已保存!")
4.3 在指定位置插入元素
python
import xml.etree.ElementTree as ET
root = ET.Element("fruits")
# 添加几个水果
ET.SubElement(root, "fruit").text = "苹果"
ET.SubElement(root, "fruit").text = "香蕉"
ET.SubElement(root, "fruit").text = "橘子"
# 在索引1的位置插入(插在"苹果"和"香蕉"之间)
new_fruit = ET.Element("fruit")
new_fruit.text = "葡萄"
root.insert(1, new_fruit)
# 查看结果
for fruit in root:
print(fruit.text)
# 输出:
# 苹果
# 葡萄 ← 插入在这里
# 香蕉
# 橘子
五、xml.dom.minidom ------ DOM 方式解析
5.1 什么是 DOM?
DOM (Document Object Model)把整个 XML 加载到内存中,形成一个树形结构,你可以随意遍历、修改任何节点。
和 ElementTree 的区别:
- ElementTree:更 Pythonic,API 更简洁
- minidom:更接近 W3C 标准,方法名更长,但功能更全
5.2 基本用法
python
from xml.dom import minidom
# 解析文件
dom = minidom.parse("books.xml")
# 获取根元素
root = dom.documentElement
print(f"根元素:{root.tagName}") # bookstore
# 获取所有 book 元素
books = root.getElementsByTagName("book")
print(f"共有 {len(books)} 本书\n")
# 遍历每本书
for book in books:
# 获取属性
book_id = book.getAttribute("id")
category = book.getAttribute("category")
# 获取子元素的文本
title = book.getElementsByTagName("title")[0].firstChild.data
author = book.getElementsByTagName("author")[0].firstChild.data
price = book.getElementsByTagName("price")[0].firstChild.data
print(f" [{book_id}] 《{title}》 - {author} - ¥{price} ({category})")
输出:
python
根元素:bookstore
共有 3 本书
[1] 《Python编程:从入门到实践》 - Eric Matthes - ¥89.00 (编程)
[2] 《百年孤独》 - 加西亚·马尔克斯 - ¥55.00 (文学)
[3] 《算法导论》 - Thomas H. Cormen - ¥128.00 (编程)
5.3 minidom 的常用方法对照
| 操作 | ElementTree | minidom |
|---|---|---|
| 解析文件 | ET.parse("file.xml") |
minidom.parse("file.xml") |
| 获取根 | tree.getroot() |
dom.documentElement |
| 标签名 | elem.tag |
node.tagName |
| 文本内容 | elem.text |
node.firstChild.data |
| 获取属性 | elem.get("name") |
node.getAttribute("name") |
| 找子元素 | elem.find("title") |
node.getElementsByTagName("title") |
| 所有子元素 | list(elem) |
node.childNodes |
5.4 用 minidom 创建 XML
python
from xml.dom import minidom
# 创建文档
doc = minidom.Document()
# 创建根元素
root = doc.createElement("config")
doc.appendChild(root)
# 创建子元素
server = doc.createElement("server")
server.setAttribute("host", "192.168.1.1")
server.setAttribute("port", "8080")
root.appendChild(server)
# 创建带文本的子元素
name = doc.createElement("name")
name_text = doc.createTextNode("我的服务器")
name.appendChild(name_text)
server.appendChild(name)
# 格式化输出
xml_string = doc.toprettyxml(indent=" ", encoding="utf-8")
print(xml_string.decode("utf-8"))
# 保存到文件
with open("config.xml", "w", encoding="utf-8") as f:
doc.writexml(f, indent=" ", addindent=" ", newl="\n", encoding="utf-8")
输出:
python
<?xml version="1.0" encoding="utf-8"?>
<config>
<server host="192.168.1.1" port="8080">
<name>我的服务器</name>
</server>
</config>
六、xml.sax ------ 流式解析(处理大文件)
6.1 什么是 SAX?
SAX (Simple API for XML)是事件驱动的解析方式:
- 不会把整个文件加载到内存
- 从上往下逐行读取
- 遇到开始标签、结束标签、文本时触发回调函数
生活比喻:
- DOM/ElementTree = 把整本书搬到桌子上,随便翻
- SAX = 像听有声书,从头听到尾,听到关键内容就记下来
适合场景:几百MB甚至几GB的XML文件。
6.2 基本用法
python
import xml.sax
# ============ 定义事件处理器 ============
class BookHandler(xml.sax.ContentHandler):
def __init__(self):
super().__init__()
self.books = [] # 存储所有书
self.current_book = {} # 当前正在解析的书
self.current_tag = "" # 当前标签名
self.in_book = False # 是否在 book 元素内
# 遇到开始标签时触发
# name: 标签名, attrs: 属性字典
def startElement(self, name, attrs):
self.current_tag = name
if name == "book":
self.in_book = True
self.current_book = {
"id": attrs.get("id", ""),
"category": attrs.get("category", ""),
}
# 遇到文本内容时触发
def characters(self, content):
content = content.strip()
if not content:
return
if self.in_book and self.current_tag in ("title", "author", "price", "year"):
self.current_book[self.current_tag] = content
# 遇到结束标签时触发
def endElement(self, name):
if name == "book":
self.in_book = False
self.books.append(self.current_book)
self.current_book = {}
self.current_tag = ""
# ============ 使用 ============
handler = BookHandler()
# 解析文件
parser = xml.sax.make_parser()
parser.setContentHandler(handler)
parser.parse("books.xml")
# 输出结果
print(f"共解析了 {len(handler.books)} 本书:\n")
for book in handler.books:
print(f" [{book['id']}] 《{book.get('title', '?')}》")
print(f" 作者:{book.get('author', '?')}")
print(f" 价格:¥{book.get('price', '?')}")
print(f" 类别:{book.get('category', '?')}")
print()
输出:
python
共解析了 3 本书:
[1] 《Python编程:从入门到实践》
作者:Eric Matthes
价格:¥89.00
类别:编程
[2] 《百年孤独》
作者:加西亚·马尔克斯
价格:¥55.00
类别:文学
[3] 《算法导论》
作者:Thomas H. Cormen
价格:¥128.00
类别:编程
6.3 SAX 的回调方法总结
| 方法 | 触发时机 | 参数 |
|---|---|---|
startDocument() |
文档开始 | 无 |
endDocument() |
文档结束 | 无 |
startElement(name, attrs) |
遇到 <tag> |
标签名、属性 |
endElement(name) |
遇到 </tag> |
标签名 |
characters(content) |
遇到文本内容 | 文本字符串 |
七、lxml ------ 第三方强大库(可选)
7.1 安装
python
pip install lxml
7.2 为什么用 lxml?
- 支持完整的 XPath(比 ElementTree 的路径语法强大得多)
- 速度更快(底层用 C 实现)
- 支持 XSLT 转换
- API 和 ElementTree 兼容
7.3 基本用法
python
from lxml import etree
# 解析文件
tree = etree.parse("books.xml")
root = tree.getroot()
# ============ 强大的 XPath ============
# 找所有价格大于60的书(ElementTree做不到!)
expensive_books = root.xpath("//book[price > 60]/title/text()")
print("价格>60的书:", expensive_books)
# ['Python编程:从入门到实践', '算法导论']
# 找所有编程类书的作者
authors = root.xpath("//book[@category='编程']/author/text()")
print("编程书作者:", authors)
# ['Eric Matthes', 'Thomas H. Cormen']
# 获取所有 tag 的文本
tags = root.xpath("//tag/text()")
print("所有标签:", tags)
# ['Python', '入门', '小说', '魔幻现实主义', '算法', '计算机']
# 获取第一本书的所有属性
attrs = root.xpath("//book[1]/@*")
print("第一本书属性:", attrs)
# ['1', '编程']
# 统计每个类别有几本书
categories = root.xpath("//book/@category")
from collections import Counter
print("类别统计:", dict(Counter(categories)))
# {'编程': 2, '文学': 1}
7.4 XPath 常用语法速查
| XPath 表达式 | 含义 |
|---|---|
/bookstore/book |
根下的直接子 book |
//book |
任意位置的 book |
//book/title |
所有 book 下的 title |
//book[@id='1'] |
id=1 的 book |
//book[price>60] |
price 大于 60 的 book |
//book[1] |
第一个 book |
//book[last()] |
最后一个 book |
//book/@category |
所有 book 的 category 属性值 |
//title/text() |
所有 title 的文本内容 |
//book[contains(title,'Python')] |
title 包含 Python 的 book |
count(//book) |
book 的数量 |
八、实战案例
8.1 解析 RSS/Atom 订阅源
python
import xml.etree.ElementTree as ET
import urllib.request
def parse_rss(url):
"""解析 RSS 订阅源"""
# 下载 XML
response = urllib.request.urlopen(url)
xml_data = response.read()
# 解析
root = ET.fromstring(xml_data)
# RSS 结构:rss > channel > item
channel = root.find("channel")
title = channel.find("title").text
print(f"📰 频道:{title}\n")
items = channel.findall("item")
for i, item in enumerate(items[:5], 1): # 只显示前5条
item_title = item.find("title").text
item_link = item.find("link").text
pub_date = item.find("pubDate")
date_str = pub_date.text if pub_date is not None else "未知"
print(f" {i}. {item_title}")
print(f" 🔗 {item_link}")
print(f" 📅 {date_str}")
print()
# 使用(示例URL,可能失效)
# parse_rss("https://example.com/rss.xml")
8.2 解析 Android 布局文件
python
import xml.etree.ElementTree as ET
android_layout = """<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
android:textSize="24sp" />
<Button
android:id="@+id/submit_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="提交" />
<EditText
android:id="@+id/input_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入内容" />
</LinearLayout>
"""
# Android XML 有命名空间
ns = {"android": "http://schemas.android.com/apk/res/android"}
root = ET.fromstring(android_layout)
print(f"根布局:{root.tag}")
print(f"方向:{root.get('{http://schemas.android.com/apk/res/android}orientation')}")
print()
# 遍历所有控件
for elem in root:
tag = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag # 去掉命名空间前缀
android_id = elem.get("{http://schemas.android.com/apk/res/android}id", "")
text = elem.get("{http://schemas.android.com/apk/res/android}text", "")
print(f" 📱 {tag}")
if android_id:
print(f" ID: {android_id}")
if text:
print(f" 文本: {text}")
输出:
python
根布局:{http://schemas.android.com/apk/res/android}LinearLayout
方向:vertical
📱 TextView
ID: @+id/title_text
文本: Hello World
📱 Button
ID: @+id/submit_btn
文本: 提交
📱 EditText
ID: @+id/input_field
8.3 XML 转 JSON
python
import xml.etree.ElementTree as ET
import json
def xml_to_dict(element):
"""递归将 XML 元素转为字典"""
result = {}
# 添加属性
if element.attrib:
result["@attributes"] = element.attrib
# 处理子元素
children = list(element)
if children:
child_dict = {}
for child in children:
child_data = xml_to_dict(child)
# 如果有多个同名子元素,转为列表
if child.tag in child_dict:
if not isinstance(child_dict[child.tag], list):
child_dict[child.tag] = [child_dict[child.tag]]
child_dict[child.tag].append(child_data)
else:
child_dict[child.tag] = child_data
result.update(child_dict)
# 添加文本
if element.text and element.text.strip():
if result:
result["#text"] = element.text.strip()
else:
return element.text.strip()
return result
# 使用
tree = ET.parse("books.xml")
root = tree.getroot()
data = {root.tag: xml_to_dict(root)}
json_string = json.dumps(data, ensure_ascii=False, indent=2)
print(json_string)
# 保存到文件
with open("books.json", "w", encoding="utf-8") as f:
f.write(json_string)
print("\n✅ 已保存为 books.json")
8.4 批量生成 XML(如:导出报表)
python
import xml.etree.ElementTree as ET
from datetime import datetime
def generate_report_xml(students_data, output_file):
"""生成学生成绩报表 XML"""
# 创建根元素
root = ET.Element("report")
root.set("generated", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
root.set("total_students", str(len(students_data)))
# 添加元信息
meta = ET.SubElement(root, "metadata")
ET.SubElement(meta, "school").text = "XX大学"
ET.SubElement(meta, "semester").text = "2025-2026学年第二学期"
# 添加学生数据
students_elem = ET.SubElement(root, "students")
for student in students_data:
s = ET.SubElement(students_elem, "student")
s.set("id", str(student["id"]))
ET.SubElement(s, "name").text = student["name"]
ET.SubElement(s, "major").text = student["major"]
# 成绩列表
scores = ET.SubElement(s, "scores")
for course, score in student["scores"].items():
score_elem = ET.SubElement(scores, "course")
score_elem.set("name", course)
score_elem.text = str(score)
# 计算平均分
avg = sum(student["scores"].values()) / len(student["scores"])
ET.SubElement(s, "average").text = f"{avg:.1f}"
# 格式化并保存
tree = ET.ElementTree(root)
ET.indent(tree, space=" ")
tree.write(output_file, encoding="utf-8", xml_declaration=True)
print(f"✅ 报表已生成:{output_file}")
# 测试数据
students = [
{
"id": 1001,
"name": "张三",
"major": "计算机科学",
"scores": {"数学": 92, "英语": 85, "Python": 98}
},
{
"id": 1002,
"name": "李四",
"major": "软件工程",
"scores": {"数学": 78, "英语": 91, "Python": 88}
},
]
generate_report_xml(students, "report.xml")
生成的 report.xml:
python
<?xml version='1.0' encoding='utf-8'?>
<report generated="2026-07-29 11:00:00" total_students="2">
<metadata>
<school>XX大学</school>
<semester>2025-2026学年第二学期</semester>
</metadata>
<students>
<student id="1001">
<name>张三</name>
<major>计算机科学</major>
<scores>
<course name="数学">92</course>
<course name="英语">85</course>
<course name="Python">98</course>
</scores>
<average>91.7</average>
</student>
<student id="1002">
<name>李四</name>
<major>软件工程</major>
<scores>
<course name="数学">78</course>
<course name="英语">91</course>
<course name="Python">88</course>
</scores>
<average>85.7</average>
</student>
</students>
</report>
九、错误处理
python
import xml.etree.ElementTree as ET
# ============ 处理文件不存在 ============
try:
tree = ET.parse("not_exist.xml")
except FileNotFoundError:
print("❌ 文件不存在!")
# ============ 处理 XML 格式错误 ============
bad_xml = "<root><unclosed></root>" # 标签未正确关闭
try:
root = ET.fromstring(bad_xml)
except ET.ParseError as e:
print(f"❌ XML 解析错误:{e}")
# 输出:XML 解析错误:mismatched tag: line 1, column 15
# ============ 处理元素不存在 ============
xml_str = "<root><name>test</name></root>"
root = ET.fromstring(xml_str)
# find() 找不到时返回 None
age = root.find("age")
if age is None:
print("⚠️ 没有找到 age 元素")
else:
print(age.text)
# 安全获取文本的辅助函数
def safe_text(element, path, default=""):
"""安全获取元素文本"""
found = element.find(path)
if found is not None and found.text:
return found.text.strip()
return default
# 使用
name = safe_text(root, "name", "未知")
age = safe_text(root, "age", "0")
print(f"姓名:{name},年龄:{age}")
十、性能对比与选择建议
python
import time
import xml.etree.ElementTree as ET
from xml.dom import minidom
from lxml import etree
# 生成一个大 XML 用于测试
def generate_large_xml(filename, count=100000):
root = ET.Element("data")
for i in range(count):
item = ET.SubElement(root, "item", id=str(i))
ET.SubElement(item, "name").text = f"Item_{i}"
ET.SubElement(item, "value").text = str(i * 1.5)
tree = ET.ElementTree(root)
tree.write(filename)
print(f"生成了 {count} 条记录的测试文件")
generate_large_xml("large_test.xml", 100000)
# 测试 ElementTree
start = time.time()
tree = ET.parse("large_test.xml")
root = tree.getroot()
count = len(root.findall("item"))
print(f"ElementTree:{time.time()-start:.3f}秒,{count}条")
# 测试 minidom
start = time.time()
dom = minidom.parse("large_test.xml")
items = dom.getElementsByTagName("item")
print(f"minidom: {time.time()-start:.3f}秒,{len(items)}条")
# 测试 lxml
start = time.time()
tree = etree.parse("large_test.xml")
root = tree.getroot()
count = len(root.findall("item"))
print(f"lxml: {time.time()-start:.3f}秒,{count}条")
典型结果(仅供参考):
python
ElementTree:0.45秒,100000条
minidom: 2.10秒,100000条
lxml: 0.18秒,100000条
选择建议
python
┌────────────────────────────────────────────────────────┐
│ 文件 < 10MB,日常使用 │
│ → xml.etree.ElementTree ✅(够用、简单) │
├────────────────────────────────────────────────────────┤
│ 需要复杂 XPath、XSLT、高性能 │
│ → lxml ✅ │
├────────────────────────────────────────────────────────┤
│ 文件 > 100MB,内存有限 │
│ → xml.sax(流式解析)✅ │
├────────────────────────────────────────────────────────┤
│ 需要 W3C 标准 DOM 操作 │
│ → xml.dom.minidom │
└────────────────────────────────────────────────────────┘
十一、常见问题 FAQ
Q1:解析时中文乱码?
python
# 确保文件编码和声明一致
# XML 声明写 encoding="UTF-8",文件也要保存为 UTF-8
# 读取时指定编码
with open("file.xml", "r", encoding="utf-8") as f:
content = f.read()
root = ET.fromstring(content)
Q2:text 返回 None?
python
# 如果元素没有文本内容(只有子元素),text 是 None
# <book><title>xxx</title></book>
# book.text → None(因为 book 和 title 之间只有空白)
# book.find("title").text → "xxx"
# 安全写法:
text = elem.text or "" # 如果是 None 就用空字符串
text = (elem.text or "").strip() # 还要去掉首尾空白
Q3:如何忽略命名空间?
python
# 方法:解析前去掉命名空间
import re
def remove_namespace(xml_string):
"""去掉 XML 中的所有命名空间"""
return re.sub(r'xmlns[^=]*="[^"]*"', '', xml_string)
clean_xml = remove_namespace(raw_xml)
root = ET.fromstring(clean_xml)
Q4:ElementTree 和 lxml 的 API 兼容吗?
python
# 大部分兼容!切换只需改导入:
# ElementTree
import xml.etree.ElementTree as ET
# lxml(API 几乎一样)
from lxml import etree as ET
# 后面的代码基本不用改
tree = ET.parse("file.xml")
root = tree.getroot()
十二、速查表
python
解析文件: tree = ET.parse("file.xml")
解析字符串: root = ET.fromstring(xml_string)
获取根元素: root = tree.getroot()
标签名: elem.tag
文本内容: elem.text
属性: elem.get("name") / elem.attrib
找第一个: elem.find("path")
找所有: elem.findall("path")
任意深度找: elem.findall(".//tag")
按属性找: elem.find("tag[@attr='value']")
遍历所有后代: for e in root.iter():
遍历指定标签: for e in root.iter("title"):
创建元素: ET.Element("tag")
创建子元素: ET.SubElement(parent, "tag")
设置文本: elem.text = "内容"
设置属性: elem.set("key", "value")
添加子元素: parent.append(child)
删除子元素: parent.remove(child)
插入元素: parent.insert(index, child)
保存到文件: tree.write("out.xml", encoding="utf-8", xml_declaration=True)
格式化缩进: ET.indent(tree, space=" ") # Python 3.9+
十三、学习路径建议
python
第1步:理解 XML 的结构(标签、属性、文本、嵌套)
第2步:用 ET.parse() 解析文件,用 find/findall 查找元素
第3步:用 iter() 遍历,用 get() 获取属性
第4步:学习路径语法(.//tag、[@attr='val'])
第5步:练习创建和修改 XML
第6步:了解 minidom 和 SAX 的区别
第7步:(进阶)学习 lxml 和 XPath
第8步:实战(解析配置文件、RSS、API返回的XML等)