学习过程中遇到了通信的报文为xml的的消息体,将通讯的内容依附于xml的载体进行传输,开始尝试使用包括ElementTree等在内的诸多库,但是因为一些处理皆不尽人意,最后选择了lxml库,该库无论处理速度还是函数功能封装基本可以满足需求。因为lxml不是Python自带的标准库,因此需要自己安装
pip3 install lxml
对于读取xml常见的有两种方式,一种是xml的字符串,即字符串的内容是xml文件,另一种是工程中包含xml文件,读取xml后进行操作,由于实际运用中一般不会直接读取一段字符串的形式(自己遇到的),都是以xml文件的形式进行读取,因此这里也以也是先读取文件,再操作文件。这里以 intuit.xml 文件为例
=1
zhagsan
20200406
=2
lisi
20200407
在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)
输出结果:
[, ]
注意这里输出的结果是 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处理命名空间