0%

python:解析xml(3):ElementTree

XML 树和元素

XML 是一种继承性的分层数据格式,最自然的表示方法是使用树。

为此, ET 有两个类

  1. ElementTree 将整个XML文档表示为一个树
  2. Element 表示该树中的单个节点

与整个文档的交互(读写文件)通常在 ElementTree 级别完成。 与单个 XML 元素及其子元素的交互是在 Element 级别完成的。

解析XML

  1. 从文件中读取来导入数据

    1
    2
    3
    import xml.etree.ElementTree as ET
    tree = ET.parse('country_data.xml')
    root = tree.getroot()

    parse()方法用来解析xml文档

  2. 直接从字符串中解析

    fromstring() 将 XML 从字符串直接解析为 Element ,该元素是已解析树的根元素

    1
    root = ET.fromstring(country_data_as_string)

    作为 Elementroot 具有标签和属性字典:

    1
    2
    3
    4
    >>> root.tag
    'data'
    >>> root.attrib
    {}

    还有可以迭代的子节点:

    1
    2
    3
    4
    5
    6
    for child in root:
    print(child.tag, child.attrib)

    country {'name': 'Liechtenstein'}
    country {'name': 'Singapore'}
    country {'name': 'Panama'}

    子级是可以嵌套的,我们可以通过索引访问特定的子级节点:

    1
    2
    >>> root[0][1].text
    '2008'

用于非阻塞解析的拉取 API

大多数解析函数都要求在返回任何结果之前一次性读取整个文档。

XMLParser :以增量方式添加数据,但这是在回调目标上调用方法的推送式 API。 有时用户真正想要的是能够以增量方式解析 XML 而无需阻塞操作,同时享受完整的已构造 Element 对象。

针对此需求的最强大工具是 XMLPullParser。 它不要求通过阻塞式读取来获得 XML 数据,而是通过执行 XMLPullParser.feed() 调用来增量式地添加数据。 要获得已解析的 XML 元素,应调用 XMLPullParser.read_events()

1
2
3
4
5
6
7
8
parser = ET.XMLPullParser(['start', 'end'])
parser.feed('<mytag>sometext')
list(parser.read_events())

parser.feed(' more text</mytag>')
for event, elem in parser.read_events():
print(event)
print(elem.tag, 'text=', elem.text)

应用:针对以非阻塞方式进行的应用程序,其中 XML 是从套接字接收或从某些存储设备增量式读取的。 在这些用例中,阻塞式读取是不可接受的。

缺点:因为其非常灵活,XMLPullParser 在更简单的用例中使用起来可能并不方便。

其他方法:

  • iterparse():不介意你的应用程序在读取 XML 数据时造成阻塞但仍希望具有增量解析能力。 它在你读取大型 XML 文档并且不希望将它完全放去内存时会很适用。
  • XMLPullParser.flush() :有助于减少延迟,适用于需要通过事件获得即时反馈的场合

查找元素

  • Element.iter():帮助递归遍历其下的所有子树(包括子级,子级的子级,等等)

    1
    2
    3
    4
    5
    6
    7
    8
    for neighbor in root.iter('neighbor'):
    print(neighbor.attrib)

    {'name': 'Austria', 'direction': 'E'}
    {'name': 'Switzerland', 'direction': 'W'}
    {'name': 'Malaysia', 'direction': 'N'}
    {'name': 'Costa Rica', 'direction': 'W'}
    {'name': 'Colombia', 'direction': 'E'}
  • Element.findall() :仅查找当前元素的直接子元素中带有指定标签的元素

  • Element.find() :找带有特定标签的 第一个 子级

然后可以用 Element.text 访问元素的文本内容。 Element.get 访问元素的属性:

1
2
3
4
5
6
7
8
for country in root.findall('country'):
rank = country.find('rank').text
name = country.get('name')
print(name, rank)

Liechtenstein 1
Singapore 4
Panama 68

ps:通过使用 XPath ,可以更精确地指定要查找的元素

修改XML文件

ElementTree 提供了一种构建XML文档并将其写入文件的简单方法。调用 ElementTree.write() 方法就可以实现。

创建后可以直接操作 Element 对象。例如:

假设我们要为每个国家/地区的中添加一个排名,并在排名元素中添加一个 updated 属性:

1
2
3
4
5
6
for rank in root.iter('rank'):
new_rank = int(rank.text) + 1
rank.text = str(new_rank)
rank.set('updated', 'yes')

tree.write('output.xml')
  • Element.remove() 删除元素

  • 假设我们要删除排名高于50的所有国家/地区:

    1
    2
    3
    4
    5
    6
    7
    for country in root.findall('country'):
    # using root.findall() to avoid removal during traversal
    rank = int(country.find('rank').text)
    if rank > 50:
    root.remove(country)

    tree.write('output.xml')

请注意在迭代时进行并发修改可能会导致问题,就像在迭代并修改 Python 列表或字典时那样。 因此,可以先通过 root.findall() 收集所有匹配的元素,在此之后再对匹配项列表进行迭代。

构建XML文档

SubElement() 函数还提供了一种便捷方法来为给定元素创建新的子元素

1
2
3
4
5
6
>>> a = ET.Element('a')
>>> b = ET.SubElement(a, 'b')
>>> c = ET.SubElement(a, 'c')
>>> d = ET.SubElement(c, 'd')
>>> ET.dump(a)
<a><b /><c><d /></c></a>