Python:处理XML文件

Python XML解析


什么是XML

1、XML指可扩展标记语言(eXtensible Markup Language)

2、XML 设计用来传输和存储数据

3、XML是一种允许用户对自己的标记语言进行定义的源语言

 

XML语法规则

1、所有的元素都必须有开始标签和结束标签,省略结束标签是非法的(在HTML中结束标签是可以省略的)

2、大小写敏感,大小写不一致时表示两个不同的标签

3、xml文档必须有根元素

4、XML必须正确嵌套,父元素必须完全包住子元素

5、XML属性值必须加引号,元素的属性值都是一个键值对形式

 

XML命名规则

1、名称可以包含字母、数字以及其他字符

2、名称不能以数字或标点符号开头

3、名称不能以字母xml或XML开始

4、名称不能包含空格,但可以使用任何名称,没有保留字

5、名称应该具有描述性,简短和简单,可以同时使用下划线。

6、避免“-”、“.”、“:”等字符

7、Xml的注释格式:

 

 

Python对XML的解析

Python 有三种方法解析XML,SAX,DOM,以及ElementTree
    ⑴SAX:Python标准库包含SAX解析器,SAX用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件
    ⑵DOM:将XML数据在内存中解析成一个树,通过对树的操作来操作XML
    ⑶ElementTree:一个轻量级的DOM,具有方便友好的API。代码可用性好,速度快,消耗内存少

注:
1、DOM需要将XML数据映射到内存中的树,一是比较慢,二是比较耗内存,

2、SAX流式读取XML文件,比较快,占用内存少,但需要用户实现回调函数

3、ElementTree具有方便友好的API。代码可用性好,速度快,消耗内存少。因此我就只了解了这种解析方式


例1:XML示例


 
    
   
    1  
    2008  
    141100  
      
     
    
   
    4  
    2011  
    59900  
     
   

注:它有如下特
1、首先,它是有标签对组成,例如:

2、标签可以有属性:

3、标签对可以嵌入数据:abc

4、标签可以嵌入子标签(具有层级关系)

5、在这个例子中data标签就是跟标签(元素):它是最外层的标签,其里面包含了各种子标签、孙标签等

6、当然标签对的属性、值(标签对中间的数据),是可以选择没有的,没有值的标签表示空标签对

7、xml文件可以理解为一个树结构,有根结点,子结点的概念。每个结点元素(element)包含结点名(tag)、属性值(atrrib)和文本(text)

 

Python解析HTML文件

1、因为前面学习了下Python解析HTML文件。Python解析HTML文件使用的BeautifulSoup库。这个库前面有专门的介绍,并且当时也说过也可以使用这个库来解析XML文档的

2、其实我感觉有时使用BeautifulSoup库来解析XML文件比使用ElementTree库来解析XML方便点。只是说BeautifulSoup库在处理的XML文档可能跟XML规则有点出入。比如使用BeautifulSoup库创建XML时会出现省略结束标签的问题

3、所以现在又专门来了解了下使用ElementTree库来解析XML,毕竟这个库是专门用来解析XML文件的

例2:使用BeautifulSoup库解析XML

from bs4 import BeautifulSoup

html = """

 
    
   
    1  
    2008  
    141100  
      
     
    

"""

soup = BeautifulSoup(html,'xml')    #使用xml解析器,将一个文件或字符串转为BeautifulSoup对象
#print(type(soup)) #返回一个
#print(soup.prettify()) #格式化输出HTML文件

tag_year = soup.find_all("year") #find_all()方法返回文档中全部的year标签组成的列表
print("year标签对有:",tag_year)
for i in tag_year:
    print("返回的a标签类型为:",type(i))#返回的是一个字符串型的Tag对象,可以直接使用str()方法进行强转换
    print(i.name)   #通过Tag对象的name属性来获得标签的名字
    print(i.attrs)  #通过Tag对象的attrs属性来获得标签的属性(为属性名与属性值组成的字典)
    print(i.string) #通过Tag对象的string属性来获得标签对中的数据(值)

"""
year标签对有: [2008]
返回的a标签类型为: 
year
{'type': 'year'}
2008
"""

注:
1、其实只是简单的解析下THML文档或XML文档的话,这个例子中的这些方法或属性应该就够了,感觉平时用到最多的也就只有这么几个
    ⑴BeautifulSoup():用来创建一个BeautifulSoup对象。这个是必须的,要解析的话,就必须将字符串HTML文档转为BeautifulSoup对象
    ⑵find_all():用来搜索文档中所有符合条件的标签对(一个tag对象)。这里是根据标签名来进行搜索的
    ⑶Tag对象的name属性:返回指定Tag对象的标签名。这个意义不大,因为这里本来就是根据标签名来搜索的
    ⑷Tag对象的attrs属性:返回指定Tag对象的属性,返回值为一个字典
    ⑸Tag对象的string属性:返回指定Tag对象的值(标签对之间的数据)

2、感觉整个解析过程就是soup对象->Tag对象->通过tag对象的name、attrs(string)属性来获取想要的

3、感觉使用ElementTree库来解析XML也是这个过程:一层一层的找下去,首先就是找到想要的标签对。只是说相对于使用BeautifulSoup库来解析有点差别

 

 

使用ElementTree解析XML文件

 

概述

1、XML树和元素:XML是一种固有的分层数据格式,最自然的表示方法是使用树。 ElementTree为此,有两个类:ElementTree和Element

2、在利用ElementTree解析XML文件的时候,有两个对象我们会用到:ElementTree和Element
    ⑴ElementTree代表了整个XML文档(将整个XML文档表示为树)
    ⑵Element代表了文档中的每个节点数据(表示此树中的单个节点)
    ⑶与整个文档的交互(读/写文件)通常在该ElementTree级别上进行。与单个XML元素及其子元素的交互在Element级别上完成
    ⑷当我们对XML文档进行解析时,主要考虑Element节点,通常一个节点具有以下属性:节点名称、节点属性、节点的文本、节点的子节点

 

 

ElementTree解析XML文件的过程

1、导入ElementTree

2、解析Xml文件找到根节点

2.1、解析字符串,root = ET.fromstring(country_data_as_string)

3、遍历根节点可以获得所有子节点,然后就可以根据需求拿到需要的字段了

注:
1、第三步是一次性获得根节点下所有的子节点,可能在实际是我们只需要其中的一部分节点的数据。特别是当XML文件较大或者其中的子节点tag非常多的时候,这样做就很不合适了

2、所以感觉更多的时候还是先找到符合条件的节点,然后再获得该节点的数据。而不是说一次性就获得全部节点的数据,然后再去查找我们需要的节点及其数据

 

解析:获得ElementTree对象

ElementTree模块提供了两种方式来获得ElementTree对象
    ⑴通过加载XML文件:使用parse()方法
    ⑵通过加载字符串型的XML:使用fromstring()方法

例3:加载XML文件

from xml.etree import ElementTree

tree = ElementTree.parse("F:\\test.xml") #加载xml文件
print(tree) #返回一个ElementTree对象,对应整个XML

root = tree.getroot() #获取根节点
print(root)  #返回一个Element对象(将整个xml看出一个节点)

"""


"""


例3_1:

from xml.etree import ElementTree

xml = """
 
    
   
    1  
    2008  
    141100  
      
     
    

"""
tree = ElementTree.fromstring(xml)
print(tree)   #直接返回一个Element对象:获取根节点

#

注:
1、通过上面例子可以看出:通过两种方式来加载XML文件还是有点区别的
    ⑴使用parse()方法加载XML文件,首先获得的是一个ElementTree对象,然后还需要通过getroot()才能获得根节点
    ⑵使用fromstring()方法加载XML的话,就是直接获得一个Element对象,这个对象也就是根节点

2、fromstring()将XML从字符串直接解析为Element,这是解析树的根元素。其他解析功能可能会创建一个ElementTree

例3_2:

from xml.etree import ElementTree

xml = """
 
    
   
    1  
    2008  
    141100  
      
     
    

"""
tree = ElementTree.fromstring(xml)
print(ElementTree.tostring(tree,encoding="unicode"))   #使用tostring()方法,来将一个Element对象转为字符串

"""
b' \n    \n   \n    1  \n    2008  \n    141100  \n      \n     \n    \n'
"""

注:
1、Element对象对应XML文件中的节点,这个对象是不能使用str()方法直接转为字符串型的。需要借助tostring()方法:来将一个Element对象转为字符串,并且还可以使用encoding参数来执行转换后的编码

2、这一点的话跟BeautifulSoup库有很大的区别:BeautifulSoup库获得一个节点后,其直接返回的是一个字符串型的Tag对象,并且可以直接使用str()方法进行强制转换

3、作为一个Element对象,本身是具有子元素,因此可以直接对Element进行迭代取值

例3_3:

from xml.etree import ElementTree

xml = """
 
    
   
    1  
    2008  
    141100  
      
     
    

"""
tree = ElementTree.fromstring(xml)
for child in tree:
    print(child.tag, child.attrib,child.text)
    
"""
time {} 20200706
country {'name': 'Liechtenstein'}  
    
"""

注:
1、这种遍历的话,只会遍历出当前节点的子节点:这个例子中使用根节点遍历的,根节点(data标签)下只有time和country两个子节点

2、这个例子中涉及到了Element对象的属性:tag、attrib、text,这三个属性后面具体介绍

3、country标签对中没有具体的值,其标签对之间是嵌套的其他的标签对,因此在使用text属性获得其值时,返回的是空格

4、记住一句话:在使用ElementTree库来解析XML(获得标签)时,始终是一层一层的往下找的,只有找到上一层后才能找到下一层
    ⑴这里也能BeautifulSoup库也有很大的区别:BeautifulSoup库是通过find()等方法查找标签时,不管标签在第几层,都能直接找到
    ⑵使用ElementTree库来解析XML的流程基本上也是:加载XML文件,找到根节点->根据根节点一层一层的往下找,直到直到我们需要的节点(标签)

5、当然,这个例子中我们是遍历的根节点,以此来获得根节点下的子节点的。如果我们想要找其他节点,如根节点下的孙节点,那该怎么办呢
    ⑴继续对子节点进行遍历:这种感觉应该也可以,不管应该很麻烦,就没尝试
    ⑵ElementTree库提供了find()和findall()方法来查找指定的标签节点

 

 

Element中的遍历与查询

find()方法

1、Element.find(path):查找当前元素下tag或path能够匹配的首个直系节点

2、一般的话,都是根据节点名字去找节点的:find(节点名字)
例4:

xml = """

    
    
        1
        2008
        141100
        
        
    
    
        4
        2011
        59900
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
#print(tree)
time =ElementTree.tostring(tree.find("time"),encoding="unicode")  #使用tostring()方法返回字符串型的标签对
print(time)
country  = tree.find("country")  #存在多个相同的节点时,返回第一个节点
print(country)

"""


"""

注:
1、find(path):查找当前元素下tag或path能够匹配的首个直系节点

2、在查找节点时,不能跳着去找,不然会找不到,只能一层一层的往下找

3、总结的来说整个流程就是:加载XML文件->获得根节点->根据根节点一层一层的往下找直到找到我们需要的节点->根据节点属性(Element对象的属性)来获取我们想要的数据

例4_1:

xml = """

    
    
        1
        2008
        141100
        
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
#print(tree)
year  = tree.find("year")  #tree是根节点,year是其孙节点:跳过父节点直接去找孙节点是找不到的
print(year)

country = tree.find("country") #先找到父节点,在根据父节点去找其下的子节点
year = country.find("year")
print(year)
"""
None

"""

 

findall()方法

1、Element.findall(path):查找当前元素下tag或path能够匹配的直系节点

2、存在多个相同的子节点时,返回的是一个所有子节点组成的列表

例5:

xml = """

    
    
        1
        2008
        141100
        
        
    
    
        4
        2011
        59900
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
#print(tree)
time = tree.findall("time")
print(time)  #f返回的是一个Element对象列表
for i in time:
    print(ElementTree.tostring(i,encoding="unicode"))  #使用tostring()方法返回字符串型的标签对

country  = tree.findall("country") #存在多个相同的节点时,返回的是一个列表
print(country)

"""
[]

[, ]
"""


例5_1:

xml = """

    
    
        1
        2008
        141100
        
        
    
    
        4
        2011
        59900
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
rank  = tree.findall("rank") #tree为根节点,rank为它的孙节点,也不能跳过父节点去找
print(rank)

country = tree.findall("country")[0] #返回的是一个列表,因此可以使用索引
rank  = country.findall("rank") 
print(rank)
"""
[]
[]
"""

注:
与find()方法一样:findall()方法也不能跳着找

 

iter()方法

Element.iter(tag=None):遍历该Element所有后代,也可以指定tag进行精确寻找

例6:

xml = """

    
    
        1
        2008
        141100
        
        
    
    
        4
        2011
        59900
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
country = tree.findall("country")[0] #返回的是一个列表,因此可以使用索引

tags  = country.iter()   #返回某个节点下的所有子节点也包含节点本身
for i in tags:
    print(i)
    print(i.tag,i.attrib,i.text)
    
"""

country {'name': 'Liechtenstein'} 
        

rank {} 1

year {} 2008

gdppc {} 141100

neighbor {'direction': 'E', 'name': 'Austria'} None

neighbor {'direction': 'W', 'name': 'Switzerland'} None
"""

 

 

Element对象属性

1、Element对象提供了三种属性来分别获取一个Element对象(节点)的标签名、属性、值(标签对键的数据)

2、其实我们获取一个标签对象,最主要的还是想获得这个标签的属性、值(标签对键的数据):text、attrib

3、方法:Element对象.属性名

属性名 说明    
tag  标签
text  获得标签对之间的内容(当标签无值时返回None)
attrib   获取标签中的属性和属性值

例7:

xml = """

    
    
        1
        2008
        141100
        
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
rank = tree.findall("country")[0].find("rank") #返回的是一个列表,因此可以使用索引
print("当前标签为:",rank)
print("标签的名字为:",rank.tag)  #返回标签的名字,类型为字符串
print("标签的属性为:",rank.attrib)    #返回标签的属性,类型为字典
print("标签的值为:",rank.text)   #返回标签的值,类型为字符串

"""
当前标签为: 
标签的名字为: rank
标签的属性为: {'type': 'number'}
标签的值为: 1
"""

 

针对属性的操作

前面说到标签的属性返回的是一组字典,因此可以使用字典的方式来对标签的属性进行一些操作

方法 说明
clear() 清空元素的后代、属性、text和tail也设置为None
get(key, default=None) 获取key对应的属性值,如该属性不存在则返回default值
items()  根据属性字典返回一个列表,列表元素为(key, value)
keys()   返回包含所有元素属性键的列表
set(key, value)  设置新的属性键与值

例8:

xml = """

    
    
        1
        2008
        141100
        
        
    

"""
from xml.etree import ElementTree

tree = ElementTree.fromstring(xml)
rank = tree.findall("country")[0].find("rank") #返回的是一个列表,因此可以使用索引
rank_attrib = rank.attrib
print("标签的属性为:",rank_attrib)    #返回标签的属性,类型为字典
print(rank_attrib.items())


"""
标签的属性为: {'type': 'number'}
dict_items([('type', 'number')])
"""

 

 

创建xml文档

1、要使用ElementTree库来创建一个XML对象(文件)的话,主要会用到:Element()和SubElement()方法
    ⑴Element()方法:用于创建一个根节点
    ⑵SubElement()方法:用于创建一个节点下的子节点

2、同样的,在创建XML文件时,也是需要一层一层的往下创建:创建跟节点->创建第一层节点->创建第二层节点....


例9:函数源码

def SubElement(parent, tag, attrib={}, **extra):
    attrib = attrib.copy()
    attrib.update(extra)
    element = parent.makeelement(tag, attrib)
    parent.append(element)
    return element

注:
Element()方法和SubElement()方法的源码类似,上面是SubElement()方法的源码
    ⑴Element()方法:因为是创建的根元素,它是第一层标签对,因此它的第一个参数,就直接是"标签名",第二个参数是标签的属性字典
    ⑵SubElement()方法:用于创建某个标签下的子标签,因此它的第一个参数时"父标签名",第二个参数才是要创建的标签名,第三个参数时标签的属性字典
    ⑶可以使用Element对象的text属性来为标签对添加值(标签对之间的数据)
    ⑷注:SubElement()方法的第一个参数必须是一个Element对象
    ⑸使用Element()方法和SubElement()方法创建一个标签后返回的都是一个Element对象,可以使用这个标签对象来创建其的子标签

例10:

from xml.etree import ElementTree
from xml.dom import minidom

#生成跟节点
root = ElementTree.Element("MSG",attrib={"xmlns:xsd":"http://www.w3.org/2001/XMLSchema"})
# 生成第一个子节点 head
print(root)
meta = ElementTree.SubElement(root, "META") #在"MSG"标签下创建一个子节点
print(meta)
xml_type = ElementTree.SubElement(meta,"TYPE")#在"META"标签下创建一个子节点
xml_type.text = "LADC"  #使用text属性来为标签对添加值

data = ElementTree.SubElement(root, "data") 
time = ElementTree.SubElement(data,"time")
time.text = "20200706"

country = ElementTree.SubElement(data,"country",attrib={"name":"Liechtenstein"})
rank = ElementTree.SubElement(country, "rank")
rank.text = "1" #值只能用字符串型的数字

country2 = ElementTree.SubElement(data,"country")
year = ElementTree.SubElement(country2, "year").text = "2011"#也可以链式赋值


#tree = ElementTree.ElementTree(root)
#tree.write('result.xml', encoding='utf-8') #将生成的XML写入文件
xml_string = ElementTree.tostring(root,short_empty_elements=True)
print(xml_string)
#直接将生成的Element对象转为字符串的话,不会有xml的头部"",因此需要minidom模块
tree = minidom.parseString(xml_string)
xml_string = tree.toxml()
print(xml_string)

"""


b'LLDM12011'
LLDM12011
"""


例10_1:

from xml.etree import ElementTree
from xml.dom import minidom
import json

class CreateXMLMsg():
    def __init__(self):
        pass

    def CreateRootTag(self,tagName="MSG"):
        """
        创建根节点
        :param tagName:根节点名称
        :param tagAttrib: 根节点属性
        :return: 根节点Element对象
        """
        root = ElementTree.Element(tagName)
        return root

    def CreateSecondFloorTag(self,parentTag,childTags):
        """
        一次性的创建同一层的xml标签:用于创建第二层标签
        :param parentTag:父节点的Element对象:CreateRootTag()方法返回的root
        :param childTags:子节点的标签名、值(标签对之间的数据),以字典形式传入(标签名:标签值)
        :return:None
        """
        for childTagName, childTagValue in childTags.items():
            childTag = ElementTree.SubElement(parentTag, childTagName)
            childTag.text = childTagValue
        return None

    def CreateThirdFloorTag(self,root,tagName,childTags,index=0):
        """
        创建某一个标签下的子标签:用于创建第三层及其之后的标签,并返回其父标签的Element对象
        :param root:要创建的标签的父标签的Element对象
        :param tagName:在哪个标签下创建子标签(标签名字符串)
        :param childTags: 子标签名和值,以字典形式传入(标签名:标签值)
        :param index:当存在多个相同标签对时,用于索引出想要的标签
        :return:创建的节点的父节点的Element对象,用于继续创建下一层标签
        """
        tag = root.findall(tagName)[index] #查找要创建的节点的父节点的Element对象
        for childTagName, childTagValue in childTags.items():
            childTag = ElementTree.SubElement(tag, childTagName)
            childTag.text = childTagValue
        return tag

    def SetAttrib(self,root,tagName,tagAttrib,index=0):
        """
        设置标签属性
        :param root: 已存在的Element对象(CreateRootTag()方法返回的root)
        :param tagName: 给哪个标签设置属性(标签名字符串)
        :param tagAttrib: 标签属性字典(属性名:值)
        :param index: 当存在多个相同标签对时,用于索引出想要的标签
        :return: None
        """
        tag = root.findall(tagName)[index]
        for attribKey, attribValue in tagAttrib.items():
            tag.set(attribKey, attribValue )
        return None

    def XmlToString(self,root):
        """
        将创建的Element对象对象转为字符串
        :param root:
        :return:
        """
        xml_string = ElementTree.tostring(root,encoding="unicode")
        tree = minidom.parseString(xml_string)
        xml_string = tree.toxml()
        return xml_string

if __name__ == "__main__":
    createMsg = CreateXMLMsg()

例10_2:

from demo.AOC.Common.CreateMsg import CreateXMLMsg
from xml.etree import ElementTree
import datetime

createXml = CreateXMLMsg()

#创建根节点(第一层节点)
root = createXml.CreateRootTag("MSG")

#创建第二层节点
secondTag = {"Header":None,"Body":None}
createXml.CreateSecondFloorTag(root,secondTag)

#创建第三层节点:其父节点"Body"是第二层(在根节点下一层),因此使用findall()方法时可以直接找到
flightDate = datetime.datetime.now().strftime("%Y/%m/%d")
thirdTag = {"NUMBER":"HJ222","Date":flightDate,"Dep":"TGH","Arr":"EFG","ProcessNode":None}
tag_Body = createXml.CreateThirdFloorTag(root,"Body",thirdTag)
print(tag_Body.tag)

#创建第四层节点:其父节点是"ProcessNode",位于第三层,因此需要先找到第二层的节点才能找到它。第二层的Element对象通过CreateThirdFloorTag()方法返回
fourthTag= {"Node1":None}
tag_ProcessNode = createXml.CreateThirdFloorTag(tag_Body,"ProcessNode",fourthTag)

#创建第五层节点:其父节点是"Node1",位于第四层,因此需要先找到第三层的节点才能找到它。第三层的Element对象通过CreateThirdFloorTag()方法返回
fifthTag = {"ProcessNodeName":"垃圾车","ProcessNodeTime":None}
tag_Node1 = createXml.CreateThirdFloorTag(tag_ProcessNode,"Node1",fifthTag)

#创建第六层节点
sixTag = {"data1":None}
tag_ProcessNodeTime = createXml.CreateThirdFloorTag(tag_Node1,"ProcessNodeTime",sixTag)

#创建第七层节点
time = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
sevenTag = {"time":time}
tag_data1 = createXml.CreateThirdFloorTag(tag_ProcessNodeTime,"data1",sevenTag)

#输出创建的xml字符串
xml = createXml.XmlToString(root)
print(xml)

注:
ElementTree模块还有很多方法,这里只是介绍了其中很少的一部分

 


 

你可能感兴趣的:(python3)