第23章-XML模块¶
python具有内置的XML解析功能,您可以通过它 xml 模块。在本文中,我们将重点介绍XML模块的两个子模块:
微型计算机
ElementTree
我们将从minidom开始,因为它曾经是XML解析的实际方法。然后我们来看看如何使用elementtree。
使用minidom¶
首先,我们需要一些实际的XML来解析。请看一下以下XML的简短示例:
<?xml version="1.0" ?>
<zAppointments reminder="15">
<appointment>
<begin>1181251680</begin>
<uid>040000008200E000</uid>
<alarmTime>1181572063</alarmTime>
<state></state>
<location></location>
<duration>1800</duration>
<subject>Bring pizza home</subject>
</appointment>
</zAppointments>
这是非常典型的XML,实际上非常直观。您可能不得不使用一些非常讨厌的XML。无论如何,请使用以下名称保存上面的XML代码: appt.xml
让我们花些时间熟悉如何使用python的 微型计算机 模块。这是一段相当长的代码,请做好准备。
import xml.dom.minidom
import urllib.request
class ApptParser(object):
def __init__(self, url, flag='url'):
self.list = []
self.appt_list = []
self.flag = flag
self.rem_value = 0
xml = self.getXml(url)
self.handleXml(xml)
def getXml(self, url):
try:
print(url)
f = urllib.request.urlopen(url)
except:
f = url
doc = xml.dom.minidom.parse(f)
node = doc.documentElement
if node.nodeType == xml.dom.Node.ELEMENT_NODE:
print('Element name: %s' % node.nodeName)
for (name, value) in node.attributes.items():
print(' Attr -- Name: %s Value: %s' % (name, value))
return node
def handleXml(self, xml):
rem = xml.getElementsByTagName('zAppointments')
appointments = xml.getElementsByTagName("appointment")
self.handleAppts(appointments)
def getElement(self, element):
return self.getText(element.childNodes)
def handleAppts(self, appts):
for appt in appts:
self.handleAppt(appt)
self.list = []
def handleAppt(self, appt):
begin = self.getElement(appt.getElementsByTagName("begin")[0])
duration = self.getElement(appt.getElementsByTagName("duration")[0])
subject = self.getElement(appt.getElementsByTagName("subject")[0])
location = self.getElement(appt.getElementsByTagName("location")[0])
uid = self.getElement(appt.getElementsByTagName("uid")[0])
self.list.append(begin)
self.list.append(duration)
self.list.append(subject)
self.list.append(location)
self.list.append(uid)
if self.flag == 'file':
try:
state = self.getElement(appt.getElementsByTagName("state")[0])
self.list.append(state)
alarm = self.getElement(appt.getElementsByTagName("alarmTime")[0])
self.list.append(alarm)
except Exception as e:
print(e)
self.appt_list.append(self.list)
def getText(self, nodelist):
rc = ""
for node in nodelist:
if node.nodeType == node.TEXT_NODE:
rc = rc + node.data
return rc
if __name__ == "__main__":
appt = ApptParser("appt.xml")
print(appt.appt_list)
这段代码松散地基于Python文档中的一个示例,我不得不承认,我认为它的变化有点难看。让我们把这段代码分解一下。您在中看到的URL参数 ApptParser 类可以是URL或文件。在 获取XML 方法,我们使用异常处理程序尝试打开URL。如果它恰巧引发了一个错误,那么我们假设URL实际上是一个文件路径。接下来我们使用minidom的 解析 方法来分析XML。然后我们从XML中拉出一个节点。我们将忽略条件,因为它对本次讨论不重要。最后,我们返回 node 对象。
从技术上讲,节点是XML,我们将其传递给 手持设备 方法。要获取XML中的所有约会实例,我们将执行以下操作:
xml.getElementsByTagName("appointment").
然后我们把信息传递给 手跳 方法。这是很多传递信息的过程。对这段代码进行一点重构,使其成为一个好主意,这样它就不需要传递信息,而只是设置类变量,然后在不带任何参数的情况下调用下一个方法。我把这个留给读者做练习。不管怎样,所有的 手跳 方法对每个约会进行循环并调用 手跳 方法从中提取一些附加信息,将数据添加到列表中,然后将该列表添加到另一个列表中。最终的想法是列出一份清单,上面列出了我所有的约会相关数据。
您会注意到handleapt方法调用 获取元素 方法调用 获取文本 方法。从技术上讲,您可以跳过对getElement的调用,直接调用getText。另一方面,您可能需要向getelement添加一些额外的处理,以便在返回文本之前将文本转换为其他类型。例如,您可能希望将数字转换为整数、浮点数或十进制对象。
在继续之前,让我们用minidom再试一个例子。我们将使用来自Microsoft的msdn网站的XML示例: http://msdn.microsoft.com/en-us/library/ms762271%28VS.85%29.aspx .将以下XML另存为 example.xml
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description>
</book>
</catalog>
对于本例,我们只分析XML,提取书籍标题并将其打印到stdout。代码如下:
import xml.dom.minidom as minidom
def getTitles(xml):
"""
Print out all titles found in xml
"""
doc = minidom.parse(xml)
node = doc.documentElement
books = doc.getElementsByTagName("book")
titles = []
for book in books:
titleObj = book.getElementsByTagName("title")[0]
titles.append(titleObj)
for title in titles:
nodes = title.childNodes
for node in nodes:
if node.nodeType == node.TEXT_NODE:
print(node.data)
if __name__ == "__main__":
document = 'example.xml'
getTitles(document)
这段代码只是一个接受一个参数XML文件的短函数。我们导入minidom模块并给它取相同的名称,以便于引用。然后我们分析XML。函数的前两行与前一个示例基本相同。我们使用 GetElementsByTagname 方法获取需要的XML部分,然后迭代结果并从中提取书籍标题。这实际上提取了标题对象,因此我们也需要对其进行迭代,并提取纯文本,这就是为什么我们使用嵌套 for 循环。
现在,让我们花点时间尝试XML模块的另一个子模块,名为 ElementTree .
使用elementtree分析¶
在本节中,您将学习如何创建XML文件、编辑XML并使用elementtree解析XML。为了进行比较,我们将使用上一节中使用的相同XML来说明使用minidom和elementtree之间的区别。这是原始XML:
<?xml version="1.0" ?>
<zAppointments reminder="15">
<appointment>
<begin>1181251680</begin>
<uid>040000008200E000</uid>
<alarmTime>1181572063</alarmTime>
<state></state>
<location></location>
<duration>1800</duration>
<subject>Bring pizza home</subject>
</appointment>
</zAppointments>
让我们从学习如何使用Python以编程方式创建这段XML开始!
如何使用elementtree创建XML¶
使用elementtree创建XML非常简单。在本节中,我们将尝试用Python创建上面的XML。代码如下:
import xml.etree.ElementTree as xml
def createXML(filename):
"""
Create an example XML file
"""
root = xml.Element("zAppointments")
appt = xml.Element("appointment")
root.append(appt)
# add appointment children
begin = xml.SubElement(appt, "begin")
begin.text = "1181251680"
uid = xml.SubElement(appt, "uid")
uid.text = "040000008200E000"
alarmTime = xml.SubElement(appt, "alarmTime")
alarmTime.text = "1181572063"
state = xml.SubElement(appt, "state")
location = xml.SubElement(appt, "location")
duration = xml.SubElement(appt, "duration")
duration.text = "1800"
subject = xml.SubElement(appt, "subject")
tree = xml.ElementTree(root)
with open(filename, "w") as fh:
tree.write(fh)
if __name__ == "__main__":
createXML("appt.xml")
如果运行此代码,则应该获得如下内容(可能全部在一行中):
<zAppointments>
<appointment>
<begin>1181251680</begin>
<uid>040000008200E000</uid>
<alarmTime>1181572063</alarmTime>
<state />
<location />
<duration>1800</duration>
<subject />
</appointment>
</zAppointments>
这与原始文件非常接近,并且确实是有效的XML。虽然不完全一样,但它足够近了。让我们花点时间检查代码并确保我们理解它。首先,我们使用elementtree的element函数创建根元素。然后我们创建一个appointment元素并将其附加到根目录。接下来,我们通过将约会元素对象(appt)与名称一起传递给子元素来创建子元素,如“begin”。然后,对于每个子元素,我们将其文本属性设置为给它一个值。在脚本的末尾,我们创建了一个元素树,并使用它将XML写到一个文件中。
现在我们准备好学习如何编辑文件了!
如何使用elementtree编辑XML¶
使用elementtree编辑XML也很容易。不过,为了让事情变得更有趣,我们将在XML中添加另一个约会块:
<?xml version="1.0" ?>
<zAppointments reminder="15">
<appointment>
<begin>1181251680</begin>
<uid>040000008200E000</uid>
<alarmTime>1181572063</alarmTime>
<state></state>
<location></location>
<duration>1800</duration>
<subject>Bring pizza home</subject>
</appointment>
<appointment>
<begin>1181253977</begin>
<uid>sdlkjlkadhdakhdfd</uid>
<alarmTime>1181588888</alarmTime>
<state>TX</state>
<location>Dallas</location>
<duration>1800</duration>
<subject>Bring pizza home</subject>
</appointment>
</zAppointments>
现在,让我们编写一些代码,将begin标记的每个值从epoch以来的秒数更改为可读性稍高的值。我们用 Python 的 time 方便这一点的模块:
import time
import xml.etree.cElementTree as ET
def editXML(filename):
"""
Edit an example XML file
"""
tree = ET.ElementTree(file=filename)
root = tree.getroot()
for begin_time in root.iter("begin"):
begin_time.text = time.ctime(int(begin_time.text))
tree = ET.ElementTree(root)
with open("updated.xml", "w") as f:
tree.write(f)
if __name__ == "__main__":
editXML("original_appt.xml")
在这里,我们创建一个elementtree对象(tree),然后提取 root 从它开始。然后我们用elementtree的 ITER()。 方法查找标记为“begin”的所有标记。注意,iter()方法是在python 2.7中添加的。在for循环中,我们通过 时间.ctime() .您会注意到,在将字符串传递给CTIME时,我们必须将它转换为整数。输出应该如下所示:
<zAppointments reminder="15">
<appointment>
<begin>Thu Jun 07 16:28:00 2007</begin>
<uid>040000008200E000</uid>
<alarmTime>1181572063</alarmTime>
<state />
<location />
<duration>1800</duration>
<subject>Bring pizza home</subject>
</appointment>
<appointment>
<begin>Thu Jun 07 17:06:17 2007</begin>
<uid>sdlkjlkadhdakhdfd</uid>
<alarmTime>1181588888</alarmTime>
<state>TX</state>
<location>Dallas</location>
<duration>1800</duration>
<subject>Bring pizza home</subject>
</appointment>
</zAppointments>
您也可以使用elementtree的 查找() 或 芬德尔() 在XML中搜索特定标记的方法。find()方法将只查找第一个实例,而find all()将查找具有指定标签的所有标记。这些有助于编辑或解析,这是我们的下一个主题!
如何用elementtree解析XML¶
现在我们学习如何使用elementtree进行一些基本的解析。首先我们要通读代码,然后一点一点地读,这样我们才能理解它。请注意,此代码是基于原始示例的,但它也应该适用于第二个示例。
import xml.etree.cElementTree as ET
def parseXML(xml_file):
"""
Parse XML with ElementTree
"""
tree = ET.ElementTree(file=xml_file)
print(tree.getroot())
root = tree.getroot()
print("tag=%s, attrib=%s" % (root.tag, root.attrib))
for child in root:
print(child.tag, child.attrib)
if child.tag == "appointment":
for step_child in child:
print(step_child.tag)
# iterate over the entire tree
print("-" * 40)
print("Iterating using a tree iterator")
print("-" * 40)
iter_ = tree.getiterator()
for elem in iter_:
print(elem.tag)
# get the information via the children!
print("-" * 40)
print("Iterating using getchildren()")
print("-" * 40)
appointments = root.getchildren()
for appointment in appointments:
appt_children = appointment.getchildren()
for appt_child in appt_children:
print("%s=%s" % (appt_child.tag, appt_child.text))
if __name__ == "__main__":
parseXML("appt.xml")
您可能已经注意到了这一点,但在本例和最后一个示例中,我们导入的是CelementTree而不是普通的ElementTree。两者之间的主要区别在于,CelementTree是基于C的,而不是基于Python的,因此速度更快。无论如何,我们再次创建一个elementtree对象并从中提取根。您会注意到,我们会打印出根、根的标记和属性。接下来,我们将展示几种遍历标记的方法。第一个循环只是逐子级迭代XML。不过,这将只打印出顶级子级(约会),因此我们添加了一个if语句来检查该子级并对其子级进行迭代。
接下来,我们从树对象本身中获取一个迭代器,并以这种方式对其进行迭代。您可以得到相同的信息,但是在第一个示例中没有额外的步骤。第三种方法使用根的 getchildren()。 功能。这里我们再次需要一个内部循环来抓取每个预约标签中的所有孩子。最后一个示例使用根的 ITER()。 方法只循环任何与字符串“begin”匹配的标记。
如前一节所述,您还可以使用 查找() 或 芬德尔() 帮助您分别查找特定的标记或标记集。还要注意,每个元素对象都有一个 tag 和A text 属性,可用于获取准确的信息。
总结¶
现在您知道了如何使用minidom解析XML。您还学习了如何使用elementtree创建、编辑和分析XML。在python之外还有其他的库提供了使用XML的额外方法。一定要做一些调查,以确保你使用的工具是你理解的,因为如果你使用的工具是钝的,这个主题会变得非常混乱。