在Python中处理XML的技巧和窍门

412 阅读7分钟

在本教程中,我们将看到一些使用Python来解析XML或可扩展标记语言的例子。XML有点像HTML的一个更灵活的版本。它是一种标记语言,定义了一套规则,用于将文件编码为人类可读和机器可读的格式。计算机对XML的解析有几种不同的方式。第一种是被称为XML的简单API,也被称为SAX。另一种解析XML的方式是通过使用DOM或文档对象模型。先回到SAX。SAX一次一个字符地读取XML数据,一直到文档的结尾。当 XML 被读取时,解析器会发出与 XML 内容有关的事件。使用 Python,我们可以在这些事件发生时处理它们。


SAX 事件

当分析器遇到我们在下面看到的 XML 时,它会产生一个开始的事件,然后当分析器到达开头标签的这个闭合角括号时,它将发送一个带有标签名称的开始标签事件,以及属性的集合,和它们的值。当解析器到达闭合标签的开口角括号时,它将发送一个结束标签事件,当它到达闭合标签的闭合括号时,也将发送一个事件。

sax XML API parse

随着这些事件的产生,我们可以使用Python来响应并操作数据。当使用SAX时,XML的内容不能以随机顺序访问。记住,SAX的工作方式是逐个字符地在XML文件中移动,直到到达文件的末端。在这个过程中,你不能 "倒退 "或后退。此外,SAX在处理过程中不能改变XML数据。由于这个原因,在使用XML作为配置文件时,SAX很好。


SAX API

为了在Python中使用SAX API,我们使用xml.sax模块。所以我们将导入该模块来运行一些测试代码。一旦导入,我们就可以访问**xml.sax.parse()函数,它可以与文件或流对象一起工作。我们可以使用的另一个函数是xml.sax.parseString()**函数,如果你已经把XML放在一个字符串变量中,就可以使用这个函数。除了这些函数之外,还有一个名为ContentHandler的基类,可以用于自定义内容处理。ContentHandler类有处理文档的开始和结束、标签的开始和结束以及处理文本数据的函数。你可以创建你自己的类,重写这些函数来处理每种类型的内容。

Python SAX XML 示例

下面我们有一些XML数据的例子。它被存储在一个名为xmldata.xml的文件中。

<?xml version="1.0" encoding="UTF-8"?>
<blogposts title="Blog Posts Collection" date="A date" author="Some dude">

   <post type="summary">
      <title>Parse XML With SAX</title>
   </post>

   <post type="detail">
      <title>Overview</title>
      <entry>
         Parsing XML is great
      </entry>
      <entry />
      <entry>
         Have fun with XML parsing
      </entry>
   </post>
</blogposts>

我们正在处理的XML数据代表一个虚构的blogposts元素。有一个blogposts根标签,它有一些属性,在blogposts里面有一些帖子,每个帖子有一些条目。代码从这个XML中提取信息,因为它正在被SAX分析器解析。有一些函数会表明我们开始处理文档和完成处理。为了打印出博文的名称,使用了startElement函数。还有endElement、字符、startDocument和endDocument的方法。为了运行这个程序,我们把它放在Python的main()函数中。一个新的CustomContentHandler实例被分配给处理程序变量。然后我们简单地使用xml.sax.parse()来读取数据并打印出一些结果。

import xml.sax


# define a Custom ContentHandler class that extends ContenHandler
class CustomContentHandler(xml.sax.ContentHandler):
    def __init__(self):
        self.postCount = 0
        self.entryCount = 0
        self.isInTitle = False

    # Handle startElement
    def startElement(self, tagName, attrs):
        if tagName == 'blogposts':
            print('Blogposts title: ' + attrs['title'])
        elif tagName == 'post':
            self.postCount += 1
        elif tagName == 'entry':
            self.entryCount += 1
        elif tagName == 'title':
            self.isInTitle = True

    # Handle endElement
    def endElement(self, tagName):
        if tagName == 'title':
            self.isInTitle = False

    # Handle text data
    def characters(self, chars):
        if self.isInTitle:
            print('Title: ' + chars)

    # Handle startDocument
    def startDocument(self):
        print('About to start!')

    # Handle endDocument
    def endDocument(self):
        print('Finishing up!')


def main():
    # create a new content handler for the SAX parser
    handler = CustomContentHandler()

    # call the parse method on an XML file
    xml.sax.parse('xmldata.xml', handler)

    # when we're done, print out some interesting results
    print(f'There were {handler.postCount} post elements')
    print(f'There were {handler.entryCount} entry elements')


if __name__ == '__main__':
    main()
About to start!
Blogposts title: Blog Posts Collection
Title: Parse XML With SAX
Title: Overview
Finishing up!
There were 2 post elements
There were 3 entry elements

Process finished with exit code 0

XML DOM API

另一种可以操作XML内容的方式是使用文档对象模型API或DOM。DOM API和SAX API的一个很大的区别是,DOM允许你随机访问XML文件的任何部分。这在SAX中是不可能的,因为它从头到尾一次读取一个字符。通过DOM,你还可以修改XML文件的内容。当使用DOM解析XML代码时,XML被完整地读入内存,并被表示为一个树状结构。然后你可以使用各种API来处理产生的文档树。Python标准库在xml.dom.minidom模块中提供了一个DOM API的实现。它旨在成为一个比完整的DOM API更小的实现。下面是一些需要注意的关键点和方法。

  • 随机访问XML结构的任何部分
  • 修改XML内容
  • 将XML表示为一个层次化的树状结构
  • xml.dom.minidom是一个轻量级的实现
  • domtree = xml.com.minidom.parseString(str)
  • elem.getElementById(id)
  • elem.getElementsByTagName(tagname)
  • elem.getAttribute(attrName)
  • elem.setAttribute(attrName, val)
  • newElem = document.createElement(tagName)
  • newElem = document.createTextNode(strOfText)
  • elem.appendChild(newElem)

下面是一个使用xml.dom.minidom来操作我们在SAX例子中使用的同一个xmldata.xml文件的例子。注意这个方法提供了更多的灵活性,我们甚至可以在内存中向文件添加数据。我们中的许多人对DOM相当熟悉,因为它在Web开发中非常普遍,所以在Python中使用DOM来处理XML是相当容易理解的。

import xml.dom.minidom


def main():
    domtree = xml.dom.minidom.parse('xmldata.xml')
    rootnode = domtree.documentElement

    # display some information about the content
    print(f'The root element is {rootnode.nodeName}')
    print(f'The Title is: {rootnode.getAttribute("title")}')
    entries = domtree.getElementsByTagName('entry')
    print(f'There are {entries.length} entry tags')

    # create a new entry tag in memory
    newItem = domtree.createElement('entry')

    # add some text to the entry
    newItem.appendChild(domtree.createTextNode('Magic Entry!'))

    # now add the entry to the first post
    firstPost = domtree.getElementsByTagName('post')[0]
    firstPost.appendChild(newItem)

    # Now count the entry tags again
    entries = domtree.getElementsByTagName('entry')
    print('Now there are {0} entry tags'.format(entries.length))

    # Print out the domtree as xml
    print(domtree.toxml())


if __name__ == '__main__':
    main()
The root element is blogposts
The Title is: Blog Posts Collection
There are 3 entry tags
Now there are 4 entry tags

<?xml version="1.0" ?><blogposts title="Blog Posts Collection" date="A date" author="Some dude">

   <post type="summary">
      <title>Parse XML With SAX</title>
   <entry>Magic Entry!</entry></post>

   <post type="detail">
      <title>Overview</title>
      <entry>
         Parsing XML is great
      </entry>
      <entry/>
      <entry>
         Have fun with XML parsing
      </entry>
   </post>
</blogposts>

Process finished with exit code 0

XML ElementTree API

DOM API非常庞大,为处理XML数据提供了跨语言和跨平台的API。ElementTree API采取了一种不同的方法,它专注于成为一种更简单的处理XML的方式 通过ElementTree API,元素被当作是列表一样处理。这意味着,如果你有一个包含其他元素的XML元素,可以使用标准的迭代方式(如for循环)来迭代这些子元素。ElementTree API像对待字典一样对待属性。因此,如果你有一个元素的引用,那么你可以访问它的attrib属性,这是一个包含所有属性名称和值的字典。ElementTree使搜索XML中的内容变得简单明了。它提供的函数可以使用XPath语法来搜索XML中的特定数据。

在下面的例子中,我们使用ElementTree API来测试这些概念。再一次,我们使用了整个教程中一直在使用的同一个XML数据文件。我们可以看到如何建立一个文档结构并找到树的根元素。我们可以访问一个属性,遍历标签,计算元素的数量,添加新的数据,等等。

from lxml import etree


def main():
    postCount = 0
    entryCount = 0

    # build a doc structure using the ElementTree API
    doc = etree.parse('xmldata.xml').getroot()
    print(doc.tag)

    # Access the value of an attribute
    print(doc.attrib['title'])

    # Iterate over tags
    for elem in doc.findall('post'):
        print(elem.tag)

    # Count the number of posts
    postCount = len(doc.findall('post'))
    entryCount = len(doc.findall('.//entry'))

    print(f'There are {postCount} post elements')
    print(f'There are {entryCount} entry elements')

    # Create a new post
    newPost = etree.SubElement(doc, 'post')
    newPost.text = 'This is a new post'

    # Count the number of posts
    postCount = len(doc.findall('post'))
    entryCount = len(doc.findall('.//entry'))

    print(f'There are now {postCount} post elements')
    print(f'There are now {entryCount} entry elements')


if __name__ == '__main__':
    main()
blogposts
Blog Posts Collection
post
post
There are 2 post elements
There are 3 entry elements
There are now 3 post elements
There are now 3 entry elements

Process finished with exit code 0

Python XML解析总结

使用本教程中提到的任何一个库都可以解决在Python中读、写和操作XML数据的问题。我们看了一下XML的SAX API,XML的DOM API,最后是XML的ElementTree API。它们各有利弊,上面的一些链接将提供更多在Python中处理XML的技巧和窍门。