python-xml
简介:Python作为一种功能强大的编程语言,在XML解析方面提供了ElementTree和lxml两大主流库。ElementTree是标准库,适合轻量级处理;而lxml基于C库实现,支持XPath、CSS选择器及XML Schema验证,性能更强。
一. XML基础概念
XML(可扩展标记语言)是一种用于存储和传输结构化数据的文本格式,具有良好的可读性和跨平台兼容性。它通过自定义标签来描述数据结构,使得数据在不同系统之间可以高效交换。
1.1、 XML基本语法结构
1.1.1、基础的xml
XML文档由嵌套的标签组成,每个标签可以包含属性和文本内容。以下是一个简单的XML示例:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore><book category="fiction"><title lang="en">The Great Gatsby</title><author>F. Scott Fitzgerald</author><year>1925</year></book>
</bookstore>
标签与属性说明:
<?xml ...?>:XML声明,定义版本和编码方式。<bookstore>:根元素,包含所有数据。<book>:子元素,带有属性category。<title>:元素包含文本内容The Great Gatsby和属性lang。
1.1.2、带有命名空间的xml
XML 命名空间(XML Namespace)是为了解决在 XML 文档中元素和属性名称可能发生冲突的问题而引入的一种机制。它允许你在同一个 XML 文档中使用相同名称但来自不同来源(或上下文)的元素或属性,而不会产生歧义。
两个xml混在一起时,可能会存在相同的tag名称,无法区分,会引起歧义,所以需要用到命名空间
使用 xmlns:前缀="命名空间URI" 来声明一个命名空间,并给它一个前缀。
<root xmlns:bk="http://example.com/books"xmlns:mv="http://example.com/movies"><bk:title>Python编程</bk:title> <!-- 来自书籍命名空间 --><mv:title>星际穿越</mv:title> <!-- 来自电影命名空间 -->
</root>
bk:title的bk是前缀,代表http://example.com/books这个命名空间。mv:title的mv是前缀,代表http://example.com/movies这个命名空间。- 虽然两个标签都是
<title>,但由于它们属于不同的命名空间,所以不会冲突。
1.2、 XML与HTML、JSON的区别
| 特性 | XML | HTML | JSON |
|---|---|---|---|
| 用途 | 数据交换、结构描述 | 页面展示、超文本标记 | 数据交换、轻量级结构 |
| 可扩展性 | 支持自定义标签 | 固定标签结构 | 基于键值对结构 |
| 语法严格性 | 语法严格,需正确闭合与嵌套 | 松散语法,容忍错误 | 语法严格 |
| 可读性 | 高 | 高 | 高 |
| 应用场景 | 配置文件、Web服务、RSS等 | 网页结构展示 | REST API、前端数据交互 |
XML比HTML更具灵活性,适用于需要自定义结构的场合;而JSON更轻量,在现代Web开发中广泛使用。尽管JSON流行,XML在企业级系统、遗留系统和标准化协议中仍占有一席之地。
二、xml库和xml的解析
2.1、xml库的结构
XML 模块的子模块包括:
xml.etree.ElementTree: ElementTree API,一个简单而轻量级的XML处理器xml.dom:DOM API 定义xml.dom.minidom:最小的 DOM 实现xml.dom.pulldom:支持构建部分 DOM 树xml.sax:SAX2 基类和便利函数xml.parsers.expat:Expat解析器绑定
2.2、解析处理xml的库
Python提供了多个处理XML的库,每个库都有其特点和适用场景:
- xml.etree.ElementTree:Python标准库的一部分,提供了轻量级的API,适合大多数简单的XML处理任务。
- lxml:第三方库,功能强大,支持XPath和XSLT,适合处理复杂的XML文档。
- xml.dom.minidom:Python标准库中的DOM实现,适合小型XML文件处理。
- xml.sax:基于事件驱动的解析模型,适合处理大型XML文件。
对于简单的XML文档,xml.etree.ElementTree通常是最佳选择,因为它无需额外安装,API简单直观,且性能良好。复杂的xml文件可以选用lxml。基本解析流程大致相同,主要包括加载 XML 数据、获取解析对象、遍历结构、处理异常等
2.3、ElementTree和lxml的比较
在Python中,XML解析器的选择直接影响到代码的效率、可维护性以及是否支持高级功能(如XPath、命名空间处理、CSS选择器等)。ElementTree 是 [Python 标准库](https://so.csdn.net/so/search?q=Python 标准库&spm=1001.2101.3001.7020)的一部分,而 lxml 是一个功能更强大、性能更高的第三方库。
2.3.1、ElementTree
xml.etree.ElementTree 是 Python 标准库中用于解析和创建 XML 数据的模块。其优点在于无需额外安装,适用于简单的 XML 解析任务。
优势:
- 标准库支持 :无需额外安装,跨平台兼容性好。
- 简洁易用 :API设计清晰,易于上手。
- 轻量级 :占用内存少,适合处理小到中型XML文件。
限制:
- XPath支持有限 :仅支持有限的XPath语法,不支持复杂的表达式。
- 无命名空间自动处理 :需要手动处理命名空间前缀。
- 性能一般 :相比 lxml,处理大文件时效率较低。
2.3.2、lxml
lxml 是一个功能更加强大的 XML 解析库,基于 libxml2 和 libxslt ,不仅兼容 ElementTree 的 API,还扩展了大量高级功能。
功能扩展:
- 完整的XPath支持 :支持XPath 1.0语法,可以进行复杂查询。
- CSS选择器支持 :通过
cssselect模块使用类似 CSS 的选择器语法。 - 命名空间自动处理 :支持自动处理带有命名空间的 XML 文档。
- XSLT转换支持 :支持使用 XSLT 转换 XML 数据。
- 文档验证 :支持 DTD、XML Schema 等验证机制。
性能优势:
- 速度更快 :底层使用 C 编写的库,执行效率远高于原生 ElementTree。
- 内存占用低 :优化了内存使用,适合处理大型 XML 文件。
2.3.3 不同场景下的库选择建议
| 场景 | 推荐库 | 理由 |
|---|---|---|
| 简单XML解析任务 | ElementTree | 无需安装,适合轻量级任务 |
| 大型XML文件处理 | lxml | 更快的解析速度与内存效率 |
| 需要XPath高级查询 | lxml | 支持完整XPath语法 |
| 需要CSS选择器 | lxml | 提供类似CSS的查询方式 |
| Web爬虫提取XML数据 | lxml + cssselect | 易于与爬虫框架结合使用 |
三、ElementTree的使用
官方文档 里面对 ET 模块进行了较为详细的描述,总的来说,ET 模块可以归纳为三个部分:ElementTree类,Element类以及一些操作 XML 的函数。XML 可以看成是一种树状结构,ET 使用ElementTree类来表示整个 XML 文档,使用Element类来表示 XML 的一个结点。参考文档 https://docs.python.org/3.14/library/xml.etree.elementtree.html#
- 与整个文档的交互(读写文件)通常是在ElementTree级别完成的(文件的读写)。
- 与单个XML元素及其子元素的交互是在Element级完成的。
3.1、ElementTree常用方法
| 函数 | 说明 |
|---|---|
| xml.etree.ElementTree.parse(source, parser=None) | 把XML文件解析成 element tree实例对象。默认使用XMLparse解析器 |
| xml.etree.ElementTree.fromstring(text) | 把字符串XML常量解析成Element实例对象 |
| xml.etree.ElementTree.XML(text, parser=None) | 把字符串XML常量解析成Element实例对象。可以指定parse解析器 |
| xml.etree.ElementTree.XMLID(text, parser=None) | 把字符串XML常量解析成Element实例对象,并且遍历所有元素的title名称为id的数组 |
| xml.etree.ElementTree.tostring(element, encoding=“us-ascii”, method=“xml”, *, short_empty_elements=True) | 将element对象转换为字符串 |
| xml.etree.ElementTree.tostringlist(element, encoding=“us-ascii”, method=“xml”, *, short_empty_elements=True) | 将element对象转换为字符串列表 |
| xml.etree.ElementTree.SubElement(parent, tag, attrib={}, **extra) | 用于创建 element 实例,并将其添加到现有的 element 中。 |
| xml.etree.ElementTree.ElementTree(element=None, file=None) | 创建一个 element tree实例对象 |
| xml.etree.ElementTree.Element(tag, attrib={}, **extra) | 创建一个element元素 |
3.2、ElementTree对象的常用方法和属性
| 类方法 | 说明 |
|---|---|
ElementTree.iter(tag=None) | 迭代所有元素 |
ElementTree.iterfind(path, namespaces=None) | 从根元素开始匹配和 Element.iterfind()作用一样 |
ElementTree.itertext() | 从根元素开始匹配和 Element.findtext()作用一样。 |
ElementTree.findall(path) | 从根元素开始匹配和 Element.findall()作用一样。 |
ElementTree.findtext(path, default=None, namespaces=None) | 从根元素开始匹配和 Element.findtext()作用一样。 |
ElementTree.find(path) | 从根元素开始匹配和 Element.find()作用一样。 |
| parse(source, parser=None) | 解析xml文本,返回根元素,是一个Element对象 |
| write(file, encoding=“us-ascii”, xml_declaration=None, default_namespace=None, method=“xml”, *, short_empty_elements=True): | 写入XML文本。 |
| _setroot(element) | 替换根元素,原来的根元素中的内容会消失 |
3.3、Element 对象的常用方法和属性
Element 对象的方法
| 类方法 | 说明 |
|---|---|
Element.iter(tag=None) | 遍历该Element所有后代,也可以指定tag进行遍历寻找。 |
Element.iterfind(path, namespaces=None) | 根据tag或XPath查找所有的后代。 |
Element.itertext() | 遍历所有后代并返回text值。 |
Element.findall(path) | 查找当前元素下tag或XPath能够匹配的直系节点 |
Element.find(path) | 查找当前元素下tag或XPath能够匹配的首个直系节点。 |
Element.findtext(path, default=None, namespaces=None) | 寻找第一个匹配子元素,返回其text值。匹配对象可以为tag或XPath。 |
Element.text | 获取当前元素的text值。 |
Element.get(key, default=None) | 获取元素指定key对应的属性值,如果没有该属性,则返回default值。 |
Element.keys() | 返回元素属性名称列表 |
Element.items() | 返回(name,value)列表 |
| remove(subelement) | 删除子元素 |
| clear() | 清除所有子元素和所有属性,并将文本和尾部属性设置为None |
| set(attribute_name,attribute_value) | 在某标签中设置属性和属性值 |
| append(subelement) | 将元素子元素添加到元素的子元素内部列表的末尾 |
| extend(subelements) | 追加子元素 |
Element 对象属性方法:
| 方法名 | 说明 |
|---|---|
Element.tag | 节点名(tag)(str) |
Element.attrib | 属性(attributes)(dict) |
Element.text | 文本(text)(str) |
Element.tail | 附加文本(tail) (str) |
Element[:] | 子节点列表(list) |
3.4、解析 XML
解析xml有两种简单方式,解析xml文件用parse函数和解析xml字符串用fromstring函数。 parse函数返回的是ElementTree对象,而fromstring函数返回的是Element对象。这两种方法解析xml都需要读取整个文档
3.4.1、解析xml文件
ET.parse(source, parser=None)
-
功能:解析xml字符串,获取 XML 文档对象 ElementTree
-
常用参数:
-
source:必选,XML 文件的路径(如
"data.xml"),或类文件对象(如通过open()打开的文件) -
parser:可选,自定义的 XML 解析器,一般用默认即可,高级用户可定制
-
-
返回值:返回一个 ElementTree对象,通过它可以:
-
调用
.getroot()获取 XML 的根元素(root element) -
调用
.write()将 XML 写回文件 -
进行 XML 树的操作和遍历
-
import xml.etree.ElementTree as ET# 解析xml文件,获取 XML 文档对象 ElementTree
tree = ET.parse('file.xml')
#获取 XML 文档对象的根结点 Element对象
root = tree.getroot()
print(tree, root)
输出结果为
<xml.etree.ElementTree.ElementTree object at 0x000001AA5C47E690> <Element 'data' at 0x000001AA5C7F2DE0>
3.4.2、解析xml字符串
ET.fromstring(source, parser=None)
-
功能:解析一个包含 XML 数据的字符串,并返回该 XML 的根元素(root
Element对象) -
常用参数:
-
source:必选,包含 合法 XML 数据的字符串,比如
"<root><child>内容</child></root>" -
parser:可选,自定义的 XML 解析器(一般用默认即可,高级用户可定制)
-
-
返回值:
-
返回一个
Element对象,即该 XML 字符串所表示的根元素(root element)。 -
可以进一步通过
.tag、.attrib、.text、.find()、.findall()等访问其内容。
-
# 解析xml字符串,获取的是一个Element对象
xml_str = "<python_test><data>hello</data></python_test>"
root = ET.fromstring(xml_str)
print(root)
输出结果为:
<Element 'python_test' at 0x00000277953A2D90>
3.5、查看Element 对象的属性
<?xml version="1.0"?>
<data shelf="New Arrivals" shelf2="add_a">根节点文本信息 <!-- data为根节点的tag;shelf="New Arrivals" shelf2="add_a"为根节点的属性;根节点属性信息为text --><title id="0">The Great Gatsby1</title>b <!-- title为子节点的tag;id="0"为子节点的属性;The Great Gatsby1为子节点的文本text;b为 -->e<name name="0">The Great Gatsby2</name>c<id name="Liechtenstein">d<title id="123">The Great Gatsby3</title><rank updated="yes">2</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"/><neighbor name="Switzerland" direction="W"/></id>ad<country name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"/></country>
</data>
tree = ET.parse('file.xml')
root = tree.getroot()
print("根节点的text为",root.text)
print("根节点的属性attrib为",root.attrib) #attrib可以存在多个,所以输出是一个dict
print("根节点的节点名tag为",root.tag)
print("根节点的附加文本tail为",root.tail)
print("根节点的子节点列表为",root[:])
print("-"*20)
print("子节点的text为",root[0].text)
print("子节点的属性attrib为",root[0].attrib)
print("子节点的节点名tag为",root[0].tag)
print("子节点的附加文本tail为",root[0].tail)输入结果为:
根节点的text为 根节点文本信息 根节点的属性attrib为 {'shelf': 'New Arrivals', 'shelf2': 'add_a'}
根节点的节点名tag为 data
根节点的附加文本tail为 None
根节点的子节点列表为 [<Element 'title' at 0x000001B26CF46ED0>, <Element 'name' at 0x000001B26CF46F70>, <Element 'id' at 0x000001B26CF47010>, <Element 'country' at 0x000001B26CF47290>]
--------------------
子节点的text为 The Great Gatsby1
子节点的属性attrib为 {'id': '0'}
子节点的节点名tag为 title
子节点的附加文本tail为 b e
官网解释:text 属性会存放元素的开始标记及其第一个子元素或结束标记之间的文本,或者为 None,而 tail 属性会存放元素的结束标记及下一个标记之间的文本,或者为 None。所以根节点是没有tail的,子节点可以有。
3.6、创建xml
ET.Element(tag, attrib={}, **extra)
-
功能:解析xml字符串,获取 XML 文档对象 ElementTree
-
常用参数:
- tag, # 文件路径(str)或文件对象
- attrib, # 可选。表示该元素的属性(attributes),是一个字典,如
{"id": "123", "lang": "zh"}。 - xml_declaration=None,# 是否写入 XML 声明,如 True 或 False 或指定版本
- default_namespace=None, # 默认命名空间(高级用法)
- method=“xml” # 写入方法,通常为 “xml”(默认)
-
返回值
-
返回一个
Element对象,代表一个 XML 标签节点,比如<tag attr="value">。 -
该对象本身还没有子元素和文本内容,但你可以后续添加。
-
ET.SubElement(parent, tag, attrib={}, **extra)
-
功能:用于在指定的父元素(
parent)下创建一个新的子元素(XML 标签),并自动将该子元素挂载(添加)为父元素的子节点。 -
常用参数:
- parent:必需,Element对象。父元素,即你要在其下添加子元素的那个 XML 节点
- tag:必选,str。子元素的标签名,比如
"book"、"title"、"child"等 - attribb: 可选,dict。子元素的属性,是一个字典,如
{"id": "001"} - **extra:可选,关键字参数。也是用于设置属性的,和
attrib作用相同,二选一即可
-
返回值
- 返回新创建的 子元素(
Element对象),你可以进一步设置它的.text、.attrib、或继续添加它的子元素。
- 返回新创建的 子元素(
tree.write()
-
功能:用于将整个 XML 树(由根元素和其子元素构成)写入到一个文件(或类文件对象)中,通常保存为 XML 格式。
-
常用参数:
-
file, # 文件路径(str)或文件对象
-
encoding=“us-ascii”, # 编码格式,如 “utf-8”
-
xml_declaration=None,# 是否写入 XML 声明,如 True 或 False 或指定版本。一般建议设置为TRUE
-
default_namespace=None, # 默认命名空间(高级用法)
-
method=“xml” # 写入方法,通常为 “xml”(默认)
-
import xml.etree.ElementTree as ET# 创建一个元素实例,并且设置元素的tag,tag为必填参数,attrib(元素属性)为选填。创建的并不是根元素,仅仅是一个元素,是否是根元素看后续ET.SubElement如何设置
a = ET.Element("set_tag", text="add_text")
ff = ET.Element('ff') # 后续未引用就是不是根元素
# 设置元素a的text
a.text = "a_text文本"
# 设置元素a的属性attrib
a.attrib['a1'] = 'a1'# 创建一个元素实例,并且将元素添加到已有元素实例中,parent必填,指定现有实例;tag为必填参数,attrib(元素属性)为选填
b = ET.SubElement(a, 'b', b_attrib="b_attrib")
# 设置元素a的text
b.text = 'leehao.me'
# 设置元素的附加文本tail中
b.tail = "b_tail"# 创建元素实例c,并且将其添加到已有元素b中
c = ET.SubElement(b, 'c')
c.attrib['greeting'] = 'hello'
c.text = "text_noah_test"
# 设置元素的附加文本tail中
c.tail = "tail_c"# 创建元素实例d,并且将其添加到已有元素a中
d = ET.SubElement(a, 'd')
d.text = 'www.leehao.me'# 将创建的元素a转换为string
xml_str = ET.tostring(a, encoding='UTF-8', method="xml")
print(xml_str)# 创建ElementTree对象,并且指定根元素为a,以便使用其 write 方法。虽然element是选填,但是推荐传入根元素。另外file是可选项,一般不推荐通过传入文件参数来构造一个ElementTree对象,新版本推荐使用ET.parse("file.xml")
tree = ET.ElementTree(a)
tree.write('a.xml', encoding='UTF-8', xml_declaration=True)
打印输出如下:
b'<set_tag text="add_text" a1="a1">a_text\xe6\x96\x87\xe6\x9c\xac<b b_attrib="b_attrib">leehao.me<c greeting="hello">text_noah_test</c>tail_c</b>b_tail<d>www.leehao.me</d></set_tag>'
生成的文件名称为a.xml。这里的文件内容是经过手动格式化的,实际写入的是压缩的xml格式,不便于阅读。代码里可以加入indent美化一下。或者使用lxml
<?xml version='1.0' encoding='UTF-8'?>
<set_tag text="add_text" a1="a1">a_text文本<b b_attrib="b_attrib">leehao.me<c greeting="hello">text_noah_test</c>tail_c</b>b_tail<d>www.leehao.me</d>
</set_tag>
3.7、查询xml
XML 是一种树状结构,每个元素可以有若干个子元素,这些就是它的子节点。Element提供了一些实用的递归遍历子树(iter)、查找子树(find、findall)、访问元素属性(get)、通过索引或者循环遍历当前节点的子节点
<?xml version="1.0"?>
<data shelf="New Arrivals" shelf2="add_a">根节点文本信息 <!-- data为根节点的tag;shelf="New Arrivals" shelf2="add_a"为根节点的属性;根节点属性信息为text --><title id="0">The Great Gatsby1</title>b <!-- title为子节点的tag;id="0"为子节点的属性;The Great Gatsby1为子节点的文本text;b为 -->e<name name="0">The Great Gatsby2</name>c<id name="Liechtenstein">d<title id="123">The Great Gatsby3</title><rank updated="yes">2</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"/><neighbor name="Switzerland" direction="W"/></id>ad<id name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"/></id>
</data>
3.7.1、通过索引直接访问子元素(适用于已知顺序)
不建议使用此种方式,这种索引方式不健壮,如果子元素顺序变化或不存在,可能引发错误
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
print(root[2][2].tag,root[2][2].attrib,root[2][2].text)
输出结果为:
year {} 2008
3.7.2、遍历所有直接子节点(不区分标签名)
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
# 获取根节点的子节点
for i in root:print("根节点的子节点名称为", i.tag, "根节点的子节点的属性为", i.text)# 获取根节点的子节点的子节点for child_i in i:print("根节点的子节点的子节点名称为", child_i.tag, "根节点的子节点的子节点的属性为", child_i.text)
输出结果为:
根节点的子节点名称为 title 根节点的子节点的属性为 The Great Gatsby1
根节点的子节点名称为 name 根节点的子节点的属性为 The Great Gatsby2
根节点的子节点名称为 id 根节点的子节点的属性为 d根节点的子节点的子节点名称为 title 根节点的子节点的子节点的属性为 The Great Gatsby3
根节点的子节点的子节点名称为 rank 根节点的子节点的子节点的属性为 2
根节点的子节点的子节点名称为 year 根节点的子节点的子节点的属性为 2008
根节点的子节点的子节点名称为 gdppc 根节点的子节点的子节点的属性为 141100
根节点的子节点的子节点名称为 neighbor 根节点的子节点的子节点的属性为 None
根节点的子节点的子节点名称为 neighbor 根节点的子节点的子节点的属性为 None
根节点的子节点名称为 id 根节点的子节点的属性为 根节点的子节点的子节点名称为 rank 根节点的子节点的子节点的属性为 4
根节点的子节点的子节点名称为 year 根节点的子节点的子节点的属性为 2011
根节点的子节点的子节点名称为 gdppc 根节点的子节点的子节点的属性为 59900
根节点的子节点的子节点名称为 neighbor 根节点的子节点的子节点的属性为 None
3.7.3、使用.iter("tag")–遍历所有指定标签(包括子孙)
iterator = element.iter(tag=None)
- 作用:返回一个迭代器,用于遍历当前节点及其所有子孙节点中指定的标签(或所有标签)。
- 返回值:返回一个 迭代器(Iterator),可以用
for循环遍历它,每次返回一个Element对象。 - 参数
tag(可选):- 如果指定,比如
iter('item'),就只返回所有标签名为'item'的节点; - 如果为
None(或不传参数,即iter()),则返回 所有标签的所有节点。
- 如果指定,比如
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
# 获取根节点的子节点
for i in root:print("根节点的子节点名称为", i.tag, "根节点的子节点的属性为", i.text)print("_"*20)
for h in root.iter():print("根节点的子节点名称为", h.tag, "根节点的子节点的属性为", h.text)
返回结果:
根节点的子节点名称为 title 根节点的子节点的属性为 The Great Gatsby1
根节点的子节点名称为 name 根节点的子节点的属性为 The Great Gatsby2
根节点的子节点名称为 id 根节点的子节点的属性为 d根节点的子节点名称为 id 根节点的子节点的属性为 ____________________
根节点的子节点名称为 data 根节点的子节点的属性为 根节点文本信息 根节点的子节点名称为 title 根节点的子节点的属性为 The Great Gatsby1
根节点的子节点名称为 name 根节点的子节点的属性为 The Great Gatsby2
根节点的子节点名称为 id 根节点的子节点的属性为 d根节点的子节点名称为 title 根节点的子节点的属性为 The Great Gatsby3
根节点的子节点名称为 rank 根节点的子节点的属性为 2
根节点的子节点名称为 year 根节点的子节点的属性为 2008
根节点的子节点名称为 gdppc 根节点的子节点的属性为 141100
根节点的子节点名称为 neighbor 根节点的子节点的属性为 None
根节点的子节点名称为 neighbor 根节点的子节点的属性为 None
根节点的子节点名称为 id 根节点的子节点的属性为 根节点的子节点名称为 rank 根节点的子节点的属性为 4
根节点的子节点名称为 year 根节点的子节点的属性为 2011
根节点的子节点名称为 gdppc 根节点的子节点的属性为 59900
根节点的子节点名称为 neighbor 根节点的子节点的属性为 None
3.7.4、使用 .find("tag") —— 查找第一个匹配的子元素
- 作用:在当前 XML 节点的子节点中,查找第一个匹配上tag的子节点。
- 返回值:返回找到的第一个符合条件的
Element对象;如果没有找到,则返回None。 - 参数match:
- 可以是一个 标签名(如
'name'),表示查找第一个该标签的子节点; - 也可以是 XPath 表达式(有限支持),比如
'.//tag'(但完整强大的 XPath 支持需要用lxml库)。
- 可以是一个 标签名(如
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
find_str = root.find("id")
if find_str is not None:print(find_str, find_str.text)
else:print("未找到 email 节点")find_str2 = root.find("id12")
if find_str2 is not None:print(find_str2, find_str2.text)
else:print("未找到 email 节点")
返回结果为:
<Element 'id' at 0x0000021A1A7D6ED0> d未找到 email 节点
3.7.5、使用 .findall("tag") —— 查找所有匹配的子元素
element.find(match)
- 作用:在当前 XML 节点(
parent)的子节点中,查找返回所有tag相同的节点。 - 返回值:返回一个 列表(List),包含所有匹配的
Element对象;如果没有匹配项,则返回空列表[](不会返回None)。 - 参数match:
- 可以是一个 标签名(如
'item'),表示查找所有该标签的子节点; - 也可以是一个 简单的 XPath 表达式(有限支持),例如
'subtag'、'tag/subtag',甚至是带属性筛选的如'*[@attr="value"]'。
- 可以是一个 标签名(如
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
find_strs=root.findall("id")
for find_str in find_strs:print(find_str,find_str.tag,find_str.attrib)
输出结果为:
<Element 'id' at 0x00000248A97E2F20> id {'name': 'Liechtenstein'}
<Element 'id' at 0x00000248A97E31A0> id {'name': 'Singapore'}
3.7.6、访问子节点的属性(Attributes)
value = element.get(key, default=None)
- element 是一个 XML 的节点(Element 对象)。
- key是该节点上的一个属性(attrib)的名称(字符串)。
- value 是该属性对应的值。如果属性不存在,则返回 None,如果default配置了值,就返回default的值
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
find_strs=root.findall("id")
for find_str in find_strs:print(find_str,find_str.tag,find_str.attrib,find_str.get("name",default="unfind"))
输出结果为:
<Element 'id' at 0x0000016885ED2ED0> id {'name': 'Liechtenstein'} Liechtenstein
<Element 'id' at 0x0000016885ED3150> id {'name': 'Singapore'} Singapore
3.8、修改xml
对xml的修改包括:直接修改字段值如text、tag、attrib、tail;使用set函数增加或修改属性(attribute);使用append函数添加新的子元素;使用remove函数删除子元素。
3.8.1、直接修改元素字段值
通过循环遍历修改元素的值或者通过元素的索引修改元素的值,包括text、tag、attrib、tail
3.8.2、set增加或者修改属性
element.set(key, value)
- 作用:为当前的 XML 节点(
Element对象)设置一个属性(attribute),如果该属性已存在,则更新它的值;如果不存在,则新增该属性。set()只能修改或添加属性(attribute),不能修改节点的标签名、文本内容(.text)、或结构 - 参数:
key:属性名(字符串),例如'id'、'name'。value:属性值(字符串),例如'101'、'Alice'。
- 返回值:无(
None),但会直接修改该Element对象的属性。
3.8.3、append添加子元素
parent_element.append(child_element)
- 作用:将一个 XML 子节点(Element 对象) 添加为某个 父节点的最后一个子节点。
- 参数:
child_element必须是一个Element对象(即通过ET.Element()或ET.SubElement()创建的节点)。 - 返回值:无(
None),但会直接修改父节点,将子节点添加到其子节点列表中。 - 常见用途:在构建或修改 XML 结构时,往某个节点下添加新的子元素。
3.8.4、remove函数子元素
parent_element.remove(child_element)
- 作用:从 父节点(parent) 中移除指定的 子节点(child)。
- 参数:
child_element必须是一个 已经存在于父节点子节点列表中的Element对象。 - 返回值:无(
None),但会直接修改 XML 树结构,将该子节点删除。 - 常见用途:在动态修改 XML 数据时,删除不需要的节点,比如删除某个
<item>、<user>等。 - 注意:
- 你不能直接通过标签名或属性删除节点,必须先获取到该节点的
Element对象,然后调用其 父节点的.remove(child)方法。 - 如果节点不存在于父节点的子节点中,会抛出
ValueError。
- 你不能直接通过标签名或属性删除节点,必须先获取到该节点的
import xml.etree.ElementTree as ET# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()
for i in root.iter("title"):if i is not None:#遍历所有tag为title的父子孙元素修改texti.text = i.text + "alter"#遍历所有tag为title的父子孙元素修改和新增attribei.set("set_add", "set_add")i.set("id", "xml_set_a")append_add = ET.Element("append_add_tag", attrib={"append_add_att": "append_add"})
#在最后增加一个元素
root.append(append_add)
remove_Element = root[1]
# remove的入参必须是一个含有父节点的Element对象
root.remove(remove_Element)tree.write("alter_file.xml", encoding="UTF-8", xml_declaration=True)
生成的alter_file.xml内容如下
<?xml version='1.0' encoding='UTF-8'?>
<data shelf="New Arrivals" shelf2="add_a">根节点文本信息 <title id="xml_set_a" set_add="set_add">The Great Gatsby1alter</title>b e<id name="Liechtenstein">d<title id="xml_set_a" set_add="set_add">The Great Gatsby3alter</title><rank updated="yes">2</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E" /><neighbor name="Switzerland" direction="W" /></id>ad<id name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N" /></id>
<append_add_tag append_add_att="append_add" /></data>
3.9、高级使用
3.9.1、命名空间
<data xmlns:cat="http://example.com/catalog"xmlns:pub="http://example.com/publishers"><cat:book><cat:title>Python编程</cat:title><cat:price>89.00</cat:price></cat:book><pub:publisher><pub:name>O'Reilly</pub:name><pub:country>USA</pub:country></pub:publisher>
</data>
import xml.etree.ElementTree as ET
# 解析xml文件
tree = ET.parse("file.xml")
root = tree.getroot()# Step 2: 定义命名空间字典(自己定义前缀,值必须是 XML 中的 URI)
namespaces = {'cat': 'http://example.com/catalog','pub': 'http://example.com/publishers'
}# Step 3: 使用 find / findall 查找带命名空间的元素
# 获取书名,'cat:book/cat:title'能被正确解析,是因为你传入了 namespaces 参数,它知道 cat 代表哪个 URI。
title_elem = root.find('cat:book/cat:title', namespaces)
print("书名:", title_elem.text) # 输出:Python编程# 获取价格
price_elem = root.find('cat:book/cat:price', namespaces)
print("价格:", price_elem.text) # 输出:89.00# 获取出版社名称
publisher_name_elem = root.find('pub:publisher/pub:name', namespaces)
print("出版社:", publisher_name_elem.text) # 输出:O'Reilly
'cat'和'pub'是你自己定义的别名(前缀),可以随便取,但建议和 XML 中保持一致,便于理解。- 值
'http://example.com/catalog'等,是 XML 中通过xmlns:cat="..."定义的真实命名空间 URI,这个 必须与 XML 中完全一致(包括大小写和斜杠等)。
3.9.1、xpath
xml.etree.ElementTree 模块对XPath表达式在树中定位元素提供了有限的支持。它支持一小部分缩写语法,完整的XPath引擎不在模块的范围内。
四、lxml的使用
lxml 是 Python 中一个功能非常强大且广泛使用的第三方库,用于高效地解析、创建、修改和查询 XML 和 HTML 文档。它结合了 libxml2 和 libxslt 这两个 C 库的性能和功能,提供了 简单易用的 Python API,是处理 XML/HTML 的首选工具之一,尤其在需要处理复杂结构、XPath、XSLT、命名空间等场景时,比标准库(如 xml.etree.ElementTree)更加强大和灵活。
lxml库包含的子模块如下:
| 模块 | 用途 |
|---|---|
lxml.etree | 最常用的模块,用于处理 XML,提供类似 ElementTree 的 API,但功能更强大 |
lxml.html | 专门用于解析和操作 HTML(即使是脏 HTML 也能自动修复) |
lxml.objectify | 将 XML 转为 Python 对象,适合面向对象方式操作 XML |
lxml.cssselect | 支持使用 CSS 选择器查找节点(通常已内置) |
lxml.etree 是 lxml 库中的一个模块,提供了高效的 XML 解析、构建、查询和修改功能,不仅兼容 ElementTree 的 API,还扩展了大量高级功能。下面主要介绍etree库的功能。
4.1、解析XML
与ElementTree一样支持两种解析方式,从文件解析xml用parse(),从字符串解析xml用fromstring()。此外还支持使用XML解析器解析和HTML解析器。另外lxml中etree.XML()可以解析xml数据,他与etree.fromstring() 底层实现完全相同,只是etree.XML()为lxml特有,为了保持和etree.HTML()风格一致。
from lxml import etreexml_string = """
<bookstore><book category="cooking"><title lang="en">Everyday Italian</title><author>Giada De Laurentiis</author><year>2005</year><price>30.00</price></book>
</bookstore>
"""
# 方法1: 从字符串解析
root = etree.fromstring(xml_string) # 返回根元素# 方法2: 从文件解析
tree = etree.parse('books.xml') # 返回ElementTree对象
root = tree.getroot() # 获取根元素# 方法3: 从字符串解析,与fromstring()功能一样,只是XML()为lxml特有的方法
root = etree.XML(xml_string) # 返回根元素# 方法4: 使用XML解析器(更多控制选项)
parser = etree.XMLParser(remove_blank_text=True) # 移除空白文本
tree = etree.parse('books.xml', parser)
root = tree.getroot()
ElementTree在解析的时候默认会去掉xml中的注释,lxml.tree在解析的时候会将注释保留,它们会被解析为一种特殊的节点类型:lxml.etree._Comment。如果需要在解析后去掉注释,可以遍历所有节点,找到类型为 etree._Comment 的节点,然后将其从父节点中移除。
from lxml import etreexml = """
<root><!-- 注释1 --><a>内容A</a><!-- 注释2 --><b>内容B</b><!-- 注释3 -->
</root>
"""root = etree.fromstring(xml)# 找出并删除所有注释节点
comments = root.xpath('//comment()') # 使用 XPath 查找所有注释
for comment in comments:parent = comment.getparent()if parent is not None:parent.remove(comment)# 打印处理后的 XML(不含注释)
print(etree.tostring(root, pretty_print=True, encoding='unicode'))
4.2、查看Element 对象的属性
使用方式与ElementTree一致
from lxml import etreetree = etree.parse('file.xml')
root = tree.getroot()
print("根节点的text为",root.text)
print("根节点的属性attrib为",root.attrib) #attrib可以存在多个,所以输出是一个dict
print("根节点的节点名tag为",root.tag)
print("根节点的附加文本tail为",root.tail)
print("根节点的子节点列表为",root[:])
4.3、创建xml
使用方式与ElementTree一致,只是tostring()和write()支持带上参数pretty_print=True,将xml文件 格式化,带缩进,易读
from lxml import etree# 创建根元素
root = etree.Element("catalog")# 创建子元素
book = etree.SubElement(root, "book")
title = etree.SubElement(book, "title")
title.text = "Python编程"# 添加属性
book.set("id", "001")# 输出格式化 XML
print(etree.tostring(root, pretty_print=True, encoding='unicode'))#写入文件
tree.write("output.xml", pretty_print=True, encoding='utf-8')
4.4、查询xml
使用方式与ElementTree一致
book = root.find('book') # 查找第一个 book 子元素
# 遍历直接子元素
for child in root:print(f"子元素: {child.tag}, 属性: {child.attrib}")# 使用iter()方法遍历特定元素
for book in root.iter('book'):print(f"找到书: {book.attrib}")# 使用get()方法安全获取属性
category = book.get('category', 'unknown') # 如果属性不存在,返回'unknown'
4.5、修改xml
使用方式与ElementTree一致
4.6、命名空间
使用方式与ElementTree一致
4.7、XPath查询与高级搜索(核心功能)
lxml.etree 最强大的功能之一就是对 XPath 的原生支持,可以非常灵活地查找和提取节点。
nodes_or_values = element.xpath("XPath表达式")
- 功能:是
lxml.etree提供的一个方法,用于在 XML/HTML 节点上执行 XPath 查询,返回匹配的节点或数据列表。 - 入参:传入一个 XPath 表达式
- 返回值:Python 列表(list),列表中的元素可能是:
- XML 节点(
lxml.etree._Element),比如你查询了//person - 字符串,比如你用了
text(),如//name/text() - 数字(浮点或整数),如果你用了
number()或 XPath 计算 - 布尔值(较少用)
- 如果没匹配到任何内容,返回 空列表
[](不会报错)
- XML 节点(
常见 XPath 语法:
| 表达式 | 说明 |
|---|---|
//tag | 任意层级下的 tag |
/tag | 直接子节点 |
tag/text() | 获取该标签的文本内容 |
tag[@attr] | 有某个属性的 tag |
tag[@attr="value"] | 属性等于某个值 |
* | 任意标签 |
.. | 父节点 |
//tag[number(attr) > 10] | 属性值转为数字并比较(XPath 1.0) |
//comment() | 选择所有注释节点(lxml 支持) |
//tag[contains(@attr, 'xxx')] | 属性包含某字符串 |
//tag[position() < 3] | 选择前几个元素 |
调用 .xpath() 方法进行查询
from lxml import etreexml_data = """
<root><person id="1"><name>Alice</name><age>30</age></person><person id="2"><name>Bob</name><age>25</age></person><person id="3"><name>Charlie</name><age>35</age></person>
</root>
"""# 解析 XML
root = etree.fromstring(xml_data)
1. 选择所有 <person> 节点
persons = root.xpath("//person")
print("找到了 %d 个 person 节点" % len(persons))
for p in persons:print(etree.tostring(p, pretty_print=True, encoding='unicode'))
输出结果:
找到了 3 个 person 节点
<person id="1"><name>Alice</name><age>30</age></person><person id="2"><name>Bob</name><age>25</age></person><person id="3"><name>Charlie</name><age>35</age></person>
//person 是 XPath 表达式,表示选取文档中任意位置的 <person> 元素。
2. 获取所有 <name> 节点的文本内容
names = root.xpath("//name/text()")
print("所有姓名:", names) # 输出: ['Alice', 'Bob', 'Charlie']
//name/text():选取所有 <name> 节点,并提取它们的文本内容。
3. 获取 id="2" 的 person 的姓名
name_of_id2 = root.xpath('//person[@id="2"]/name/text()')
print("ID为2的人的名字:", name_of_id2[0]) # 输出: Bob
//person[@id="2"]:选取具有属性 id="2" 的 <person> 节点。
4. 获取所有人的 ID 属性
ids = root.xpath("//person/@id")
print("所有人的ID:", ids) # 输出: ['1', '2', '3']
//person/@id:选取所有 <person> 节点的 id 属性值,返回字符串列表。
5. 获取年龄大于 30 的人的姓名
older_names = root.xpath('//person[number(age) > 30]/name/text()')
print("年龄大于30的人:", older_names) # 输出: ['Charlie']
五、xml和json的转换
xmltodict 是一个非常实用的 Python 库,它可以将 XML 数据转换成 Python 的字典(dict),也可以将字典转换回 XML
json库可以将json字符串与python的数据类型相互转换
| XML → Dict | xmltodict.parse(xml_string) | 把 XML 字符串解析为嵌套字典,支持多节点转列表 |
|---|---|---|
| Dict → XML | xmltodict.unparse(data_dict) | 把字典转换回格式化的 XML 字符串 |
| dict→ json | json.dumps(obj) | 将python数据类型转换为json格式的字符串。 |
| json→ dict | json.loads(s) | 将json格式的字符串转换为python的类型。 |
5.1、将xml文件转换为json
<?xml version="1.0"?>
<data shelf="New Arrivals" shelf2="add_a"><title id="0">The Great Gatsby1</title>be<name name="0">The Great Gatsby2</name>c<id name="Liechtenstein">d<title id="123">The Great Gatsby3中文跟</title><rank updated="yes">2</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"/><neighbor name="Switzerland" direction="W"/></id>ad<id name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"/></id>
</data>
import json
import xmltodictdef xml_to_json(xml_str):"""parse是的xml解析器,参数需要:param xml_str: xml字符串:return: json字符串"""xml_parse = xmltodict.parse(xml_str)# json库dumps()是将dict转化成json格式,loads()是将json转化成dict格式。# dumps()方法的ident=1,格式化jsonjson_str = json.dumps(xml_parse, indent=1, ensure_ascii=False)return json_strXML_PATH = './xml2json.xml' # xml文件的路径
# 读取文件是需要带上编码,防止存在中文报错
with open(XML_PATH, 'r', encoding='utf-8') as f:xmlfile = f.read()with open(XML_PATH[:-3] + 'json', 'w') as newfile:newfile.write(xml_to_json(xmlfile))
执行后的结果为在当前目录下生成一个与xml文件名称相同的json文件,文件内容为:
{"data": {"@shelf": "New Arrivals","@shelf2": "add_a","title": {"@id": "0","#text": "The Great Gatsby1"},"name": {"@name": "0","#text": "The Great Gatsby2"},"id": [{"@name": "Liechtenstein","title": {"@id": "123","#text": "The Great Gatsby3中文跟"},"rank": {"@updated": "yes","#text": "2"},"year": "2008","gdppc": "141100","neighbor": [{"@name": "Austria","@direction": "E"},{"@name": "Switzerland","@direction": "W"}],"#text": "d"},{"@name": "Singapore","rank": "4","year": "2011","gdppc": "59900","neighbor": {"@name": "Malaysia","@direction": "N"}}],"#text": "b\n ec\n ad"}
}
5.2、将json文件转换为xml
{"data": {"@shelf": "New Arrivals","@shelf2": "add_a","title": {"@id": "0","#text": "The Great Gatsby1"},"name": {"@name": "0","#text": "The Great Gatsby2"},"id": [{"@name": "Liechtenstein","title": {"@id": "123","#text": "The Great Gatsby3"},"rank": {"@updated": "yes","#text": "2"},"year": "2008","gdppc": "141100","neighbor": [{"@name": "Austria","@direction": "E"},{"@name": "Switzerland","@direction": "W"}],"#text": "d"},{"@name": "Singapore","rank": "4","year": "2011","gdppc": "59900","neighbor": {"@name": "Malaysia","@direction": "N"}}],"#text": "根节点文本信息\n b\n ec\n ad"}
}
import xmltodict
import jsondef json_to_xml(python_dict):"""xmltodict库的unparse()json转xml:param python_dict: python的字典对象:return: xml字符串"""xml_str = xmltodict.unparse(python_dict)return xml_strJSON_PATH = './json2xml.json' # json文件的路径
with open(JSON_PATH, 'r',encoding='utf-8') as f:jsonfile = f.read()python_dict = json.loads(jsonfile) # 将json字符串转换为python字典对象with open(JSON_PATH[:-4] + 'xml', 'w',encoding="utf-8") as newfile:newfile.write(json_to_xml(python_dict))
执行后的结果为在当前目录下生成一个与xml文件名称相同的json文件,文件内容为:
<?xml version="1.0" encoding="utf-8"?>
<data shelf="New Arrivals" shelf2="add_a"><title id="0">The Great Gatsby1</title><name name="0">The Great Gatsby2</name><id name="Liechtenstein"><title id="123">The Great Gatsby3</title><rank updated="yes">2</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"></neighbor><neighbor name="Switzerland" direction="W"></neighbor>d</id><id name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"></neighbor></id>根节点文本信息becad</data>
六、lxml解析html
lxml解析html的API与解析xml相同,解析HTML的三种方式,其他增删改查一样
# 方法1: 从字符串解析
root = etree.fromstring(xml_string) # 返回根元素# 方法2: 从文件解析
html_tree = etree.parse('page.html')
html_root = html_tree.getroot()# 方法3: 解析HTML。入参只能是str或者byte
html_tree = etree.HTML(xml_string,parser=None)html_parser = etree.HTMLParser()
html_tree1 = etree.HTML(xml_string,parser=html_parser)
参考:
https://blog.csdn.net/weixin_36279318/article/details/79176475
https://docs.python.org/3.14/library/xml.etree.elementtree.html#
