Skip to content

Python XML 解析

XML(可扩展标记语言)是一种用于存储和传输数据的标记语言,它具有自我描述性和平台无关性。Python 提供了多种库来解析和处理 XML 数据,本章节将详细介绍这些库的使用方法。

XML 基础

XML 文档结构

XML 文档由以下部分组成:

  1. 声明:XML 文档的第一行,指定 XML 版本和编码
  2. 根元素:XML 文档的顶级元素
  3. 子元素:根元素内的元素
  4. 属性:元素的属性
  5. 文本:元素内的文本内容

XML 示例

xml
<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book id="1">
        <title>Python Programming</title>
        <author>John Doe</author>
        <year>2023</year>
        <price>29.99</price>
    </book>
    <book id="2">
        <title>Advanced Python</title>
        <author>Jane Smith</author>
        <year>2024</year>
        <price>39.99</price>
    </book>
</library>

Python XML 解析库

Python 中常用的 XML 解析库包括:

  1. xml.etree.ElementTree:Python 标准库,提供了简单高效的 XML 解析功能
  2. xml.dom.minidom:Python 标准库,提供了 DOM(文档对象模型)风格的 XML 解析
  3. lxml:第三方库,提供了更强大和灵活的 XML 解析功能

本章节将详细介绍这些库的使用方法。

使用 xml.etree.ElementTree

xml.etree.ElementTree 是 Python 标准库中推荐的 XML 解析库,它提供了简单高效的 XML 解析功能。

解析 XML 文件

python
import xml.etree.ElementTree as ET

# 解析 XML 文件
tree = ET.parse('books.xml')
root = tree.getroot()

# 打印根元素
print(f"根元素: {root.tag}")

# 遍历子元素
for book in root.findall('book'):
    # 获取属性
    book_id = book.get('id')
    # 获取子元素文本
    title = book.find('title').text
    author = book.find('author').text
    year = book.find('year').text
    price = book.find('price').text
    
    print(f"\n书籍 ID: {book_id}")
    print(f"标题: {title}")
    print(f"作者: {author}")
    print(f"年份: {year}")
    print(f"价格: {price}")

解析 XML 字符串

python
import xml.etree.ElementTree as ET

# XML 字符串
xml_string = '''
<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book id="1">
        <title>Python Programming</title>
        <author>John Doe</author>
        <year>2023</year>
        <price>29.99</price>
    </book>
    <book id="2">
        <title>Advanced Python</title>
        <author>Jane Smith</author>
        <year>2024</year>
        <price>39.99</price>
    </book>
</library>
'''

# 解析 XML 字符串
root = ET.fromstring(xml_string)

# 打印根元素
print(f"根元素: {root.tag}")

# 遍历子元素
for book in root.findall('book'):
    # 获取属性
    book_id = book.get('id')
    # 获取子元素文本
    title = book.find('title').text
    author = book.find('author').text
    year = book.find('year').text
    price = book.find('price').text
    
    print(f"\n书籍 ID: {book_id}")
    print(f"标题: {title}")
    print(f"作者: {author}")
    print(f"年份: {year}")
    print(f"价格: {price}")

查找元素

python
import xml.etree.ElementTree as ET

# 解析 XML 文件
tree = ET.parse('books.xml')
root = tree.getroot()

# 查找所有 book 元素
print("所有书籍:")
for book in root.findall('book'):
    title = book.find('title').text
    print(f"- {title}")

# 查找特定属性的 book 元素
print("\nID 为 1 的书籍:")
book = root.find("book[@id='1']")
if book:
    title = book.find('title').text
    author = book.find('author').text
    print(f"标题: {title}")
    print(f"作者: {author}")

# 查找价格大于 30 的书籍
print("\n价格大于 30 的书籍:")
for book in root.findall('book'):
    price = float(book.find('price').text)
    if price > 30:
        title = book.find('title').text
        print(f"- {title} (价格: {price})")

# 递归查找所有 title 元素
print("\n所有标题:")
for title in root.iter('title'):
    print(f"- {title.text}")

修改 XML

python
import xml.etree.ElementTree as ET

# 解析 XML 文件
tree = ET.parse('books.xml')
root = tree.getroot()

# 修改元素文本
print("修改前:")
for book in root.findall('book'):
    price = book.find('price')
    print(f"{book.find('title').text}: {price.text}")

# 增加所有书籍的价格
for book in root.findall('book'):
    price = book.find('price')
    current_price = float(price.text)
    new_price = current_price * 1.1  # 增加 10%
    price.text = str(round(new_price, 2))

# 添加新元素
new_book = ET.SubElement(root, 'book', id='3')
ET.SubElement(new_book, 'title').text = 'Python for Beginners'
ET.SubElement(new_book, 'author').text = 'Bob Brown'
ET.SubElement(new_book, 'year').text = '2022'
ET.SubElement(new_book, 'price').text = '19.99'

# 删除元素
book_to_remove = root.find("book[@id='1']")
if book_to_remove:
    root.remove(book_to_remove)
    print("\n已删除 ID 为 1 的书籍")

# 保存修改后的 XML
output_file = 'modified_books.xml'
tree.write(output_file, encoding='UTF-8', xml_declaration=True)
print(f"\n修改后的 XML 已保存到 {output_file}")

# 打印修改后的内容
print("\n修改后:")
tree = ET.parse(output_file)
root = tree.getroot()
for book in root.findall('book'):
    title = book.find('title').text
    price = book.find('price').text
    print(f"{title}: {price}")

创建 XML

python
import xml.etree.ElementTree as ET

# 创建根元素
root = ET.Element('library')

# 创建子元素
book1 = ET.SubElement(root, 'book', id='1')
ET.SubElement(book1, 'title').text = 'Python Programming'
ET.SubElement(book1, 'author').text = 'John Doe'
ET.SubElement(book1, 'year').text = '2023'
ET.SubElement(book1, 'price').text = '29.99'

book2 = ET.SubElement(root, 'book', id='2')
ET.SubElement(book2, 'title').text = 'Advanced Python'
ET.SubElement(book2, 'author').text = 'Jane Smith'
ET.SubElement(book2, 'year').text = '2024'
ET.SubElement(book2, 'price').text = '39.99'

# 创建 ElementTree 对象
tree = ET.ElementTree(root)

# 保存到文件
output_file = 'new_books.xml'
tree.write(output_file, encoding='UTF-8', xml_declaration=True)
print(f"XML 文件已创建: {output_file}")

# 打印创建的 XML
print("\n创建的 XML 内容:")
with open(output_file, 'r', encoding='UTF-8') as f:
    print(f.read())

使用 xml.dom.minidom

xml.dom.minidom 是 Python 标准库中的另一个 XML 解析库,它提供了 DOM(文档对象模型)风格的 XML 解析。

解析 XML 文件

python
from xml.dom.minidom import parse

# 解析 XML 文件
doc = parse('books.xml')

# 获取根元素
root = doc.documentElement
print(f"根元素: {root.tagName}")

# 获取所有 book 元素
books = root.getElementsByTagName('book')
print(f"\n共有 {books.length} 本书籍")

# 遍历 book 元素
for i, book in enumerate(books):
    # 获取属性
    book_id = book.getAttribute('id')
    print(f"\n书籍 {i+1} ID: {book_id}")
    
    # 获取子元素
    title = book.getElementsByTagName('title')[0].firstChild.nodeValue
    author = book.getElementsByTagName('author')[0].firstChild.nodeValue
    year = book.getElementsByTagName('year')[0].firstChild.nodeValue
    price = book.getElementsByTagName('price')[0].firstChild.nodeValue
    
    print(f"标题: {title}")
    print(f"作者: {author}")
    print(f"年份: {year}")
    print(f"价格: {price}")

解析 XML 字符串

python
from xml.dom.minidom import parseString

# XML 字符串
xml_string = '''
<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book id="1">
        <title>Python Programming</title>
        <author>John Doe</author>
        <year>2023</year>
        <price>29.99</price>
    </book>
    <book id="2">
        <title>Advanced Python</title>
        <author>Jane Smith</author>
        <year>2024</year>
        <price>39.99</price>
    </book>
</library>
'''

# 解析 XML 字符串
doc = parseString(xml_string)

# 获取根元素
root = doc.documentElement
print(f"根元素: {root.tagName}")

# 获取所有 book 元素
books = root.getElementsByTagName('book')
print(f"\n共有 {books.length} 本书籍")

# 遍历 book 元素
for i, book in enumerate(books):
    # 获取属性
    book_id = book.getAttribute('id')
    print(f"\n书籍 {i+1} ID: {book_id}")
    
    # 获取子元素
    title = book.getElementsByTagName('title')[0].firstChild.nodeValue
    author = book.getElementsByTagName('author')[0].firstChild.nodeValue
    year = book.getElementsByTagName('year')[0].firstChild.nodeValue
    price = book.getElementsByTagName('price')[0].firstChild.nodeValue
    
    print(f"标题: {title}")
    print(f"作者: {author}")
    print(f"年份: {year}")
    print(f"价格: {price}")

创建和修改 XML

python
from xml.dom.minidom import Document

# 创建文档
doc = Document()

# 创建根元素
library = doc.createElement('library')
doc.appendChild(library)

# 创建第一个 book 元素
book1 = doc.createElement('book')
book1.setAttribute('id', '1')
library.appendChild(book1)

# 添加子元素
 title1 = doc.createElement('title')
 title1.appendChild(doc.createTextNode('Python Programming'))
book1.appendChild(title1)

 author1 = doc.createElement('author')
 author1.appendChild(doc.createTextNode('John Doe'))
book1.appendChild(author1)

 year1 = doc.createElement('year')
 year1.appendChild(doc.createTextNode('2023'))
book1.appendChild(year1)

 price1 = doc.createElement('price')
 price1.appendChild(doc.createTextNode('29.99'))
book1.appendChild(price1)

# 创建第二个 book 元素
book2 = doc.createElement('book')
book2.setAttribute('id', '2')
library.appendChild(book2)

 title2 = doc.createElement('title')
 title2.appendChild(doc.createTextNode('Advanced Python'))
book2.appendChild(title2)

 author2 = doc.createElement('author')
 author2.appendChild(doc.createTextNode('Jane Smith'))
book2.appendChild(author2)

 year2 = doc.createElement('year')
 year2.appendChild(doc.createTextNode('2024'))
book2.appendChild(year2)

 price2 = doc.createElement('price')
 price2.appendChild(doc.createTextNode('39.99'))
book2.appendChild(price2)

# 保存到文件
output_file = 'dom_books.xml'
with open(output_file, 'w', encoding='UTF-8') as f:
    f.write(doc.toprettyxml(indent='  '))

print(f"XML 文件已创建: {output_file}")

# 打印创建的 XML
print("\n创建的 XML 内容:")
with open(output_file, 'r', encoding='UTF-8') as f:
    print(f.read())

使用 lxml

lxml 是一个第三方库,提供了更强大和灵活的 XML 解析功能,它基于 C 语言的 libxml2 和 libxslt 库,性能比标准库更好。

安装 lxml

bash
pip install lxml

解析 XML 文件

python
from lxml import etree

# 解析 XML 文件
tree = etree.parse('books.xml')
root = tree.getroot()

# 打印根元素
print(f"根元素: {root.tag}")

# 遍历子元素
for book in root.findall('book'):
    # 获取属性
    book_id = book.get('id')
    # 获取子元素文本
    title = book.find('title').text
    author = book.find('author').text
    year = book.find('year').text
    price = book.find('price').text
    
    print(f"\n书籍 ID: {book_id}")
    print(f"标题: {title}")
    print(f"作者: {author}")
    print(f"年份: {year}")
    print(f"价格: {price}")

使用 XPath 查找元素

python
from lxml import etree

# 解析 XML 文件
tree = etree.parse('books.xml')
root = tree.getroot()

# 使用 XPath 查找所有 book 元素
print("所有书籍:")
books = root.xpath('//book')
for book in books:
    title = book.xpath('./title/text()')[0]
    print(f"- {title}")

# 使用 XPath 查找特定属性的 book 元素
print("\nID 为 1 的书籍:")
book = root.xpath('//book[@id="1"]')[0]
title = book.xpath('./title/text()')[0]
author = book.xpath('./author/text()')[0]
print(f"标题: {title}")
print(f"作者: {author}")

# 使用 XPath 查找价格大于 30 的书籍
print("\n价格大于 30 的书籍:")
books = root.xpath('//book[price > 30]')
for book in books:
    title = book.xpath('./title/text()')[0]
    price = book.xpath('./price/text()')[0]
    print(f"- {title} (价格: {price})")

# 使用 XPath 查找所有标题
print("\n所有标题:")
titles = root.xpath('//title/text()')
for title in titles:
    print(f"- {title}")

# 使用 XPath 查找第二本书的作者
print("\n第二本书的作者:")
author = root.xpath('//book[2]/author/text()')[0]
print(f"- {author}")

修改 XML

python
from lxml import etree

# 解析 XML 文件
tree = etree.parse('books.xml')
root = tree.getroot()

# 修改元素文本
print("修改前:")
for book in root.findall('book'):
    price = book.find('price')
    print(f"{book.find('title').text}: {price.text}")

# 增加所有书籍的价格
for book in root.findall('book'):
    price = book.find('price')
    current_price = float(price.text)
    new_price = current_price * 1.1  # 增加 10%
    price.text = str(round(new_price, 2))

# 添加新元素
new_book = etree.SubElement(root, 'book', id='3')
etree.SubElement(new_book, 'title').text = 'Python for Beginners'
etree.SubElement(new_book, 'author').text = 'Bob Brown'
etree.SubElement(new_book, 'year').text = '2022'
etree.SubElement(new_book, 'price').text = '19.99'

# 删除元素
book_to_remove = root.find("book[@id='1']")
if book_to_remove is not None:
    root.remove(book_to_remove)
    print("\n已删除 ID 为 1 的书籍")

# 保存修改后的 XML
output_file = 'lxml_modified_books.xml'
tree.write(output_file, encoding='UTF-8', xml_declaration=True, pretty_print=True)
print(f"\n修改后的 XML 已保存到 {output_file}")

# 打印修改后的内容
print("\n修改后:")
tree = etree.parse(output_file)
root = tree.getroot()
for book in root.findall('book'):
    title = book.find('title').text
    price = book.find('price').text
    print(f"{title}: {price}")

创建 XML

python
from lxml import etree

# 创建根元素
root = etree.Element('library')

# 创建子元素
book1 = etree.SubElement(root, 'book', id='1')
etree.SubElement(book1, 'title').text = 'Python Programming'
etree.SubElement(book1, 'author').text = 'John Doe'
etree.SubElement(book1, 'year').text = '2023'
etree.SubElement(book1, 'price').text = '29.99'

book2 = etree.SubElement(root, 'book', id='2')
etree.SubElement(book2, 'title').text = 'Advanced Python'
etree.SubElement(book2, 'author').text = 'Jane Smith'
etree.SubElement(book2, 'year').text = '2024'
etree.SubElement(book2, 'price').text = '39.99'

# 创建 ElementTree 对象
tree = etree.ElementTree(root)

# 保存到文件
output_file = 'lxml_new_books.xml'
tree.write(output_file, encoding='UTF-8', xml_declaration=True, pretty_print=True)
print(f"XML 文件已创建: {output_file}")

# 打印创建的 XML
print("\n创建的 XML 内容:")
with open(output_file, 'r', encoding='UTF-8') as f:
    print(f.read())

XML 解析的实际应用

示例 1:解析 RSS 订阅

python
import xml.etree.ElementTree as ET
import requests

# 获取 RSS 订阅内容
url = "https://www.python.org/blogs/feed/"
response = requests.get(url)
xml_content = response.content

# 解析 XML
root = ET.fromstring(xml_content)

# 打印频道信息
channel = root.find('channel')
title = channel.find('title').text
description = channel.find('description').text
link = channel.find('link').text

print(f"频道标题: {title}")
print(f"频道描述: {description}")
print(f"频道链接: {link}")

# 打印最新的 5 篇文章
print("\n最新的 5 篇文章:")
items = channel.findall('item')[:5]
for i, item in enumerate(items):
    item_title = item.find('title').text
    item_link = item.find('link').text
    item_pubdate = item.find('pubDate').text
    
    print(f"\n{i+1}. {item_title}")
    print(f"链接: {item_link}")
    print(f"发布日期: {item_pubdate}")

示例 2:生成 XML 配置文件

python
import xml.etree.ElementTree as ET

# 创建配置文件
root = ET.Element('configuration')

# 添加数据库配置
database = ET.SubElement(root, 'database')
ET.SubElement(database, 'host').text = 'localhost'
ET.SubElement(database, 'port').text = '3306'
ET.SubElement(database, 'user').text = 'root'
ET.SubElement(database, 'password').text = 'password'
ET.SubElement(database, 'name').text = 'test_db'

# 添加服务器配置
server = ET.SubElement(root, 'server')
ET.SubElement(server, 'host').text = '0.0.0.0'
ET.SubElement(server, 'port').text = '8080'
ET.SubElement(server, 'debug').text = 'true'

# 添加日志配置
logging = ET.SubElement(root, 'logging')
ET.SubElement(logging, 'level').text = 'INFO'
ET.SubElement(logging, 'file').text = 'app.log'
ET.SubElement(logging, 'format').text = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

# 创建 ElementTree 对象
tree = ET.ElementTree(root)

# 保存到文件
output_file = 'config.xml'
tree.write(output_file, encoding='UTF-8', xml_declaration=True)

print(f"配置文件已创建: {output_file}")

# 打印创建的配置文件
print("\n创建的配置文件内容:")
with open(output_file, 'r', encoding='UTF-8') as f:
    print(f.read())

示例 3:解析和处理大型 XML 文件

对于大型 XML 文件,可以使用迭代器来减少内存使用:

python
import xml.etree.ElementTree as ET

# 创建 XML 文件
large_xml = '''
<?xml version="1.0" encoding="UTF-8"?>
<data>
'''

# 添加 1000 个元素
for i in range(1000):
    large_xml += f'    <item id="{i+1}"><name>Item {i+1}</name><value>{i+1}</value></item>\n'

large_xml += '''
</data>
'''

# 保存到文件
with open('large.xml', 'w', encoding='UTF-8') as f:
    f.write(large_xml)

print("大型 XML 文件已创建: large.xml")

# 使用迭代器解析大型 XML 文件
print("\n解析大型 XML 文件:")
count = 0
sum_value = 0

# 使用 iterparse 迭代解析
for event, elem in ET.iterparse('large.xml', events=('start', 'end')):
    if event == 'end' and elem.tag == 'item':
        # 获取元素属性和文本
        item_id = elem.get('id')
        name = elem.find('name').text
        value = int(elem.find('value').text)
        
        # 处理数据
        count += 1
        sum_value += value
        
        # 每 100 个元素打印一次
        if count % 100 == 0:
            print(f"已处理 {count} 个元素,当前总和: {sum_value}")
        
        # 清除元素,释放内存
        elem.clear()

print(f"\n解析完成,共处理 {count} 个元素,总和: {sum_value}")

XML 解析的最佳实践

1. 选择合适的解析库

  • 小型 XML 文件:使用 xml.etree.ElementTree 即可
  • 大型 XML 文件:使用 xml.etree.ElementTree.iterparse()lxml.etree.iterparse()
  • 需要 XPath 支持:使用 lxml
  • 需要 DOM 接口:使用 xml.dom.minidom

2. 错误处理

  • 捕获解析错误:使用 try-except 捕获 XML 解析错误
  • 验证 XML 格式:使用 XML Schema 或 DTD 验证 XML 格式
  • 处理命名空间:注意处理 XML 命名空间

3. 性能优化

  • 使用迭代器:对于大型 XML 文件,使用迭代器解析
  • 清除元素:使用 iterparse 时,及时清除元素释放内存
  • 使用 lxml:对于性能要求高的场景,使用 lxml 库

4. 安全性

  • 避免 XML 注入:处理用户输入时,避免 XML 注入攻击
  • 限制解析大小:限制 XML 文件的大小,防止拒绝服务攻击
  • 使用安全的解析模式:使用 resolve_entities=False 避免实体扩展攻击

5. 代码可读性

  • 使用有意义的变量名:提高代码可读性
  • 添加注释:解释复杂的 XML 结构和解析逻辑
  • 模块化:将 XML 解析逻辑封装到函数或类中

总结

本章节介绍了 Python 中的 XML 解析,包括:

  1. XML 基础:XML 文档结构和示例
  2. Python XML 解析库:xml.etree.ElementTree、xml.dom.minidom、lxml
  3. xml.etree.ElementTree:解析、查找、修改、创建 XML
  4. xml.dom.minidom:DOM 风格的 XML 解析
  5. lxml:更强大的 XML 解析库,支持 XPath
  6. 实际应用示例:解析 RSS 订阅、生成 XML 配置文件、处理大型 XML 文件
  7. 最佳实践:选择合适的解析库、错误处理、性能优化、安全性、代码可读性

掌握 XML 解析技术,对于处理配置文件、数据交换、Web 服务等场景非常重要。通过合理选择和使用 XML 解析库,可以高效地处理各种 XML 数据。