python之lxml处理xml

    学习过程中遇到了通信的报文为xml的的消息体,将通讯的内容依附于xml的载体进行传输,开始尝试使用包括ElementTree等在内的诸多库,但是因为一些处理皆不尽人意,最后选择了lxml库,该库无论处理速度还是函数功能封装基本可以满足需求。因为lxml不是Python自带的标准库,因此需要自己安装

pip3 install lxml

对于读取xml常见的有两种方式,一种是xml的字符串,即字符串的内容是xml文件,另一种是工程中包含xml文件,读取xml后进行操作,由于实际运用中一般不会直接读取一段字符串的形式(自己遇到的),都是以xml文件的形式进行读取,因此这里也以也是先读取文件,再操作文件。这里以  intuit.xml  文件为例

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Intuit xmlns="http://schema.intuit.com/finance/v3" time="2016-10-14T10:48:39.109-07:00">
    <QueryRequest startPosition="1" maxResults="79" totalCount="79">
        <Bill domain="QBO" sparse="false">
            <Id>=1</Id>
			<name>zhagsan</name>
            <MsgId>20200406</MsgId>
        </Bill>
    </QueryRequest>
    <QueryResponse startPosition="1" maxResults="79" totalCount="79">
        <Bill domain="QBO" sparse="false">
            <Id>=2</Id>
			<name>lisi</name>
            <responseId>20200407</responseId>
        </Bill>
    </QueryResponse>
</Intuit>

在xml文件的开头明显的发现不同于常规的xml文件,因为携带了命名空间xmlns,因此与没有命名空间的xml相比每一个节点前都用命名空间,如下:

# 读取xml文件
xml = etree.parse("Intuit.xml")
root = xml.getroot() #获取根节点
# 获取命名空间
ns = {'x':root.nsmap[None]}
print(ns)
{'x': 'http://schema.intuit.com/finance/v3'}

xml中每一个节点通常有3个特性,分别是标签tag,属性attrib,文本text,因此为了获取某一个节点的以上特性,需要查找获取到需要的结点,常用的方法可以是遍历查找,一种是遍历所有,另一种是遍历某个节点

print("遍历所有节点:")
for node in root.iter():
    print(node.tag)

print("遍历指定节点:")
ns = "{http://schema.intuit.com/finance/v3}"
for node in root.iter(f"{ns}name"):
    print(node.tag)

输出结果为:

当然还有一些其他的查找方法如find,findall,具体使用方法在这里不再赘述,参考xml文档

常见的还有获取属性的函数如:

#获取属性
print(root.items()) #获取全部属性和属性值
print(root.keys())  #获取全部属性
print(root.get('version', '')) #获取具体某个属性

此外使用xpath操作xml是非常常用且重要的功能的,自己在学习中希望的是知道某个节点tag,去修改text值,然后发送修改后的xml文件的消息到服务器,对于xpath,其他的文章已有介绍。对于没有命名空间的可以通过相对路径或者绝对路径进行访问:

#通过相对路径
root.xpath('//name')

#通过绝对路径
root.xpath('Intuit/QueryResponse/name')

但这里自然是访问不了的,因为有命名空间的限制,因此有命名空间的话:

# 导入库
import lxml.etree as etree

# 读取xml文件
xml = etree.parse("Intuit.xml")
root = xml.getroot() #获取根节点

# 获取命名空间
ns = {'x':root.nsmap[None]}
node = root.xpath("//x:name",namespaces=ns)
print(node)

输出结果:

[<Element {http://schema.intuit.com/finance/v3}name at 0x263aa0d4d88>, <Element {http://schema.intuit.com/finance/v3}name at 0x263aa0d4e88>]

注意这里输出的结果是 list,因此比如需要修改的是  QueryResponse/Bill/name这个标签的性质,可以使用

node[1].text = "4"

当然还可以通过绝对路径进行修改

node = root.xpath("//x:Intuit//x:QueryResponse//x:Bill//x:name",namespaces=ns)

其实我们也发现使用绝对路径貌似有点sha哈,一个是路径深度太深,第二个是每一个结点都需要加上//x:*使用起来非常麻烦,因此如果能唯一定位,比如MsgId或者是responseId这样最方便,但是有些结点重复且深度较深。对于结点的定位使用xpath有以下三种形式:

xml = etree.parse("Intuit.xml")
root = xml.getroot() #获取根节点

# 获取命名空间
ns = {'x':root.nsmap[None]}

MsgId = root.xpath("//x:MsgId",namespaces=ns)
print(MsgId[0].text)

name = root.xpath("//x:QueryResponse//..//x:name",namespaces=ns)
print(name[0].text)

Id = root.xpath("//x:Intuit//x:QueryResponse//x:Bill//x:Id",namespaces=ns)
print(Id[0].text)

注意:如果是使用相对路径中含有..的形式,该..前不能加上//x:*。

当然实际使用过程中发现xml结点较多,且使用多个不同的xml文件,因为每一个xml都有命名空间,因此封装了文件用于操作xml。

# 导入库
import re

import lxml.etree as etree

# 读取xml文件
import os

class XmlOperation():
    def __init__(self):
        pass
    def readXml(self,fileName):
        path = os.path.join("..","resource",fileName)
        tree = etree.parse(path)
        self.root = tree.getroot()
        return self
    #region设置文本节点
    def setNodeText(self,xpath="",replaceName="",index=0):
        ns = {'x':self.root.nsmap[None]}

        path = ""
        #如果传入的xpath以/开头或者/结尾,则去除开头或者结尾的/
        if re.match(r"^(/).+?",xpath) or re.match(r".+?(/)$",xpath):
            path = xpath.strip("/")
        #可以唯一定位,如MsgId
        if not re.search(r"/",xpath):
            path = "//x:" +  xpath
        elif re.search(r"/",xpath):
            pathList = xpath.split("/")
            #路径拼接
            path = "//x:" + "//x:".join(pathList)
            pattern = re.compile(r"(x:\W{2)")
            #如果是//x:A//x:..//x:c的形式修改修改为//x:A//..//x:c
            if pattern.search(path):
                path  = path.replace(r"x:..",r"..")
        else:raise Exception("路径输入有误")

        self.root.xpath(path,namespaces=ns)[index].text = replaceName
    #endregion
    
    #region删除属性
    def del_node_attrib(self,node,attrib):
        if node.getchildren():
            for child in node.getchildren():
                del_node_attrib(child,attrib):
        else:
            attri = node.attrib.get(attrib)
            if attri and attri == "√":
                del node.attrib[attrib]
            
    #endregion
    

这里再插入一点小常识:

1.使用pycharm的时候,生成类(大写首字母,写完类名)按下Alt+Enter,导入库(比如直接使用os.path选中os)按下Alt+Enter都是很好的快捷方法

2.函数的编写,加入#region...#endregion,可以更方便的浏览函数

写在最后,在学习使用lxml处理的时候遇到了不少问题,推荐几篇入门的帖子,对于如何使用lxml中携带有命名空间的比较有帮助,python读取xml,和命名空间使用以及lxml处理命名空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值