Python标准库自带的xml.dom.minidom模块提供了一套轻量级的DOM实现,适合处理结构清晰、体积不大的XML数据。它会把整个文档加载到内存并构建节点树,开发者可以按层级访问元素、属性和文本内容。解析时只需调用parse或parseString,拿到Document对象后通过getElementsByTagName、childNodes等接口读取数据;格式化输出则可使用toprettyxml生成带缩进的XML字符串,或直接调用writexml写入文件。需要注意的是,minidom生成的文本节点可能包含空白字符,遍历时通常要结合nodeType做判断。相比ElementTree,它的API更贴近W3C DOM标准,但内存占用较高,不适合超大XML。这篇教程会用完整示例演示如何加载、遍历、修改和美化输出XML。

一、使用parse与parseString加载XML文档
minidom解析XML有两种入口:parse用于文件对象或文件路径,parseString用于直接解析字符串。两者都会返回一个Document对象,表示整棵文档树。拿到Document对象后,通常先访问documentElement获取根元素,再用getElementsByTagName按标签名筛选节点。下面示例中的XML字符串包含两个book元素,每个book带有id属性以及title、author、price子元素。代码中根元素是<books>,但为演示属性读取,我们直接通过getElementsByTagName获得所有book节点,并取出第一个book的id属性。
运行这段代码会输出根元素名称books、book节点数量2以及第一个book的id值1001。这里要注意,getElementsByTagName返回的是NodeList而不是Python列表,它支持索引访问和length属性,但不能直接使用列表推导式,需要先转换成list。另外,如果XML声明中包含encoding,parseString会自动处理字节到字符串的转换,但在使用时建议统一传入字符串,避免编码混淆。
import xml.dom.minidom
xml_data = '''<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1001">
<title>Python核心编程</title>
<author>张三</author>
<price>89.00</price>
</book>
<book id="1002">
<title>XML数据解析</title>
<author>李四</author>
<price>65.50</price>
</book>
</books>
'''
dom = xml.dom.minidom.parseString(xml_data)
root = dom.documentElement
print(root.tagName)
books = dom.getElementsByTagName('book')
print(books.length)
print(books[0].getAttribute('id'))
补充一点,documentElement永远指向根元素节点,而childNodes则返回该节点下所有子节点,包括元素节点和文本节点。初学者在获取子元素时经常直接遍历childNodes却忽略了空白文本节点,导致打印出多余的空行或换行。解决方式有两种:一是通过getElementsByTagName直接定位目标节点,二是在遍历时判断nodeType是否等于Node.ELEMENT_NODE。这个小细节在第二节会重点展开。
二、遍历节点与提取文本内容
DOM解析的核心动作是遍历节点树,minidom为每个节点提供了nodeType、nodeName、nodeValue、childNodes等属性。元素节点的nodeType值为1,文本节点为3,注释节点为8。很多从BeautifulSoup切过来的开发者容易直接用node.childNodes[0].nodeValue获取文本,这在紧凑XML中有效,但一旦XML带有缩进和换行,子节点列表中就会混入代表空白字符的文本节点,直接用下标取值可能得到空字符串或换行符。更稳妥的方法是写一个递归函数,只收集文本节点的data属性。
下面给出的get_text函数会判断节点类型:遇到文本节点直接返回data;遇到元素节点则递归处理其所有子节点并将结果拼接。这样即使XML格式化得再漂亮,也能准确提取标签内的可见文本。随后遍历所有book节点,分别读取title、author和price,输出的价格显示为89.00和65.50。采用递归方式的好处是兼容嵌套层级更深的XML结构,例如某个title内部还有强调标签时,依然能获取完整文本。
def get_text(node):
if node.nodeType == node.TEXT_NODE:
return node.data
if node.nodeType == node.ELEMENT_NODE:
result = []
for child in node.childNodes:
result.append(get_text(child))
return ''.join(result)
return ''
for book in dom.getElementsByTagName('book'):
title = book.getElementsByTagName('title')[0]
author = book.getElementsByTagName('author')[0]
price = book.getElementsByTagName('price')[0]
print('书名:', get_text(title))
print('作者:', get_text(author))
print('价格:', get_text(price))
此外,minidom还提供一个normalize方法,可以把相邻的文本节点合并成一个,减少遍历时的判断负担。调用root.normalize()之后,元素节点下的连续文本会合并,但该方法不会移除空白文本节点,所以如果XML中存在大量缩进,仍然需要在提取文本时做过滤。实际项目中,如果只是读取少量字段,推荐使用getElementsByTagName配合上面提到的递归函数;如果需要处理复杂文档结构,则建议封装一个通用的节点遍历器,统一处理元素、属性和文本节点。
三、修改节点并格式化输出XML
minidom的写操作依赖createElement、createTextNode和appendChild等方法。创建新元素后,需要通过appendChild挂到目标父节点下,设置属性则使用setAttribute。下面的示例新建一个id为1003的book元素,添加title、author、price三个子元素,然后追加到根节点books的末尾。这种操作会直接修改内存中的DOM树,但不会自动写回文件,需要调用toprettyxml或writexml生成输出字符串或写入文件。
new_book = dom.createElement('book')
new_book.setAttribute('id', '1003')
title = dom.createElement('title')
title.appendChild(dom.createTextNode('深入理解DOM解析'))
new_book.appendChild(title)
author = dom.createElement('author')
author.appendChild(dom.createTextNode('王五'))
new_book.appendChild(author)
price = dom.createElement('price')
price.appendChild(dom.createTextNode('79.00'))
new_book.appendChild(price)
root.appendChild(new_book)
formatted_xml = dom.toprettyxml(indent=' ')
print(formatted_xml)
with open('output.xml', 'w', encoding='utf-8') as f:
f.write(formatted_xml)
toprettyxml是minidom最常用的格式化输出工具,它接受indent参数控制缩进宽度,还可以通过encoding参数返回指定编码的字节串。上面的代码使用indent为两个空格,打印后会得到层次清晰的XML文本。不过toprettyxml有一个已知问题:它可能会在元素之间插入额外的空白文本节点,如果后续还需要继续解析或修改文档,这些空白节点会出现在childNodes中,所以建议在最终输出前再调用toprettyxml,而不要在修改中间频繁调用。
如果希望直接写入文件,可以使用writexml方法,配合file对象输出。writexml要求显式指定缩进字符和换行符,否则默认输出的XML不会自动换行。例如可以这样写:with open('output.xml', 'w', encoding='utf-8') as f: dom.writexml(f, indent=' ', addindent=' ', newl='\n', encoding='utf-8')。注意这里的文件编码要与XML声明保持一致,避免写入后出现乱码。删除节点可以用父节点的removeChild方法,需要传入待删除的子节点引用;删除属性则用removeAttribute。
四、minidom的适用场景与常见问题
minidom的API设计接近W3C DOM规范,对于熟悉浏览器DOM操作的开发者比较友好,方法命名也较直观。但它的代价是内存开销较大,因为每个节点都包装成Python对象,并且节点之间保留较多冗余信息。如果XML文件只有几百KB或者几MB,使用minidom完全可行;如果文件达到几十MB甚至更大,解析时会出现明显卡顿甚至内存不足。此时可以考虑使用xml.etree.ElementTree或基于事件驱动的SAX解析器,它们的内存占用更低。
另一个常见问题是空白文本节点的干扰。minidom在解析带缩进的XML时,会把缩进和换行当作文本节点保留下来。很多人第一次遍历childNodes时会发现节点数量比预期多出很多,就是因为把空白文本也算进去了。处理方式除了前面提到的nodeType判断,还可以在解析前对XML字符串做正则清理,去掉标签之间的多余空白,但这可能会破坏标签内部的空格内容,因此不建议在通用场景中使用。更可靠的方法是在提取数据时统一通过递归或辅助函数过滤文本节点。
在处理编码时,minidom默认会从XML声明中读取encoding并自动解码,但如果传入的是二进制字符串且声明缺失,可能抛出异常。建议始终使用UTF-8编码的字符串作为输入,文件读取时也显式指定encoding='utf-8'。输出时如果要保留中文注释或中文内容,toprettyxml配合UTF-8编码即可正常显示。minidom不支持从URL直接解析,需要先用urllib或requests获取内容再调用parseString,这也是与一些XML解析库不同的地方。整体来看,minidom适合中小型XML的读写、格式化和快速原型验证,尤其在需要按W3C DOM方式操作节点时仍然是一个稳定的标准库选择。
最后需要提醒的是,minidom不会自动转义特殊字符。如果文本内容包含小于号、大于号或与号,创建文本节点时需要使用createTextNode,不要直接拼接XML字符串。正确的做法是让DOM API处理转义,如果手工拼接字符串再解析,很容易因为未转义的特殊符号导致解析错误。掌握这些细节之后,用minidom解析和格式化XML就能更加顺畅。
Python minidomXML解析XML格式化修改时间:2026-10-04 20:42:22