Python解析XML

解析XML的Python包

Python的标准库中,提供了6种可以用于处理XML的包。

xml.dom

xml.dom实现的是W3C制定的DOM API。如果你习惯于使用DOM API或者有人要求这这样做,可以使用这个包。不过要注意,在这个包中,还提供了几个不同的模块,各自的性能有所区别。

DOM解析器在任何处理开始之前,必须把基于XML文件生成的树状数据放在内存,所以DOM解析器的内存使用量完全根据输入资料的大小。

xml.dom.minidom

xml.dom.minidom是DOM API的极简化实现,比完整版的DOM要简单的多,而且这个包也小的多。那些不熟悉DOM的朋友,应该考虑使用xml.etree.ElementTree模块。据lxml的作者评价,这个模块使用起来并不方便,效率也不高,而且还容易出现问题。

xml.dom.pulldom

与其他模块不同,xml.dom.pulldom模块提供的是一个“pull解析器”,其背后的基本概念指的是从XML流中pull事件,然后进行处理。虽然与SAX一样采用事件驱动模型(event-driven processing model),但是不同的是,使用pull解析器时,使用者需要明确地从XML流中pull事件,并对这些事件遍历处理,直到处理完成或者出现错误。

pull解析(pull parsing)是近来兴起的一种XML处理趋势。此前诸如SAX和DOM这些流行的XML解析框架,都是push-based,也就是说对解析工作的控制权,掌握在解析器的手中。

xml.sax

xml.sax模块实现的是SAX API,这个模块牺牲了便捷性来换取速度和内存占用。SAX是Simple API for XML的缩写,它并不是由W3C官方所提出的标准。它是事件驱动的,并不需要一次性读入整个文档,而文档的读入过程也就是SAX的解析过程。所谓事件驱动,是指一种基于回调(callback)机制的程序运行方法。

xml.etree.ElementTree(以下简称ET)

xml.etree.ElementTree模块提供了一个轻量级、Pythonic的API,同时还有一个高效的C语言实现,即xml.etree.cElementTree。与DOM相比,ET的速度更快,API使用更直接、方便。与SAX相比,ET.iterparse函数同样提供了按需解析的功能,不会一次性在内存中读入整个文档。ET的性能与SAX模块大致相仿,但是它的API更加高层次,用户使用起来更加便捷。

详解 xml.etree.ElementTree

简介

Element类型是一种灵活的容器对象,用于在内存中存储结构化数据。

注意:xml.etree.ElementTree模块在应对恶意结构数据时显得并不安全。

每个element对象都具有以下属性:

  • tag:string对象,表示数据代表的种类。
  • attrib:dictionary对象,表示附有的属性。
  • text:string对象,表示element的内容。
  • tail:string对象,表示element闭合之后的尾迹。
  • 若干子元素(child elements)。
1
2
<tag attrib1=1>text</tag>tail
1 2 3 4

导入

Python标准库中,提供了ET的两种实现。一个是纯Python实现的xml.etree.ElementTree,另一个是速度更快的C语言实现xml.etree.cElementTree

Python 3.3版本之前优先导入C语言编译的API:

1
2
3
4
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET

Python 3.3之后,ElemenTree模块会自动优先使用C加速器,如果不存在C实现,则会使用Python实现。

Element对象

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
class xml.etree.ElementTree.Element(tag, attrib={}, **extra)

  tag:string,元素代表的数据种类。
  text:string,元素的内容。
  tail:string,元素的尾形。
  attrib:dictionary,元素的属性字典。
  
  #针对属性的操作
  clear():清空元素的后代、属性、text和tail也设置为None
  get(key, default=None):获取key对应的属性值,如该属性不存在则返回default值。
  items():根据属性字典返回一个列表,列表元素为(key, value)。
  keys():返回包含所有元素属性键的列表。
  set(key, value):设置新的属性键与值。

  #针对后代的操作
  append(subelement):添加直系子元素。
  extend(subelements):增加一串元素对象作为子元素。#python2.7新特性
  find(match):寻找第一个匹配子元素,匹配对象可以为tag或path。
  findall(match):寻找所有匹配子元素,匹配对象可以为tag或path。
  findtext(match):寻找第一个匹配子元素,返回其text值。匹配对象可以为tag或path。
  insert(index, element):在指定位置插入子元素。
  iter(tag=None):生成遍历当前元素所有后代或者给定tag的后代的迭代器。#python2.7新特性
  iterfind(match):根据tag或path查找所有的后代。
  itertext():遍历所有后代并返回text值。
  remove(subelement):删除子元素。

ElementTree对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class xml.etree.ElementTree.ElementTree(element=None, file=None)
  element如果给定,则为新的ElementTree的根节点。

  _setroot(element):用给定的element替换当前的根节点。慎用。
  
  # 以下方法与Element类中同名方法近似,区别在于它们指定以根节点作为操作对象。
  find(match)
  findall(match)
  findtext(match, default=None)
  getroot():获取根节点.
  iter(tag=None)
  iterfind(match)
  parse(source, parser=None):装载xml对象,source可以为文件名或文件类型对象.
  write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml")

快速入门

将XML文档解析为树(tree)

XML是一种结构化、层级化的数据格式,最适合体现XML的数据结构就是树。ET提供了两个对象:ElementTree将整个XML文档转化为树,Element则代表着树上的单个节点。对整个XML文档的交互(读取,写入,查找需要的元素),一般是在ElementTree层面进行的。对单个XML元素及其子元素,则是在Element层面进行的。

使用下面的XML文档,作为演示数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<ProductMetaData>
<SatelliteID>HY-1C</SatelliteID>
<SensorID>CZI</SensorID>
<TimeType>MONTHLY</TimeType>
<ProductUnit>NSOAS</ProductUnit>
<ProductInfo>
<ProductLevel>L1B</ProductLevel>
<ProductFormat>HDF5.0</ProductFormat>
<ProductDescription>HY-1C LEVELL1B Product</ProductDescription>
<ProcessingType>OPER</ProcessingType>
<ProduceTime>2022-04-25T03:18:00.225817</ProduceTime>
<SpatialResolution></SpatialResolution>
</ProductInfo>
</ProductMetaData>

加载xml文档,获取ElementTree对象

1
2
3
4
5
6
>>> import xml.etree.ElementTree as ET

>>> tree = ET.ElementTree(file='D:\PythonFiles\SatelliteDataParsing\H1C_OPER_CZI_L1B_20220425T025003_20220425T025059_19011_10.meta.xml')


<xml.etree.ElementTree.ElementTree object at 0x000001DA59EB5940>

获取根元素(root element)

1
2
3
>>> root = tree获取根元素(root element).getroot()
>>> root.tag, root.attrib, root.text, root.tail
('ProductMetaData', {}, '\n ', None)

遍历直接子元素

1
2
3
4
5
6
7
8
>>> for child in root:
... print(child.tag, child.attrib, child.text)
...
SatelliteID {} HY-1C
SensorID {} CZI
TimeType {} MONTHLY
ProductUnit {} NSOAS
ProductInfo {}

通过索引值来访问特定的子元素

1
2
3
4
>>> root[0].tag, root[0].text
('SatelliteID', 'HY-1C')
>>> root[4][0].tag, root[4][0].text
('ProductLevel', 'L1B')

访问元素

Element对象有一个iter方法,可以对某个元素对象之下所有的子元素进行深度优先遍历(DFS)。ElementTree对象同样也有这个方法。下面是查找XML文档中所有元素的最简单方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> for e in tree.iter():
... print(e.tag, e.attrib, e.text)
...
ProductMetaData {}

SatelliteID {} HY-1C
SensorID {} CZI
TimeType {} MONTHLY
ProductUnit {} NSOAS
ProductInfo {}

ProductLevel {} L1B
ProductFormat {} HDF5.0
ProductDescription {} HY-1C LEVELL1B Product
ProcessingType {} OPER
ProduceTime {} 2022-04-25T03:18:00.225817
SpatialResolution {} None

遍历所有具备所提供tag的元素

1
2
3
4
>>> for e in tree.iter(tag='ProductName'):
... print(e.tag, e.attrib, e.text)
...
ProductLevel {} L1B

通过XPath查找元素

Element对象中有一些find方法可以接受Xpath路径作为参数,find方法会返回第一个匹配的子元素,findall以列表的形式返回所有匹配的子元素, iterfind则返回一个所有匹配元素的迭代器(iterator)。ElementTree对象也具备这些方法,相应地它的查找是从根节点开始的。

ProductInfo元素之下所有tag为ProductFormat的元素

1
2
3
4
>>> for e in tree.iterfind('ProductInfo/ProductFormat'):
... print(e.tag, e.attrib, e.text)
...
ProductFormat {} HDF5.0

查找所有具备某个name属性的branch元素

1
2
# for elem in tree.iterfind('branch[@name="release01"]'):
# print elem.tag, elem.attrib

构建XML文档

利用ET,很容易就可以完成XML文档构建,并写入保存为文件。ElementTree对象的write方法就可以实现这个需求。

一般来说,有两种主要使用场景。一是你先读取一个XML文档,进行修改,然后再将修改写入文档,二是从头创建一个新XML文档。

修改文档的话,可以通过调整Element对象来实现。请看下面的例子:

1
2
3
4
5
6
7
8
>>> root = tree.getroot()
>>> del root[2]
>>> root[0].set('foo', 'bar')
>>> for subelem in root:
... print subelem.tag, subelem.attrib
...
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}

在上面的代码中,我们删除了root元素的第三个子元素,为第一个子元素增加了新属性。这个树可以重新写入至文件中。最终的XML文档应该是下面这样的:

请注意,文档中元素的属性顺序与原文档不同。这是因为ET是以字典的形式保存属性的,而字典是一个无序的数据结构。当然,XML也不关注属性的顺序。

从头构建一个完整的文档也很容易。ET模块提供了一个SubElement工厂函数,让创建元素的过程变得很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
>>> for i in tree.iter():
... print(i.tag, i.attrib, i.text)
...
root {} None
elem {} None
child1 {} some text
child2 {} None
elem_b {} None

利用iterparse解析XML流

ML文档通常都会比较大,如何直接将文档读入内存的话,那么进行解析时就会出现问题。这也就是为什么不建议使用DOM,而是SAX API的理由之一。

我们上面谈到,ET可以将XML文档加载为保存在内存里的树(in-memory tree),然后再进行处理。但是在解析大文件时,这应该也会出现和DOM一样的内存消耗大的问题吧?没错,的确有这个问题。为了解决这个问题,ET提供了一个类似SAX的特殊工具——iterparse,可以循序地解析XML。

接下来,笔者为大家展示如何使用iterparse,并与标准的树解析方式进行对比。我们使用一个自动生成的XML文档,下面是该文档的开头部分:

……


模块方法

Comment()

1
xml.etree.ElementTree.Comment(text=None)

创建一个特别的element,通过标准序列化使其代表了一个comment。comment可以为bytestring或unicode。

dump()

1
xml.etree.ElementTree.dump(elem)

生成一个element tree,通过sys.stdout输出,elem可以是元素树或单个元素。这个方法最好只用于debug。

fromstring()

1
xml.etree.ElementTree.fromstring(text) 

text是一个包含XML数据的字符串,与XML()方法类似,返回一个Element实例。

fromstringlist()

1
xml.etree.ElementTree.fromstringlist(sequence, parser=None) 

从字符串的序列对象中解析xml文档。缺省parser为XMLParser,返回Element实例。

New in version 2.7.

iselement()

1
xml.etree.ElementTree.iselement(element)

检查是否是一个element对象。

1
2
iterparse() 
xml.etree.ElementTree.iterparse(source, events=None, parser=None)

将文件或包含xml数据的文件对象递增解析为element tree,并且报告进度。events是一个汇报列表,如果忽略,将只有end事件会汇报出来。

注意,iterparse()只会在看见开始标签的”>”符号时才会抛出start事件,因此届时属性是已经定义了,但是text和tail属性在那时还没有定义,同样子元素也没有定义,因此他们可能不能被显示出来。如果你想要完整的元素,请查找end事件。

parse()

1
xml.etree.ElementTree.parse(source, parser=None)

将一个文件或者字符串解析为element tree。

SubElement()

1
xml.etree.ElementTree.SubElement(parent, tag, attrib={}, **extra)

子元素工厂,创建一个Element实例并追加到已知的节点。

tostring()

1
xml.etree.ElementTree.tostring(element, encoding="us-ascii", method="xml")

生成一个字符串来表示表示xml的element,包括所有子元素。element是Element实例,method为”xml”,”html”,”text”。返回包含了xml数据的字符串。

tostringlist()

1
xml.etree.ElementTree.tostringlist(element, encoding="us-ascii", method="xml")

生成一个字符串来表示表示xml的element,包括所有子元素。element是Element实例,method为”xml”,”html”,”text”。返回包含了xml数据的字符串列表。


参考:

https://juejin.cn/post/6844903426740994056#heading-8

https://zhuanlan.zhihu.com/p/152207687


Python解析XML
https://blog-21n.pages.dev/2023/04/04/Python解析XML/
作者
Neo
发布于
2023年4月4日
许可协议