当前位置:首页>python>Python进阶教程:5_XML 解析 —— 新手完全指南

Python进阶教程:5_XML 解析 —— 新手完全指南

  • 2026-09-28 09:59:34
Python进阶教程:5_XML 解析 —— 新手完全指南

一、什么是 XML?

XML(eXtensible Markup Language,可扩展标记语言)是一种用来存储和传输数据的文本格式。

1.1 生活比喻

把 XML 想象成一个带标签的收纳盒:

  • 每个盒子有名字(标签名)
  • 盒子里可以放东西(文本内容)
  • 盒子里还可以套小盒子(子元素)
  • 每个盒子可以贴便签(属性)

1.2 XML 长什么样?

<?xml version="1.0" encoding="UTF-8"?><bookstore>    <bookcategory="编程">        <title>Python入门</title>        <author>张三</author>        <price>59.9</price>        <year>2025</year>    </book>    <bookcategory="文学">        <title>红楼梦</title>        <author>曹雪芹</author>        <price>39.0</price>        <year>1791</year>    </book></bookstore>

1.3 XML 的基本结构

<?xml version="1.0" encoding="UTF-8"?>   ← XML 声明(可选)<bookstore>                               ← 根元素(必须有且只有一个)    <bookcategory="编程">                ← 子元素,category 是"属性"        <title>Python入门</title>         ← 子元素,"Python入门"是"文本内容"        <author>张三</author>        <price>59.9</price>    </book></bookstore>

1.4 XML 的术语对照表

<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 的规则

✅ 必须有且只有一个根元素✅ 标签必须成对关闭:<title>...</title>✅ 标签必须正确嵌套:<a><b></b></a>  ✅                    <a><b></a></b>  ❌✅ 属性值必须加引号:name="张三"  ✅                    name=张三    ❌✅ 标签名区分大小写:<Title> 和 <title> 是不同的

二、Python 解析 XML 的方式概览

Python 提供了多种解析 XML 的方式:

方式
模块
特点
适合场景
ElementTreexml.etree.ElementTree
简单、轻量、Pythonic
✅ 新手首选,日常使用
minidomxml.dom.minidom
DOM方式,功能全
需要完整DOM操作
SAXxml.sax
事件驱动,流式解析
超大文件,内存敏感
lxmllxml
(第三方)
功能最强,速度快
复杂XPath、性能要求高

本教程重点讲 ElementTree(最常用),其他方式也会介绍。


三、ElementTree —— 最推荐的方式

3.1 导入模块

import xml.etree.ElementTree as ET

约定俗成:用 ET 作为别名。

3.2 准备一个 XML 文件

创建文件 books.xml:

<?xml version="1.0" encoding="UTF-8"?><bookstore>    <bookid="1"category="编程">        <title>Python编程:从入门到实践</title>        <author>Eric Matthes</author>        <pricecurrency="CNY">89.00</price>        <year>2020</year>        <tags>            <tag>Python</tag>            <tag>入门</tag>        </tags>    </book>    <bookid="2"category="文学">        <title>百年孤独</title>        <author>加西亚·马尔克斯</author>        <pricecurrency="CNY">55.00</price>        <year>1967</year>        <tags>            <tag>小说</tag>            <tag>魔幻现实主义</tag>        </tags>    </book>    <bookid="3"category="编程">        <title>算法导论</title>        <author>Thomas H. Cormen</author>        <pricecurrency="USD">128.00</price>        <year>2009</year>        <tags>            <tag>算法</tag>            <tag>计算机</tag>        </tags>    </book></bookstore>

3.3 解析 XML 文件(从文件读取)

import xml.etree.ElementTree as ET# ============ 第一步:解析文件,得到"树"对象 ============tree = ET.parse("books.xml")# ============ 第二步:获取根元素 ============root = tree.getroot()# 查看根元素的信息print(f"根元素标签:{root.tag}")        # bookstoreprint(f"根元素属性:{root.attrib}")     # {}(根元素没有属性)print(f"子元素数量:{len(root)}")       # 3(有3个book)

输出:

根元素标签:bookstore根元素属性:{}子元素数量:3

3.4 也可以从字符串解析

import xml.etree.ElementTree as ETxml_string = """<student>    <name>小明</name>    <age>20</age>    <grade>大三</grade></student>"""# fromstring() 直接从字符串解析root = ET.fromstring(xml_string)print(root.tag)                    # studentprint(root.find("name").text)      # 小明print(root.find("age").text)       # 20

3.5 遍历所有子元素

import xml.etree.ElementTree as ETtree = 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()

这是最常用的操作!

import xml.etree.ElementTree as ETtree = 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 下的 titletitle = first_book.find("title")print(f"书名:{title.text}")# ============ findall():找所有匹配的元素 ============# 找所有 <book>all_books = root.findall("book")print(f"\n共有 {len(all_books)} 本书")# 找所有 book 下的 titleall_titles = root.findall("book/title")  # 路径写法!print("所有书名:")for t in all_titles:    print(f"  - {t.text}")

输出:

第一本书的标签: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()]")
import xml.etree.ElementTree as ETtree = ET.parse("books.xml")root = tree.getroot()# 示例1:找任意深度的所有 tag 元素print("=== 所有 tag ===")for tag in root.findall(".//tag"):    print(f"  🏷️  {tag.text}")# 示例2:找 id="2" 的 bookprint("\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}")

输出:

=== 所有 tag ===  🏷️  Python  🏷️  入门  🏷️  小说  🏷️  魔幻现实主义  🏷️  算法  🏷️  计算机=== 找 id=2 的书 ===  书名:百年孤独  作者:加西亚·马尔克斯=== 所有编程类书籍 ===  📗 Python编程:从入门到实践  📗 算法导论=== 价格 > 60 的书 ===  💰 Python编程:从入门到实践 - ¥89.0  💰 算法导论 - ¥128.0

3.8 获取元素的详细信息

import xml.etree.ElementTree as ETtree = 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')}")         # 1print(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)

import xml.etree.ElementTree as ETtree = 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 带有命名空间,解析时会比较麻烦:

<?xml version="1.0"?><rootxmlns:ns="http://example.com/schema">    <ns:item>        <ns:name>测试</ns:name>    </ns:item></root>
import xml.etree.ElementTree as ETxml_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

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:

<?xml version='1.0' encoding='utf-8'?><students>    <studentid="1"class="三班">        <name>张三</name>        <age>20</age>        <score>95</score>    </student>    <studentid="2"class="一班">        <name>李四</name>        <age>21</age>        <score>88</score>    </student></students>

4.2 修改现有 XML

import xml.etree.ElementTree as ETtree = ET.parse("books.xml")root = tree.getroot()# ============ 修改文本 ============# 把第一本书的价格改为 99.00first_book = root.find("book")price = first_book.find("price")print(f"修改前价格:{price.text}")   # 89.00price.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 在指定位置插入元素

import xml.etree.ElementTree as ETroot = 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 基本用法

from xml.dom import minidom# 解析文件dom = minidom.parse("books.xml")# 获取根元素root = dom.documentElementprint(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})")

输出:

根元素: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.tagnode.tagName
文本内容
elem.textnode.firstChild.data
获取属性
elem.get("name")node.getAttribute("name")
找子元素
elem.find("title")node.getElementsByTagName("title")
所有子元素
list(elem)node.childNodes

5.4 用 minidom 创建 XML

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")

输出:

<?xml version="1.0" encoding="utf-8"?><config>    <serverhost="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 基本用法

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()

输出:

共解析了 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 安装

pip install lxml

7.2 为什么用 lxml?

  • 支持完整的 XPath(比 ElementTree 的路径语法强大得多)
  • 速度更快(底层用 C 实现)
  • 支持 XSLT 转换
  • API 和 ElementTree 兼容

7.3 基本用法

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 Counterprint("类别统计:", 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 订阅源

import xml.etree.ElementTree as ETimport urllib.requestdef 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 布局文件

import xml.etree.ElementTree as ETandroid_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}")

输出:

根布局:{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

import xml.etree.ElementTree as ETimport jsondef 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(如:导出报表)

import xml.etree.ElementTree as ETfrom datetime import datetimedef 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:

<?xml version='1.0' encoding='utf-8'?><reportgenerated="2026-07-29 11:00:00"total_students="2">    <metadata>        <school>XX大学</school>        <semester>2025-2026学年第二学期</semester>    </metadata>    <students>        <studentid="1001">            <name>张三</name>            <major>计算机科学</major>            <scores>                <coursename="数学">92</course>                <coursename="英语">85</course>                <coursename="Python">98</course>            </scores>            <average>91.7</average>        </student>        <studentid="1002">            <name>李四</name>            <major>软件工程</major>            <scores>                <coursename="数学">78</course>                <coursename="英语">91</course>                <coursename="Python">88</course>            </scores>            <average>85.7</average>        </student>    </students></report>

九、错误处理

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() 找不到时返回 Noneage = 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}")

十、性能对比与选择建议

import timeimport xml.etree.ElementTree as ETfrom xml.dom import minidomfrom 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)# 测试 ElementTreestart = time.time()tree = ET.parse("large_test.xml")root = tree.getroot()count = len(root.findall("item"))print(f"ElementTree:{time.time()-start:.3f}秒,{count}条")# 测试 minidomstart = time.time()dom = minidom.parse("large_test.xml")items = dom.getElementsByTagName("item")print(f"minidom:    {time.time()-start:.3f}秒,{len(items)}条")# 测试 lxmlstart = time.time()tree = etree.parse("large_test.xml")root = tree.getroot()count = len(root.findall("item"))print(f"lxml:       {time.time()-start:.3f}秒,{count}条")

典型结果(仅供参考):

ElementTree:0.45秒,100000条minidom:    2.10秒,100000条lxml:       0.18秒,100000条

选择建议

┌────────────────────────────────────────────────────────┐│  文件 < 10MB,日常使用                                  ││  → xml.etree.ElementTree ✅(够用、简单)               │├────────────────────────────────────────────────────────┤│  需要复杂 XPath、XSLT、高性能                            ││  → lxml ✅                                             │├────────────────────────────────────────────────────────┤│  文件 > 100MB,内存有限                                 ││  → xml.sax(流式解析)✅                                │├────────────────────────────────────────────────────────┤│  需要 W3C 标准 DOM 操作                                 ││  → xml.dom.minidom                                     │└────────────────────────────────────────────────────────┘

十一、常见问题 FAQ

Q1:解析时中文乱码?

# 确保文件编码和声明一致# XML 声明写 encoding="UTF-8",文件也要保存为 UTF-8# 读取时指定编码withopen("file.xml", "r", encoding="utf-8") as f:    content = f.read()root = ET.fromstring(content)

Q2:text 返回 None?

# 如果元素没有文本内容(只有子元素),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:如何忽略命名空间?

# 方法:解析前去掉命名空间import redef 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 兼容吗?

# 大部分兼容!切换只需改导入:# ElementTreeimport xml.etree.ElementTree as ET# lxml(API 几乎一样)from lxml import etree as ET# 后面的代码基本不用改tree = ET.parse("file.xml")root = tree.getroot()

十二、速查表

解析文件:      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+

十三、学习路径建议

第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等)

最新文章

随机文章