Python - DOM操作XML技巧汇总

本文最后更新于:2022年8月10日 上午

XML已经成为数据传输存储使用越来越广泛的数据格式,本文讲述使用Python DOM处理XML文件的方法。

准备工作

Python常用处理XML库

常见的 XML 编程接口有 DOM 和 SAX,这两种接口处理 XML 文件的方式不同,当然使用场合也不同。Python 有三种方法解析 XML,SAX,DOM,以及 ElementTree:

DOM(Document Object Model) - 本文介绍

DOM (Document Object Model) 译为文档对象模型,是 HTML 和 XML 文档的编程接口。HTML DOM 定义了访问和操作 HTML 文档的标准方法。将 XML 数据在内存中解析成一个树,通过对树的操作来操作XML。

SAX (simple API for XML )

Python 标准库包含 SAX 解析器,SAX 用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。

ElementTree(元素树)

ElementTree就像一个轻量级的DOM,具有方便友好的API。代码可用性好,速度快,消耗内存少。

测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<!--file name "test.xml" 2020.2.24 by VVD-->
<bookstore type="info science">

<book category="Coding">
<title>C++Primer</title>
<author>StanleyB.Lippman</author>
<price>60.0</price>
<year>2009</year>
</book>

<book category="Computer">
<title>编译原理</title>
<author>AlfredV.Aho</author>
<price>70.0</price>
<year>2013</year>
</book>

</bookstore>

Python DOM 解析XML

DOM节点树

一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里。

节点级别

节点树中的节点彼此之间都有等级关系。

  • 在节点树中,顶端的节点成为根节点
  • 根节点之外的每个节点都有一个父节点
  • 节点可以有任何数量的子节点
  • 叶子是没有子节点的节点
  • 同级节点是拥有相同父节点的节点

解析XML

python 加载DOM解析XML,并输出整个XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#加载DOM minidom模块
from xml.dom.minidom import parse

#解析XML文件
contents=parse('test.xml')
root=contents.documentElement

print(root.toxml())

>>><bookstore type="info science">

<book category="Coding">
<title>C++Primer</title>
<author>StanleyB.Lippman</author>
<price>60.0</price>
<year>2009</year>
</book>

<book category="Computer">
<title>编译原理</title>
<author>AlfredV.Aho</author>
<price>70.0</price>
<year>2013</year>
</book>

</bookstore>

此时root获取了XML文件中所有内容,组织成了一棵树,root就是该树的根节点

获取节点名称

1
2
3
4
5
6
#获取节点名称
print(root.nodeName)
print(root.tagName)

>>> bookstore
bookstore

获取子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#获取子节点
children_Nodes=root.childNodes
print(f'子节点个数:{children_Nodes.length}')

for item in children_Nodes:
print (item.toxml())

>>> 子节点个数:5



<book category="Coding">
<title>C++Primer</title>
<author>StanleyB.Lippman</author>
<price>60.0</price>
<year>2009</year>
</book>



<book category="Computer">
<title>编译原理</title>
<author>AlfredV.Aho</author>
<price>70.0</price>
<year>2013</year>
</book>

节点类型

为什么2本书却有5个节点?

因为两本书前中后各有一个回车,被划为文本节点。

DOM规定节点:

  1. 整个文档是一个文档节点
  2. 每个 XML 标签是一个元素节点
  3. 包含在 XML 元素中的文本是文本节点
  4. 每一个 XML 属性是一个属性节点
  5. 注释属于注释节点

文本总是存储在文本节点中

在 DOM 处理中一个普遍的错误是,认为元素节点包含文本。

不过,元素节点的文本是存储在文本节点中的。

在这个例子中:<year>2005</year>,元素节点 <year>,拥有一个值为 “2005” 的文本节点。

“2005” 不是 <year> 元素的值!d

获取节点属性

1
2
3
4
5
6
7
8
9
#获取节点属性
print(root.attributes.items())
atr_root=root.attributes['type']
print(atr_root.name)
print(atr_root.value)

>>>[('type', 'info science')]
type
info science

获取指定名称的节点

1
2
3
4
5
6
7
8
#获取指定名称的节点
books=root.getElementsByTagName('book')

for book in books:
print (book.nodeName)

>>>book
book

判断是否包含属性值

1
2
3
4
5
6
#判断是否包含属性值
print(books[0].hasAttribute('nnn'))
print(books[0].hasAttribute('category'))

>>>False
True

获取节点指定属性的属性值

1
2
3
4
5
6
#获取节点指定属性的属性值
print(books[0].getAttribute('category'))
print(books[1].getAttribute('category'))

>>>Coding
Computer

获取节点文本值

1
2
3
4
5
6
#获取节点文本值
node=books[0]
author=node.getElementsByTagName('author')
print(author[0].firstChild.data)

>>>StanleyB.Lippman

判断是否有子节点

1
2
3
4
5
6
7
8
#判断是否有子节点
print(root.hasChildNodes())
print(author[0].hasChildNodes())
print(author[0].firstChild.hasChildNodes())

>>>True
True
False

DOM解析XML方法总结

  • contents = xml.dom.minidom.parse(filename):加载读取XML文件
  • node = contents.documentElement:获取XML文档对象
  • node.getAttribute(AttributeName):获取XML节点属性值
  • node.getElementsByTagName(TagName):获取XML节点对象集合
  • node.childNodes :返回子节点列表。
  • node.childNodes[index].nodeValue:获取XML节点值
  • node.firstChild:访问第一个节点,等价于pagexml.childNodes[0]
  • node.toxml(‘UTF-8’)返回Node节点的xml表示的文本:
  • 访问元素属性:
    atr=Node.attributes[“id”]
    atr.name # “id”
    atr.value #属性的值
  • node.nodeName/node.tagName:节点的名称
  • node.nodeValue:节点的值,文本节点才有值,其它节点返回的是None
  • node.nodeType:节点的类型

Python DOM 修改XML

生成XML树、添加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from xml.dom import minidom

#1.创建DOM树对象
dom=minidom.Document()
# 2.创建根节点。每次都要用DOM对象来创建任何节点。
root_node=dom.createElement('root')
# 3.用DOM对象添加根节点
dom.appendChild(root_node)
# 用DOM对象创建元素子节点
book_node=dom.createElement('book')
# 用父节点对象添加元素子节点
root_node.appendChild(book_node)
# 设置该节点的属性
book_node.setAttribute('price','399')
name_node=dom.createElement('name')
book_node.appendChild(name_node)
# 也用DOM创建文本节点,把文本节点(文字内容)看成子节点
name_text=dom.createTextNode('C++ Primer 第1版')
# 用添加了文本的节点对象(看成文本节点的父节点)添加文本节点
name_node.appendChild(name_text)

print(dom.toxml()) #字符格式输出
print(dom.toprettyxml()) #美丽的输出

>>> <?xml version="1.0" ?><root><book price="399"><name>C++ Primer 第1版</name></book></root>
<?xml version="1.0" ?>
<root>
<book price="399">
<name>C++ Primer 第1版</name>
</book>
</root>

删除节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#增加要删除的节点
another_node=dom.createElement('delete')
another_node_child=dom.createElement('content')
another_node_child_text=dom.createTextNode('testing data')
another_node_child.appendChild(another_node_child_text)
another_node.appendChild(another_node_child)
root_node.appendChild(another_node)
print(dom.toprettyxml())

>>><?xml version="1.0" ?>
<root>
<book price="399">
<name>C++ Primer 第1版</name>
</book>
<delete>
<content>testing data</content>
</delete>
</root>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#删除上文增加的节点
children_Nodes=root_node.childNodes
for i in range(children_Nodes.length-1,-1,-1):
if children_Nodes[i].tagName=='delete':
children_Nodes.remove(children_Nodes[i]) #删除root中的文本节点

print(dom.toprettyxml())

>>> <?xml version="1.0" ?>
<root>
<book price="399">
<name>C++ Primer 第1版</name>
</book>
</root>

删除节点属性

1
2
3
4
5
6
7
8
9
10
#删除节点属性
book_node.setAttribute('price','199')
print(book_node.hasAttribute('price'))
print(book_node.getAttribute('price'))
book_node.removeAttribute('price')
print(book_node.hasAttribute('price'))

>>>True
199
False

修改节点内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#修改节点内容
children_Nodes=root_node.childNodes
name_Append = ' (中文版)'
for item in children_Nodes:
#print (item.toxml())
tag_name='name'
c_name = item.getElementsByTagName(tag_name)
print(c_name[0].firstChild.data)
c_name[0].firstChild.data+=name_Append
print(dom.toprettyxml())

>>>C++ Primer 第1
<?xml version="1.0" ?>
<root>
<book>
<name>C++ Primer 第1版 (中文版)</name>
</book>
</root>

修改节点属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#修改节点属性
book_node.setAttribute('price','199')
print(book_node.hasAttribute('price'))
print(book_node.getAttribute('price'))

book_node.setAttribute('price','399')
print(book_node.getAttribute('price'))

book_node.setAttribute('price2','599')
print(book_node.attributes.items())

>>> True
199
399
[('price', '399'), ('price2', '599')]

修改节点名称(可用、不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#修改节点名称(可用、不安全)
name_node=dom.createElement('name')
name_node.nodeName='node name'
name_node.tagName='tag name'
root_node.appendChild(name_node)
print(dom.toxml())
print(name_node.nodeName)
print(name_node.localName)
#删掉刚刚增加的测试节点
children_Nodes=root_node.childNodes
children_Nodes.remove(children_Nodes[1])

>>><?xml version="1.0" ?><root><book price="399" price2="599"><name>C++ Primer 第1版 (中文版)</name></book><tag name/></root>
node name
tag name

可以看到,改变tagName在事实上实现了改变节点名称的效果,但nodeName并没有一并更新,使用时需要谨慎。

DOM修改XML方法总结

  • dom=minidom.Document() #创建DOM树对象
  • root_node=dom.createElement(‘root’) #用DOM对象创建元素节点
  • name_text=dom.createTextNode(‘计算机程序设计语言 第1版’)#用DOM对象创建文本节点
  • dom.appendChild(root_node) # 用DOM对象添加节点
  • book_node.setAttribute(‘price’,‘199’) #添加节点属性(可多个),也可以用来覆盖修改原有的属性
  • children_Nodes.remove(children_Nodes[i]) #删除节点
  • book_node.removeAttribute(‘price’) #删除节点属性
  • price[0].firstChild.data=str(price_float) #直接修改文本节点完成对xml保存内容的修改
  • name_node.tagName=‘tag name’ #修改节点名称

Python DOM 写入XML

写入XML

语法:writexml(writer, indent=“”, addindent=“”, newl=“”, encoding=None)

  • writer:文件对象
  • indent:根节点缩进格式(一般根节点不缩进,所以一般为空)
  • addindent:子节点缩进格式(一般为空格 或制表符/t
  • newl:换行格式(一般为换行回车/n)
  • encoding:编码方式
1
2
3
#Python DOM 写入XML
with open('write_test.xml','w',encoding='UTF-8') as writer:
dom.writexml(writer,indent='',addindent=' ',newl='/n',encoding='UTF-8')

得到xml文件write_test.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<root>
<book price="399" price2="599">
<name>C++ Primer 第1版 (中文版)</name>
</book>
</root>

获取源码

文中测试环境与所有源码可在Github下载。


Python - DOM操作XML技巧汇总
https://www.zywvvd.com/notes/coding/python/processing-xml/python-xml/
作者
Yiwei Zhang
发布于
2020年2月24日
许可协议