Python处理XML

通过

import os
import sys
import fcntl
import xml.etree.ElementTree as ET

global example_xml
example_xml = '/home/example/example.xml

# 读取xml

lockf = None
if os.path.exists(example_xml):
    lockf = open(example_xml)
    fcntl.flock(lockf, fcntl.LOCK_EX)
    tree = ET.parse(example_xml)
    root = tree.getroot()
    logging.info("%s open and root" % example_xml)
else:
    logging.error("%s not found!" % example_xml)
    sys.exit(-1)

# 解析 name 和 memory
try:
    example_name = root.find("name").text
    logging.info("example_name is %s" % example_name)
    example['name'] = example_name
    ...
except:
    logging.exception("Can't get example_name!")
    sys.exit(-2)

# 修改添加子对象(3级) <app><color><red id='0'></red></color></app>
target = tree.find('app/color/red')
if target is None:
    print "No app/color/red tag in example.xml"
    sys.exit(-1)
target.attrib['id'] = '0'

# 完整添加子节点
e_size = "<size><high redid=\"0\" mode=\"square\" sizeset=\"%d\" /></size>" % size_set
new = ET.fromstring(e_size)
target = root.find(new.tag)
if target is not None:
    print "Tag <%s> exists in example.xml" % new.tag
    sys.exit(-3)
root.append(new)

以下是用 ElementTree 在 Python 中解析 XML摘要,便于后续自己查询:使用ET处理XML确实非常方便直观,推荐使用。不过,我在实践编程时在项目上使用这些方法并没有完全实现我的目标,也许我使用方法还有问题。实际开发过程中,我使用了同事给我的参考代码进行整合,见上文。

xml.etree.ElementTree (以下简称 ET)。它提供了轻量级的 Python 式的 API ,它由一个 C 实现来提供。

ElementTree 生来就是为了处理 XML ,它在 Python 标准库中有两种实现。一种是纯 Python 实现例如 xml.etree.ElementTree ,另外一种是速度快一点的 xml.etree.cElementTree 。

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

从 Python 3.3 开始,ElementTree 模块会自动寻找可用的 C 库来加快速度

将 XML 解析为树的形式

XML 是一种分级的数据形式,所以最自然的表示方法是将它表示为一棵树。ET 有两个对象来实现这个目的 - ElementTree 将整个 XML 解析为一棵树, Element 将单个结点解析为树。

XML 文件例子

<?xml version="1.0"?>
<doc>
    <branch name="testing" hash="1cdf045c">
        text,source
    </branch>
    <branch name="release01" hash="f200013e">
        <sub-branch name="subrelease01">
            xml,sgml
        </sub-branch>
    </branch>
    <branch name="invalid">
    </branch>
</doc>

加载并且解析这个 XML :

import xml.etree.cElementTree as ET
tree = ET.ElementTree(file='doc1.xml')

抓根结点元素:

tree.getroot()

root 是一个 Element 元素

root = tree.getroot()
root.tag, root.attrib

根元素没有任何状态,可以找到自己的子结点:

for child_of_root in root:
    print child_of_root.tag, child_of_root.attrib

找到元素

Element 对象有一个 iter 方法可以对子结点进行深度优先遍历。 ElementTree 对象也有 iter 方法来提供便利。

for elem in tree.iter():
    print elem.tag, elem.attrib

iter 方法接受一个标签名字,然后只遍历那些有指定标签的元素:

for elem in tree.iter(tag='branch'):
    print elem.tag, elem.attrib

Element 有一些关于寻找的方法可以接受 XPath 作为参数。 find 返回第一个匹配的子元素, findall 以列表的形式返回所有匹配的子元素, iterfind 为所有匹配项提供迭代器。这些方法在 ElementTree 里面也有。

for elem in tree.iterfind('branch/sub-branch'):
    print elem.tag, elem.attrib

在 branch 下面找到所有标签为 sub-branch 的元素。然后给出如何找到所有的 branch 元素,用一个指定 name 的状态即可:

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

建立 XML 文档

ET 提供了建立 XML 文档和写入文件的便捷方式。 ElementTree 对象提供了 write 方法。

root = tree.getroot()
del root[2]
root[0].set('foo', 'bar')
for subelem in root:
    print subelem.tag, subelem.attrib
  • 写回文件

import sys
tree.write(sys.stdout)
  • 建立一个全新的元素,ET 模块提供了 SubElement 函数来简化过程:

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)

使用 iterparse 来处理 XML 流

XML 文档通常比较大, ET 提供一个特殊的工具,用来处理大型文档,并且解决了内存问题,这个工具叫 iterparse 。

案例XML文件如下:

<?xml version="1.0" standalone="yes"?>
<site>
    <regions>
        <africa>
            <item id="item0">
                <location>United States</location>    <!-- Counting locations -->
                <quantity>1</quantity>
                <name>duteous nine eighteen </name>
                <payment>Creditcard</payment>
                <description>
                    <parlist>
[...]

用一个简单的脚本来计数有多少 location 元素并且文本内容为“Zimbabwe”。这是用 ET.parse 的一个标准的写法:

tree = ET.parse(sys.argv[2])

count = 0
for elem in tree.iter(tag='location'):
    if elem.text == 'Zimbabwe':
        count += 1
print count

所有 XML 树中的元素都会被检验。当处理一个大约 100MB 的 XML 文件时,占用的内存大约是 560MB ,耗时 2.9 秒。

但是,并不需要在内存中加载整颗树。它检测我们需要的带特定值的 location 元素。其他元素被丢弃。这是 iterparse 的来源:

count = 0
for event, elem in ET.iterparse(sys.argv[2]):
    if event == 'end':
        if elem.tag == 'location' and elem.text == 'Zimbabwe':
            count += 1
    elem.clear() # discard the element

print count

这个循环遍历 iterparse 事件,检测“闭合的”(end)事件并且寻找 location 标签和指定的值。在这里 elem.clear() 是关键 - iterparse 仍然建立一棵树,只不过不需要全部加载进内存,这样做可以有效的利用内存空间。

处理同样的文件,这个脚本占用内存只需要仅仅的 7MB ,耗时 2.5 秒。速度的提升归功于生成树的时候只遍历一次。相比较来说, parse 方法首先建立了整个树,然后再次遍历来寻找我们需要的元素(所以慢了一点)。

参考

Last updated