在开发过程中,我们经常会遇到需要处理XML格式数据的场景,比如读取应用的配置文件、解析第三方接口返回的XML格式响应等,核心需求就是把XML中的数据提取出来并存储到内存的变量中方便后续使用。不同的XML解析方式有不同的特点,我们可以根据实际需求选择合适的方法。

XML文件示例
我们先准备一个用于测试的XML文件,命名为test_data.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<user_list>
<user id="1">
<name>张三</name>
<age>25</age>
<email>zhangsan@ipipp.com</email>
</user>
<user id="2">
<name>李四</name>
<age>28</age>
<email>lisi@ipipp.com</email>
</user>
</user_list>
使用DOM解析方式读取XML到内存
DOM解析会把整个XML文档加载到内存中,形成一个树形结构,我们可以遍历这个树来获取需要的数据,适合处理较小的XML文件。
实现步骤
- 导入Python内置的
xml.dom.minidom模块 - 使用
parse方法加载XML文件,得到DOM对象 - 通过DOM对象的方法获取对应的节点,提取节点内容和属性
- 把提取到的数据存储到内存的数据结构中,比如列表、字典
代码示例
import xml.dom.minidom
def read_xml_by_dom(file_path):
# 加载XML文件到内存,得到DOM对象
dom_tree = xml.dom.minidom.parse(file_path)
# 获取根节点
root = dom_tree.documentElement
# 获取所有user节点
user_nodes = root.getElementsByTagName("user")
# 存储用户数据的列表,放在内存中
user_list = []
for user_node in user_nodes:
# 获取user节点的id属性
user_id = user_node.getAttribute("id")
# 获取name节点的文本内容
name_node = user_node.getElementsByTagName("name")[0]
name = name_node.childNodes[0].data
# 获取age节点的文本内容
age_node = user_node.getElementsByTagName("age")[0]
age = age_node.childNodes[0].data
# 获取email节点的文本内容
email_node = user_node.getElementsByTagName("email")[0]
email = email_node.childNodes[0].data
# 把单个用户数据存入字典
user_info = {
"id": user_id,
"name": name,
"age": age,
"email": email
}
user_list.append(user_info)
return user_list
if __name__ == "__main__":
result = read_xml_by_dom("test_data.xml")
# 打印内存中的用户数据
for item in result:
print(item)
使用SAX解析方式读取XML到内存
SAX解析是事件驱动的解析方式,不会一次性把整个XML加载到内存,而是边解析边触发对应的事件,适合处理较大的XML文件,内存占用更低。
实现步骤
- 导入Python内置的
xml.sax模块 - 自定义一个继承自
xml.sax.ContentHandler的处理器类,重写对应的事件方法 - 创建解析器和处理器实例,绑定后开始解析
- 在事件方法中提取数据,存储到内存的数据结构中
代码示例
import xml.sax
class UserHandler(xml.sax.ContentHandler):
def __init__(self):
self.user_list = []
self.current_user = {}
self.current_tag = ""
self.content = ""
def startElement(self, tag, attrs):
self.current_tag = tag
if tag == "user":
# 开始解析user节点,初始化当前用户数据
self.current_user = {}
self.current_user["id"] = attrs["id"]
def characters(self, content):
# 记录节点内的文本内容
if self.current_tag in ["name", "age", "email"]:
self.content += content
def endElement(self, tag):
if tag == "name":
self.current_user["name"] = self.content.strip()
elif tag == "age":
self.current_user["age"] = self.content.strip()
elif tag == "email":
self.current_user["email"] = self.content.strip()
elif tag == "user":
# user节点解析结束,把用户数据加入列表
self.user_list.append(self.current_user)
self.current_tag = ""
self.content = ""
def read_xml_by_sax(file_path):
# 创建XML解析器
parser = xml.sax.make_parser()
# 关闭命名空间处理
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
# 创建处理器实例
handler = UserHandler()
parser.setContentHandler(handler)
# 开始解析文件
parser.parse(file_path)
return handler.user_list
if __name__ == "__main__":
result = read_xml_by_sax("test_data.xml")
for item in result:
print(item)
两种方式的对比
我们可以通过下面的表格对比两种解析方式的特点,方便选择:
| 解析方式 | 内存占用 | 适用场景 | 操作复杂度 |
|---|---|---|---|
| DOM解析 | 较高,需要加载整个文档 | 小体积XML文件,需要随机访问节点 | 较低,树形结构直观 |
| SAX解析 | 较低,边解析边处理 | 大体积XML文件,只需要顺序读取 | 较高,需要自定义事件处理逻辑 |
注意事项
- 读取XML文件时要注意文件的编码格式,和XML声明中的encoding保持一致,避免出现乱码
- 如果XML文件来自不可信的来源,要注意避免XXE漏洞,不要开启外部实体解析
- 提取到的数据如果需要后续做类型转换,比如把age从字符串转为整数,要在存储到内存后做对应的处理