在线运行和编辑此教程中的代码
运行代码什么是 XML?
XML 是 "Extensible Markup Language"(可扩展标记语言)的缩写。它主要用于网页等场景,在这些场景中,数据具有特定结构,并由 XML 框架动态理解。
XML 会创建一种易于解读且支持层级结构的树形结构。只要页面遵循 XML,就可以称为 XML 文档。
- XML 文档有称为元素的部分,由开始和结束标签定义。标签是一种以
<开始、以>结束的标记结构。开始标签与结束标签之间的字符(如果存在)是元素的内容。元素可以包含标记,包括其他元素,这些称为“子元素”。 - 最大、顶层的元素称为根,它包含所有其他元素。
- 属性是在开始标签或空元素标签内存在的名称–值对。一个 XML 属性只能有单一值,并且每个属性在每个元素上最多出现一次。
为更好地理解,请看下面这个(已截短的)XML 文件:
<?xml version="1.0"?>
<collection>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back 2 the Future">
<format multiple="False">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
</genre>
<genre category="Thriller">
<decade years="1970s">
<movie favorite="False" title="ALIEN">
<format multiple="Yes">DVD</format>
<year>1979</year>
<rating>R</rating>
<description>"""""""""</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="True" title="Ferris Bueller's Day Off">
<format multiple="No">DVD</format>
<year>1986</year>
<rating>PG13</rating>
<description>Funny movie about a funny guy</description>
</movie>
<movie favorite="FALSE" title="American Psycho">
<format multiple="No">blue-ray</format>
<year>2000</year>
<rating>Unrated</rating>
<description>psychopathic Bateman</description>
</movie>
</decade>
</genre>
从上面的内容,您可以看到:
<collection>是唯一的根元素:它包含所有其他元素,例如<genre>或<movie>,这些都是子元素或子节点。如您所见,这些元素是嵌套的。
注意,这些子元素也可以作为父元素,包含它们自己的子元素,这些随后被称为“子子元素”。
- 您会发现,例如
<movie>元素包含一些“属性”,如favorite、title,可以提供更多信息!
有了这段关于 XML 文件的简短介绍,您已经可以开始学习 ElementTree 了!
ElementTree 简介
XML 的树形结构使得以编程方式进行导航、修改和删除都相对简单。Python 提供了内置库 ElementTree,可用于读取和操作 XML(及其他类似结构的文件)。
首先导入 ElementTree。常见做法是使用别名 ET:
import xml.etree.ElementTree as ET
解析 XML 数据
提供的 XML 文件描述了一个基础的电影收藏。唯一的问题是数据一团糟!这个收藏先后有许多不同的维护者,每个人都有自己的录入风格。本教程的主要目标是使用 Python 读取并理解该文件,然后修复其中的问题。
首先,您需要用 ElementTree 读取该文件。
tree = ET.parse('movies.xml')
root = tree.getroot()
现在树已初始化,您应该查看 XML 并打印一些值,以便理解树的结构。
树中的每个部分(包括根)都有一个用于描述元素的标签。此外,正如在介绍中所见,元素可能拥有属性,这些是用于重复标签时的额外描述符。属性还有助于校验填入该标签的值,再次强化了 XML 的结构化格式。
您将在本教程后面看到,当 XML 中包含属性时,它们会非常强大!
root.tag
'collection'
在顶层,您会看到该 XML 的根标签是 collection。
root.attrib
{}
因此,根没有任何属性。
For 循环
您可以使用简单的 “for” 循环轻松遍历根中的子元素(通常称为“children”)。
for child in root:
print(child.tag, child.attrib)
genre {'category': 'Action'}
genre {'category': 'Thriller'}
genre {'category': 'Comedy'}
现在您知道根 collection 的子元素全是 genre。为指明类型,XML 使用属性 category。根据 genre 元素,有动作(Action)、惊悚(Thriller)和喜剧(Comedy)电影。
通常了解整棵树中的所有元素很有帮助。一个有用的函数是 root.iter()。您可以在 “for” 循环中使用它,它会遍历整个树。
[elem.tag for elem in root.iter()]
['collection',
'genre',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'genre',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'genre',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description',
'decade',
'movie',
'format',
'year',
'rating',
'description']
这能让您大致了解元素数量,但不会显示属性或树中的层级。
有一种更直观查看整个文档的方法。任意元素都有 .tostring() 方法。如果将根传入 .tostring(),就能返回整个文档。在 ElementTree(记得我们起了别名 ET)中,.tostring() 的用法稍显特别。
由于 ElementTree 是能解析不止 XML 的强大库,您必须同时指定要显示为字符串的文档的编码与解码。对于 XML,请使用 'utf8' —— 这是 XML 的常用文档格式。
print(ET.tostring(root, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<collection>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back 2 the Future">
<format multiple="False">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
</genre>
<genre category="Thriller">
<decade years="1970s">
<movie favorite="False" title="ALIEN">
<format multiple="Yes">DVD</format>
<year>1979</year>
<rating>R</rating>
<description>"""""""""</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="True" title="Ferris Bueller's Day Off">
<format multiple="No">DVD</format>
<year>1986</year>
<rating>PG13</rating>
<description>Funny movie about a funny guy</description>
</movie>
<movie favorite="FALSE" title="American Psycho">
<format multiple="No">blue-ray</format>
<year>2000</year>
<rating>Unrated</rating>
<description>psychopathic Bateman</description>
</movie>
</decade>
</genre>
<genre category="Comedy">
<decade years="1960s">
<movie favorite="False" title="Batman: The Movie">
<format multiple="Yes">DVD,VHS</format>
<year>1966</year>
<rating>PG</rating>
<description>What a joke!</description>
</movie>
</decade>
<decade years="2010s">
<movie favorite="True" title="Easy A">
<format multiple="No">DVD</format>
<year>2010</year>
<rating>PG--13</rating>
<description>Emma Stone = Hester Prynne</description>
</movie>
<movie favorite="True" title="Dinner for SCHMUCKS">
<format multiple="Yes">DVD,digital,Netflix</format>
<year>2011</year>
<rating>Unrated</rating>
<description>Tim (Rudd) is a rising executive
who “succeeds” in finding the perfect guest,
IRS employee Barry (Carell), for his boss’ monthly event,
a so-called “dinner for idiots,” which offers certain
advantages to the exec who shows up with the biggest buffoon.
</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="False" title="Ghostbusters">
<format multiple="No">Online,VHS</format>
<year>1984</year>
<rating>PG</rating>
<description>Who ya gonna call?</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Robin Hood: Prince of Thieves">
<format multiple="No">Blu_Ray</format>
<year>1991</year>
<rating>Unknown</rating>
<description>Robin Hood slaying</description>
</movie>
</decade>
</genre>
</collection>
您可以扩展 iter() 的用法来帮助查找特定感兴趣的元素。root.iter() 会列出根下所有与指定元素匹配的子元素。这里,您将列出树中 movie 元素的所有属性:
for movie in root.iter('movie'):
print(movie.attrib)
{'favorite': 'True', 'title': 'Indiana Jones: The raiders of the lost Ark'}
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'Back 2 the Future'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'True', 'title': "Ferris Bueller's Day Off"}
{'favorite': 'FALSE', 'title': 'American Psycho'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Easy A'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
{'favorite': 'False', 'title': 'Ghostbusters'}
{'favorite': 'True', 'title': 'Robin Hood: Prince of Thieves'}
您已经可以看出 movies 的录入方式各不相同。先不用担心,稍后您将有机会修复其中一个错误。
XPath 表达式
很多时候元素没有属性,只有文本内容。使用 .text 属性,您可以打印这些内容。
现在,打印所有电影的描述。
for description in root.iter('description'):
print(description.text)
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
None provided.
Marty McFly
Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.
NA.
WhAtEvER I Want!!!?!
"""""""""
Funny movie about a funny guy
psychopathic Bateman
What a joke!
Emma Stone = Hester Prynne
Tim (Rudd) is a rising executive
who “succeeds” in finding the perfect guest,
IRS employee Barry (Carell), for his boss’ monthly event,
a so-called “dinner for idiots,” which offers certain
advantages to the exec who shows up with the biggest buffoon.
Who ya gonna call?
Robin Hood slaying
直接打印 XML 很有帮助,但 XPath 是一种查询语言,可用于快速便捷地搜索 XML。XPath 是 XML Path Language 的缩写,顾名思义,它使用类似“路径”的语法来定位并导航 XML 文档中的节点。
理解 XPath 对扫描和填充 XML 至关重要。ElementTree 提供了 .findall() 函数,它将遍历被引用元素的直接子元素。您可以使用 XPath 表达式来指定更有用的搜索。
下面,您将搜索 1992 年上映的电影:
for movie in root.findall("./genre/decade/movie/[year='1992']"):
print(movie.attrib)
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
.findall() 函数总是从指定的元素开始。这类函数对“查找并替换”非常强大。您甚至可以按属性搜索!
现在,仅打印在多种格式中可用的电影(一个属性)。
for movie in root.findall("./genre/decade/movie/format/[@multiple='Yes']"):
print(movie.attrib)
{'multiple': 'Yes'}
{'multiple': 'Yes'}
{'multiple': 'Yes'}
{'multiple': 'Yes'}
{'multiple': 'Yes'}
思考一下为什么在这个例子中,打印语句返回的是 multiple 的 "Yes" 值。想想这个 “for” 循环是如何定义的。您能否重写这个循环来打印电影标题?请在下方尝试:
提示:在 XPath 中使用 '...' 返回当前元素的父元素。
for movie in root.findall("./genre/decade/movie/format[@multiple='Yes']..."):
print(movie.attrib)
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
修改 XML
先前,电影标题混乱不堪。现在,再次打印它们:
for movie in root.iter('movie'):
print(movie.attrib)
{'favorite': 'True', 'title': 'Indiana Jones: The raiders of the lost Ark'}
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'Back 2 the Future'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'True', 'title': "Ferris Bueller's Day Off"}
{'favorite': 'FALSE', 'title': 'American Psycho'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Easy A'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
{'favorite': 'False', 'title': 'Ghostbusters'}
{'favorite': 'True', 'title': 'Robin Hood: Prince of Thieves'}
修正 Back 2 the Future 中的“2”。这应该是一个查找并替换的问题。编写代码查找标题为 'Back 2 the Future' 的电影并将其保存为变量:
b2tf = root.find("./genre/decade/movie[@title='Back 2 the Future']")
print(b2tf)
<Element 'movie' at 0x10ce00ef8>
请注意,使用 .find() 方法会返回树中的一个元素。大多数时候,编辑元素内部的内容更有用。
将 Back 2 the Future 这个元素变量的 title 属性改为 "Back to the Future"。然后打印该变量的属性以查看更改。您可以轻松地通过访问元素的属性并为其赋予新值来完成:
b2tf.attrib["title"] = "Back to the Future"
print(b2tf.attrib)
{'favorite': 'False', 'title': 'Back to the Future'}
将您的更改写回 XML,使它们在文档中得到永久修复。再次打印电影属性,确保更改生效。使用 .write() 方法完成:
tree.write("movies.xml")
tree = ET.parse('movies.xml')
root = tree.getroot()
for movie in root.iter('movie'):
print(movie.attrib)
{'favorite': 'True', 'title': 'Indiana Jones: The raiders of the lost Ark'}
{'favorite': 'True', 'title': 'THE KARATE KID'}
{'favorite': 'False', 'title': 'Back to the Future'}
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'True', 'title': 'Batman Returns'}
{'favorite': 'False', 'title': 'Reservoir Dogs'}
{'favorite': 'False', 'title': 'ALIEN'}
{'favorite': 'True', 'title': "Ferris Bueller's Day Off"}
{'favorite': 'FALSE', 'title': 'American Psycho'}
{'favorite': 'False', 'title': 'Batman: The Movie'}
{'favorite': 'True', 'title': 'Easy A'}
{'favorite': 'True', 'title': 'Dinner for SCHMUCKS'}
{'favorite': 'False', 'title': 'Ghostbusters'}
{'favorite': 'True', 'title': 'Robin Hood: Prince of Thieves'}
修复属性
multiple 属性在某些地方不正确。使用 ElementTree 根据电影包含的格式数量来修复该标记。首先,打印 format 的属性与文本,看看需要修复哪些部分。
for form in root.findall("./genre/decade/movie/format"):
print(form.attrib, form.text)
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,Online
{'multiple': 'False'} Blu-ray
{'multiple': 'Yes'} dvd, digital
{'multiple': 'No'} VHS
{'multiple': 'No'} Online
{'multiple': 'Yes'} DVD
{'multiple': 'No'} DVD
{'multiple': 'No'} blue-ray
{'multiple': 'Yes'} DVD,VHS
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,digital,Netflix
{'multiple': 'No'} Online,VHS
{'multiple': 'No'} Blu_Ray
这个标签确实需要一些处理。
您可以用正则查找逗号——这将用于判断 multiple 属性应该是 "Yes" 还是 "No"。添加和修改属性可通过 .set() 方法轻松完成。
注意:re 是 Python 的标准正则解析器。若想了解更多正则表达式,请参考此教程。
import re
for form in root.findall("./genre/decade/movie/format"):
# Search for the commas in the format text
match = re.search(',',form.text)
if match:
form.set('multiple','Yes')
else:
form.set('multiple','No')
# Write out the tree to the file again
tree.write("movies.xml")
tree = ET.parse('movies.xml')
root = tree.getroot()
for form in root.findall("./genre/decade/movie/format"):
print(form.attrib, form.text)
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,Online
{'multiple': 'No'} Blu-ray
{'multiple': 'Yes'} dvd, digital
{'multiple': 'No'} VHS
{'multiple': 'No'} Online
{'multiple': 'No'} DVD
{'multiple': 'No'} DVD
{'multiple': 'No'} blue-ray
{'multiple': 'Yes'} DVD,VHS
{'multiple': 'No'} DVD
{'multiple': 'Yes'} DVD,digital,Netflix
{'multiple': 'Yes'} Online,VHS
{'multiple': 'No'} Blu_Ray
移动元素
部分数据被放到了错误的年代。使用您对 XML 和 ElementTree 的学习来查找并修复年代数据错误。
打印整个文档中的 decade 标签以及其下的 year 标签会很有用。
for decade in root.findall("./genre/decade"):
print(decade.attrib)
for year in decade.findall("./movie/year"):
print(year.text, '\n')
{'years': '1980s'}
1981
1984
1985
{'years': '1990s'}
2000
1992
1992
{'years': '1970s'}
1979
{'years': '1980s'}
1986
2000
{'years': '1960s'}
1966
{'years': '2010s'}
2010
2011
{'years': '1980s'}
1984
{'years': '1990s'}
1991
两部电影的年份在错误的年代里,它们来自 2000 年代。请用 XPath 表达式找出这些电影。
for movie in root.findall("./genre/decade/movie/[year='2000']"):
print(movie.attrib)
{'favorite': 'False', 'title': 'X-Men'}
{'favorite': 'FALSE', 'title': 'American Psycho'}
您需要在动作(Action)类型中新增一个 2000 年代的 decade 标签,以便移动 X-Men 的数据。可以使用 .SubElement() 方法将此标签添加到 XML 末尾。
action = root.find("./genre[@category='Action']")
new_dec = ET.SubElement(action, 'decade')
new_dec.attrib["years"] = '2000s'
print(ET.tostring(action, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back to the Future">
<format multiple="No">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
<decade years="2000s" /></genre>
现在使用 .append() 和 .remove() 分别将 X-Men 追加到 2000 年代,并从 1990 年代中移除。
xmen = root.find("./genre/decade/movie[@title='X-Men']")
dec2000s = root.find("./genre[@category='Action']/decade[@years='2000s']")
dec2000s.append(xmen)
dec1990s = root.find("./genre[@category='Action']/decade[@years='1990s']")
dec1990s.remove(xmen)
print(ET.tostring(action, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back to the Future">
<format multiple="No">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
<decade years="2000s"><movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
</decade></genre>
构建 XML 文档
很好,您已经成功地将整部电影“移动”到了新的年代。将更改保存回 XML。
tree.write("movies.xml")
tree = ET.parse('movies.xml')
root = tree.getroot()
print(ET.tostring(root, encoding='utf8').decode('utf8'))
<?xml version='1.0' encoding='utf8'?>
<collection>
<genre category="Action">
<decade years="1980s">
<movie favorite="True" title="Indiana Jones: The raiders of the lost Ark">
<format multiple="No">DVD</format>
<year>1981</year>
<rating>PG</rating>
<description>
'Archaeologist and adventurer Indiana Jones
is hired by the U.S. government to find the Ark of the
Covenant before the Nazis.'
</description>
</movie>
<movie favorite="True" title="THE KARATE KID">
<format multiple="Yes">DVD,Online</format>
<year>1984</year>
<rating>PG</rating>
<description>None provided.</description>
</movie>
<movie favorite="False" title="Back to the Future">
<format multiple="No">Blu-ray</format>
<year>1985</year>
<rating>PG</rating>
<description>Marty McFly</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Batman Returns">
<format multiple="No">VHS</format>
<year>1992</year>
<rating>PG13</rating>
<description>NA.</description>
</movie>
<movie favorite="False" title="Reservoir Dogs">
<format multiple="No">Online</format>
<year>1992</year>
<rating>R</rating>
<description>WhAtEvER I Want!!!?!</description>
</movie>
</decade>
<decade years="2000s"><movie favorite="False" title="X-Men">
<format multiple="Yes">dvd, digital</format>
<year>2000</year>
<rating>PG-13</rating>
<description>Two mutants come to a private academy for their kind whose resident superhero team must
oppose a terrorist organization with similar powers.</description>
</movie>
</decade></genre>
<genre category="Thriller">
<decade years="1970s">
<movie favorite="False" title="ALIEN">
<format multiple="No">DVD</format>
<year>1979</year>
<rating>R</rating>
<description>"""""""""</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="True" title="Ferris Bueller's Day Off">
<format multiple="No">DVD</format>
<year>1986</year>
<rating>PG13</rating>
<description>Funny movie about a funny guy</description>
</movie>
<movie favorite="FALSE" title="American Psycho">
<format multiple="No">blue-ray</format>
<year>2000</year>
<rating>Unrated</rating>
<description>psychopathic Bateman</description>
</movie>
</decade>
</genre>
<genre category="Comedy">
<decade years="1960s">
<movie favorite="False" title="Batman: The Movie">
<format multiple="Yes">DVD,VHS</format>
<year>1966</year>
<rating>PG</rating>
<description>What a joke!</description>
</movie>
</decade>
<decade years="2010s">
<movie favorite="True" title="Easy A">
<format multiple="No">DVD</format>
<year>2010</year>
<rating>PG--13</rating>
<description>Emma Stone = Hester Prynne</description>
</movie>
<movie favorite="True" title="Dinner for SCHMUCKS">
<format multiple="Yes">DVD,digital,Netflix</format>
<year>2011</year>
<rating>Unrated</rating>
<description>Tim (Rudd) is a rising executive
who “succeeds” in finding the perfect guest,
IRS employee Barry (Carell), for his boss’ monthly event,
a so-called “dinner for idiots,” which offers certain
advantages to the exec who shows up with the biggest buffoon.
</description>
</movie>
</decade>
<decade years="1980s">
<movie favorite="False" title="Ghostbusters">
<format multiple="Yes">Online,VHS</format>
<year>1984</year>
<rating>PG</rating>
<description>Who ya gonna call?</description>
</movie>
</decade>
<decade years="1990s">
<movie favorite="True" title="Robin Hood: Prince of Thieves">
<format multiple="No">Blu_Ray</format>
<year>1991</year>
<rating>Unknown</rating>
<description>Robin Hood slaying</description>
</movie>
</decade>
</genre>
</collection>
ElementTree 有哪些新变化?
以下是较新 Python 版本中 ElementTree 库的新特性与改进概览:
1. 支持 XPath 1.0(Python 3.8):自 Python 3.8 起,ElementTree 在 find() 和 findall() 方法中包含对 XPath 1.0 的完整支持,可进行更丰富、更复杂的 XML 查询。例如:
# 使用 XPath 查找具有特定属性的所有电影
for movie in root.findall(".//movie[@favorite='True']"):
print(movie.attrib)
2. 命名空间改进(Python 3.8+):增强了对 XML 命名空间的支持,更便于处理使用前缀或默认命名空间的 XML 文件。例如:
# 注册命名空间并据此查找元素
ET.register_namespace('', 'http://example.com/namespace')
movies = root.findall(".//{http://example.com/namespace}movie")
3. 解析器增强(Python 3.9):更友好的解析错误信息,让调试格式错误的 XML 文件更容易。
4. 新增 indent() 函数(Python 3.9):添加 xml.etree.ElementTree.indent(),可通过缩进元素对 XML 文档进行美化打印。例如:
ET.indent(root, space=" ", level=0)
ET.dump(root)
5. 使用 iterparse 的高效解析(Python 3.10):针对内存效率进行了优化,尤其适用于处理大型 XML 文件。
6. 文档扩展(持续更新):Python 的 ElementTree 文档更加全面,涵盖最佳实践与高级用例。
ElementTree 的弃用特性与替代方案
1. Python 3.8+ 中带 xml_declaration 的 write():当编码设为 'unicode' 时,write() 方法的 xml_declaration 参数已弃用。
- 替代方案:仅当编码被明确设置为
'unicode'以外的值时,才使用xml_declaration。
tree.write("output.xml", encoding="utf-8", xml_declaration=True)
2. html 解析器:尽管未正式弃用,但不建议使用 ElementTree 解析 HTML,因为它在处理不规范 HTML 时能力有限。
- 替代方案:使用专为 HTML 解析设计的库,例如
bs4包中的BeautifulSoup。
from bs4 import BeautifulSoup soup = BeautifulSoup(html_content, 'html.parser')
3. 命名空间处理的变通法:随着新版本中更健壮的命名空间支持,旧式的手动处理命名空间方法(例如将命名空间 URI 与元素标签拼接)已不再推荐。
- 替代方案:使用内置的、支持命名空间的方法与函数。
ET.register_namespace('', 'http://example.com/namespace') movies = root.findall(".//{http://example.com/namespace}movie")
4. 手动美化打印:Python 3.9 新增的 indent() 函数使得以往手动缩进与格式化 XML 的技巧变得多余。
- 替代方案:使用
ET.indent()进行自动格式化。
ET.indent(root, space=" ")
5. 直接使用 _ElementInterface:诸如 _ElementInterface 这样的内部类并非为直接使用而设计,未来版本中可能会失效。
- 替代方案:始终使用 ElementTree 库已文档化的公共 API。
结论
关于 XML 以及使用 ElementTree,有几点关键内容需要记住。
标签构建树形结构,并指明应在其中划分哪些值。合理的结构设计可以让 XML 的读写更容易。标签始终需要起止括号来体现父子关系。
属性进一步描述如何校验标签,或用于布尔指示。属性通常取非常具体的值,以便 XML 解析器(以及用户)能使用属性来检查标签值。
ElementTree 是一个重要的 Python 库,可让您解析和导航 XML 文档。使用 ElementTree 将 XML 文档拆解为易于处理的树结构。如有疑问,就打印出来(print(ET.tostring(root, encoding='utf8').decode('utf8')))——使用这个有用的打印语句一次性查看整个 XML 文档。它有助于在编辑、添加或删除 XML 时进行检查。
现在,您已具备理解 XML 并开始解析的能力!

作者
Steph Howson
FAQs
在数据科学中,使用 XML 的常见场景有哪些?
XML 在数据科学中常用于系统间数据交换、网页抓取、配置文件,以及处理具有复杂层级结构的数据。当使用返回 XML 数据的 API 时,XML 尤其有用。
ElementTree 能高效处理大型 XML 文件吗?
ElementTree 适合处理中等规模的 XML 文件,但对于非常大的文件,您可以考虑使用 lxml 或 xml.sax,它们在内存效率上更佳,并能处理大型文件的流式读取。
ElementTree 与 lxml 或 minidom 等其他 XML 解析库相比如何?
ElementTree 属于 Python 标准库,易于用于基础的 XML 解析任务。 lxml 更强大且更快,提供了诸如 XPath 2.0 支持等附加功能。 minidom 则是另一款标准库,基于 DOM,对大型文档的效率较低。
什么是 XPath 表达式?它们在 XML 解析中有何用处?
XPath 表达式是用于从 XML 文档中选择节点的查询语言。它们有助于在 XML 文档中导航元素与属性,从而实现精确的数据检索与操作。
在用 ElementTree 解析前,如何校验 XML 文档?
可以使用 XML 模式定义(XSD)或文档类型定义(DTD)对 XML 文档进行校验。Python 库如 lxml 提供了对这些标准的内置校验支持。
使用 ElementTree 修改 XML 数据有哪些最佳实践?
最佳实践包括在数据副本上操作以避免意外数据丢失、使用 XPath 表达式进行精确导航,并确保所有更改都保持 XML 文档的良好格式性。
可以使用 ElementTree 将 XML 数据转换为 JSON 吗?
ElementTree 本身不直接提供 XML 到 JSON 的转换,但您可以使用 ElementTree 将 XML 解析为 Python 字典,然后使用 Python 的 json 模块将该字典转换为 JSON。
如何在 ElementTree 中处理 XML 命名空间?
ElementTree 可通过在标签名中使用 {namespace} 前缀来处理 XML 命名空间。您也可以使用 ET.register_namespace() 注册命名空间,以更轻松地处理带命名空间的 XML。
在 ElementTree 中遇到 XML 解析错误时该怎么办?
检查常见问题,例如 XML 格式错误、不受支持的编码或文件路径不正确。使用 Python 的错误处理机制(try-except 代码块)来优雅地诊断与处理解析错误。
能否使用 ElementTree 对 XML 文档进行美化打印?
ElementTree 本身不支持直接美化打印,但您可以使用 xml.dom.minidom 解析 XML 字符串,然后使用其 toprettyxml() 方法来格式化 XML 以提高可读性。
作为一名数据科学家,理解 XML 对于网页抓取以及解析结构化文档的日常实践都非常有用。
ElementTree包。ElementTree的函数、for 循环和 XPath 表达式,学习如何探索 XML 树,从而更好地理解您正在处理的数据。