Python 提供了多种读取 XML 文件的库,从标准库中的 xml.etree.ElementTree 到第三方的高性能 lxml,各有适用场景。本文以一份包含国家信息的 test.xml 为例,演示五种主流方法的核心用法,并给出选型建议。
示例 XML 文件(test.xml)内容如下:- <?xml version="1.0"?>
- <data>
- <country name="Liechtenstein">
- <rank>1</rank>
- <year>2008</year>
- <gdppc>141100</gdppc>
- <neighbor name="Austria" direction="E"/>
- <neighbor name="Switzerland" direction="W"/>
- </country>
- <country name="Singapore">
- <rank>4</rank>
- <year>2011</year>
- <gdppc>59900</gdppc>
- <neighbor name="Malaysia" direction="N"/>
- </country>
- </data>
复制代码
方法一:xml.etree.ElementTree(标准库,轻量级)
ElementTree 是 Python 官方推荐的标准库模块,API 简洁,无需安装第三方包。它支持从文件路径或字符串解析,并提供 find/findall/iter 等便捷方法。- import xml.etree.ElementTree as ET
- # 解析文件
- tree = ET.parse('test.xml')
- root = tree.getroot()
- # 解析字符串:ET.fromstring(xml_string)
- print(f"根节点标签: {root.tag}")
- for child in root:
- print(f"子节点: {child.tag}, 属性: {child.attrib}")
- for subchild in child:
- print(f" 孙节点: {subchild.tag}, 文本: {subchild.text}")
- # findall 与 get 用法
- for country in root.findall('country'):
- name = country.get('name')
- rank = country.find('rank').text
- print(f"国家: {name}, 排名: {rank}")
- # iter 迭代查找特定标签
- for neighbor in root.iter('neighbor'):
- print(f"邻居: {neighbor.get('name')}, 方向: {neighbor.get('direction')}")
复制代码
方法二:xml.dom.minidom(标准库,完整 DOM)
minidom 实现了 W3C DOM 接口,将整个文档加载为内存树,适合需要随机访问或修改节点的场景。但内存占用较高,速度较慢。- from xml.dom import minidom
- dom = minidom.parse('test.xml')
- root = dom.documentElement
- countries = root.getElementsByTagName('country')
- print(f"找到 {len(countries)} 个国家")
- for country in countries:
- name = country.getAttribute('name')
- print(f"国家: {name}")
- rank = country.getElementsByTagName('rank')[0].firstChild.data
- year = country.getElementsByTagName('year')[0].firstChild.data
- print(f" 排名: {rank}, 年份: {year}")
- for neighbor in country.getElementsByTagName('neighbor'):
- print(f" 邻居: {neighbor.getAttribute('name')}, 方向: {neighbor.getAttribute('direction')}")
复制代码
方法三:BeautifulSoup(第三方库,灵活强大)
BeautifulSoup 原本用于 HTML 解析,但指定 XML 解析器后可完美处理 XML。安装命令:pip install beautifulsoup4,并需额外安装解析器(如 lxml-xml)。它支持 find_all、CSS 选择器,对格式不规范的文档容错性高。- from bs4 import BeautifulSoup
- with open('test.xml', 'r', encoding='utf-8') as f:
- soup = BeautifulSoup(f, 'xml') # 或 'lxml-xml'
- print(f"根节点: {soup.data.name}")
- countries = soup.find_all('country')
- for country in countries:
- print(f"国家: {country['name']}")
- print(f" 排名: {country.rank.text}, 年份: {country.year.text}")
- for neighbor in country.find_all('neighbor'):
- print(f" 邻居: {neighbor['name']}, 方向: {neighbor['direction']}")
- # CSS 选择器
- for gdppc in soup.select('gdppc'):
- print(f"人均GDP: {gdppc.text}")
复制代码
方法四:lxml.etree(第三方库,高性能)
lxml 在底层使用 C 扩展,速度极快,且完全兼容 ElementTree 的 API,同时支持 XPath 等高级查询。安装:pip install lxml。- from lxml import etree
- tree = etree.parse('test.xml')
- root = tree.getroot()
- print(f"根节点: {root.tag}")
- for country in root:
- print(f"国家属性: {country.attrib}")
- # XPath 查找所有 rank
- ranks = root.xpath('//rank')
- print("所有排名:")
- for rank in ranks:
- print(f" {rank.text}")
- # XPath 查找指定国家年份
- singapore_year = root.xpath('//country[@name="Singapore"]/year/text()')
- print(f"Singapore 年份: {singapore_year[0]}")
- # XPath 获取所有 neighbor 的 direction 属性
- directions = root.xpath('//neighbor/@direction')
- print(f"所有方向: {directions}")
复制代码
方法五:xml.sax(标准库,流式解析)
SAX(Simple API for XML)基于事件驱动,边读边解析,内存占用极低,适合处理超大型 XML 文件。但其代码编写相对繁琐,需要自定义 ContentHandler。- import xml.sax
- class CountryHandler(xml.sax.ContentHandler):
- def __init__(self):
- self.current_data = ""
- self.name = ""
- self.rank = ""
- def startElement(self, tag, attributes):
- self.current_data = tag
- if tag == "country":
- self.name = attributes["name"]
- print(f"\n国家: {self.name}")
- elif tag == "neighbor":
- print(f" 邻居: {attributes['name']}, 方向: {attributes['direction']}")
- def characters(self, content):
- if self.current_data == "rank":
- self.rank = content
- elif self.current_data == "year":
- self.year = content
- def endElement(self, tag):
- if self.current_data == "rank":
- print(f" 排名: {self.rank}")
- elif self.current_data == "year":
- print(f" 年份: {self.year}")
- self.current_data = ""
- parser = xml.sax.make_parser()
- parser.setFeature(xml.sax.handler.feature_namespaces, 0)
- handler = CountryHandler()
- parser.setContentHandler(handler)
- parser.parse("test.xml")
复制代码
方法对比一览
- xml.etree.ElementTree:标准库,内存中等,速度快,API 简单,适合大多数场景。
- xml.dom.minidom:标准库,内存高,速度慢,适合需要完整 DOM 树随机访问。
- BeautifulSoup:第三方,内存较高,速度中等,易用性极高,适合复杂或 HTML/XML 混合解析。
- lxml.etree:第三方,内存中等,速度最快,支持 XPath,适合性能敏感和高级查询。
- xml.sax:标准库,内存极低,速度较快,编码复杂,适合超大文件流式处理。
选型建议
日常脚本开发首选 xml.etree.ElementTree,零依赖且能满足大部分需求。当需要 XPath 或对性能有极致要求时,选用 lxml。处理不规范文档或需要强大查找能力时,BeautifulSoup 更加得心应手。如果 XML 文件大小超过数 GB,使用 xml.sax 避免内存溢出。仅当必须遵守 W3C DOM 规范或需要随机修改节点时,才考虑 minidom。 |