本主题主要说明python的xml处理标准模块xml.etree的使用。xml.etree模块包含4个子模块,其中cElementTree是ElementTree的别名,已经不推荐使用。本主题主要包含内容:
1. ElementInclude模块使用
2. ElementPath模块使用
3. ElementTree模块使用
一、etree模块帮助
import xml.etree
help(xml.etree)
Help on package xml.etree in xml:
NAME
xml.etree
DESCRIPTION
# $Id: __init__.py 3375 2008-02-13 08:05:08Z fredrik $
# elementtree package
PACKAGE CONTENTS
ElementInclude
ElementPath
ElementTree
cElementTree
FILE
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/xml/etree/__init__.py
提供四个模块:
|- ElementInclude
|- ElementPath
|- ElementTree
|- cElementTree
二、ElementInclude模块
ElementInclude模块主要提供xml中xinclude展开使用。
提供了几个异常类:
FatalIncludeError
|-builtins.SyntaxError
|-builtins.Exception
|-builtins.BaseException
|-builtins.object
提供了两个函数:
FUNCTIONS
|-default_loader(href, parse, encoding=None)
|-include(elem, loader=None)
提供了三个常量:
DATA
|-XINCLUDE = '{http://www.w3.org/2001/XInclude}'
|-XINCLUDE_FALLBACK = '{http://www.w3.org/2001/XInclude}fallback'
|-XINCLUDE_INCLUDE = '{http://www.w3.org/2001/XInclude}include'
2.1. 使用xinclude语法的xml文件
1. webpages.xml文件
<?xml version="1.0"?>
<webpage>
<body>Hello world!</body>
<xi:include href="footer.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
</webpage>
2. footer.xml文件
<?xml version="1.0"?>
<footer>Mage Education, 2019</footer>
2.2. 使用default_loader加载xml文件
函数default_loader加载任何文本文件,不过按照xml加载就返回Element对象,非xml就返回str字符串。
函数原型如下:
default_loader(href, parse, encoding=None)
|- href:用来指定加载的xml文件
|- parse:指定解析的类型:xml与非xml,xml就返回xml.etree.ElementTree.Element类型,否则字符串。
|- encoding:对非xml有用,用来指定编码格式,一般就是utf-8了。
import xml.etree.ElementInclude
# 非xml解析,直接返回字符串
result = xml.etree.ElementInclude.default_loader(
href='codes/webpages.xml',
parse='text',
encoding='utf-8')
print(':', type(result))
print(result)
print('------------------')
# 作为xml解析返回xml.etree.ElementTree.Element对象。
result = xml.etree.ElementInclude.default_loader(
href='codes/webpages.xml',
parse='xml',
encoding='utf-8')
print(':', type(result))
print(result)
: <class 'str'>
<?xml version="1.0"?>
<webpage>
<body>Hello world!</body>
<xi:include href="codes/footer.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/>
</webpage>
------------------
: <class 'xml.etree.ElementTree.Element'>
<Element 'webpage' at 0x105a32728>
2.3. 使用include函数扩展xinclude
函数include用来展开xml中的xinclude指令。
函数原型如下:
include(elem, loader=None)
|- elem:要扩展xinclude的元素。
|- loader:只加载扩展xml文件的加载器,默认是default_loader函数加载器。
注意:
经过include函数处理的元素,如果包含include指令,则会展开成xml的文件。 可以仔细观察西面例子的输出。
import xml.etree.ElementInclude
# 由于版本变化,默认的常量值,可以根据已有的文档修改。
xml.etree.ElementInclude.XINCLUDE_INCLUDE='{http://www.w3.org/2003/XInclude}include'
# 作为xml解析返回xml.etree.ElementTree.Element对象。
result = xml.etree.ElementInclude.default_loader(
href='codes/webpages.xml',
parse='xml',
encoding='utf-8')
print('xinclude扩展前输出')
for ele in result:
print(type(ele),ele)
xml.etree.ElementInclude.include(result, loader=None)
print('xinclude扩展后输出')
for ele in result:
print(type(ele),ele)
xinclude扩展前输出
<class 'xml.etree.ElementTree.Element'> <Element 'body' at 0x105a56e58>
<class 'xml.etree.ElementTree.Element'> <Element '{http://www.w3.org/2003/XInclude}include' at 0x105a4dae8>
xinclude扩展后输出
<class 'xml.etree.ElementTree.Element'> <Element 'body' at 0x105a56e58>
<class 'xml.etree.ElementTree.Element'> <Element 'footer' at 0x105a32728>
三、ElementPath模块
提供XPath的支持。
支持XPath的函数如下:
FUNCTIONS
|- find(elem, path, namespaces=None)
|- findall(elem, path, namespaces=None)
|- findtext(elem, path, default=None, namespaces=None)
|- get_parent_map(context)
|- iterfind(elem, path, namespaces=None)
|- prepare_child(next, token)
|- prepare_descendant(next, token)
|- prepare_parent(next, token)
|- prepare_predicate(next, token)
|- prepare_self(next, token)
|- prepare_star(next, token)
|- xpath_tokenizer(pattern, namespaces=None)
其中提供的常量数据有:
|- ops = {'': <function prepare_child>, '': <function prepare_star>, '.'...
|- xpath_tokenizer_re = re.compile('('[^']'|\"[^\"]*\"|::|//?|\.....
注意:
其中prepare_XXX函数就是ops的操作列表,操作会被iterfind使用,被用来解析path的每个部分,一般不直接使用。下面也不介绍。
实际ElementTree中的path操作方式也是使用这里的find系列函数。
下面是xpath支持的语法:
xpath语法 | 说明 |
---|---|
tag | 选择所有tag子元素 |
* | 选择一级子元素,比如*/egg选在孙子元素egg |
. | 表示当前元素 |
// | 所有子元素,比如.//egg选在任何级别节点上的egg元素。 |
.. | 上级父元素 |
[@attrib] | 具有属性attrib的元素 |
[@attrib='value'] | 属性值等于value的元素 |
[tag] | 所有具有tag子元素的元素 |
[tag='text'] | 选择子节点是tag,同时内容为text的元素 |
[position] | 选在给定位置的元素(1表示第一个), last()表示最后一个,last()-1从最后开始计数. |
3.1. findall函数
findall函数返回一个列表,原型实现如下:
def findall(elem, path, namespaces=None):
return list(iterfind(elem, path, namespaces))
findall本质是调用iterfind函数实现。
参数说明:
elem:被搜索的元素。
path:查找路径。
namespaces=None:指定path的命名空间。
import xml.etree.ElementPath
import xml.etree.ElementInclude
print(xml.etree.ElementPath.ops)
print(xml.etree.ElementPath.xpath_tokenizer_re)
root = xml.etree.ElementInclude.default_loader('codes/books.xml', 'xml')
eles = xml.etree.ElementPath.findall(root, 'book')
print('findall结果:----------')
print(eles)
{'': <function prepare_child at 0x105a53378>, '*': <function prepare_star at 0x105a53400>, '.': <function prepare_self at 0x105a53488>, '..': <function prepare_parent at 0x105a53598>, '//': <function prepare_descendant at 0x105a53510>, '[': <function prepare_predicate at 0x105a53620>}
re.compile('(\'[^\']*\'|\\"[^\\"]*\\"|::|//?|\\.\\.|\\(\\)|[/.*:\\[\\]\\(\\)@=])|((?:\\{[^}]+\\})?[^/\\[\\]\\(\\)@=\\s]+)|\\s+')
findall结果:----------
[<Element 'book' at 0x105a4d9f8>, <Element 'book' at 0x105a62048>, <Element 'book' at 0x105a62228>]
3.2. find函数
find函数返回查找的第一个元素,函数原型实现如下:
def find(elem, path, namespaces=None):
return next(iterfind(elem, path, namespaces), None)
从find的实现代码中可以看出,实际find每次返回都是iterfind返回的迭代器中的下一个元素,因为iterfind每次返回都是全新的查找的结果,所以find每次返回都是查找列表中的第一个。
import xml.etree.ElementPath
import xml.etree.ElementInclude
root = xml.etree.ElementInclude.default_loader('codes/books.xml', 'xml')
ele = xml.etree.ElementPath.find(root, 'book')
if ele:
print(ele.attrib)
{'category': 'Python'}
3.3. findtext函数
findtext是查找满足path条件的text内容,函数原型如下:
def findtext(elem, path, default=None, namespaces=None):
try:
elem = next(iterfind(elem, path, namespaces))
return elem.text or ""
except StopIteration:
return default
注意:返回的不是查找的元素,而是元素的text文本。
参数说明:
default:就是找到的元素没有文本内容的时候,使用该值替代。
import xml.etree.ElementPath
import xml.etree.ElementInclude
root = xml.etree.ElementInclude.default_loader('codes/note.xml', 'xml')
ele = xml.etree.ElementPath.findtext(root, 'to', '缺省值')
print(ele)
Tove
3.4. iterfind函数
iterfind返回一个迭代器类型,实际本质是一个生成器(class 'generator'),该函数的原型实现如下:
def iterfind(elem, path, namespaces=None):
import xml.etree.ElementPath
import xml.etree.ElementInclude
root = xml.etree.ElementInclude.default_loader('codes/books.xml', 'xml')
eles = xml.etree.ElementPath.iterfind(root, 'book')
print(eles)
print(type(eles))
for e in eles:
print(e)
<generator object prepare_child.<locals>.select at 0x105a487d8>
<class 'generator'>
<Element 'book' at 0x105a62b88>
<Element 'book' at 0x105a629a8>
<Element 'book' at 0x105a72048>
四、ElementTree模块
ElementTree模块提供xml的dom解析实现,该模块的类包含:
builtins.SyntaxError(builtins.Exception)
ParseError
builtins.object
Element:xml的基本单元:元素
ElementTree:xml元素构成的树状数据结构
QName:Quality Name:根元素
TreeBuilder:xml树构建器
XMLParser:xml解析器
XMLPullParser:xml非阻塞解析器
同时提供一组快捷函数:
Comment(text=None)
PI = ProcessingInstruction(target, text=None)
ProcessingInstruction(target, text=None)
SubElement(...)
XML(text, parser=None)
XMLID(text, parser=None)
dump(elem)
fromstring = XML(text, parser=None)
fromstringlist(sequence, parser=None)
iselement(element)
iterparse(source, events=None, parser=None)
parse(source, parser=None)
register_namespace(prefix, uri)
tostring(element, encoding=None, method=None, *, short_empty_elements=True)
tostringlist(element, encoding=None, method=None, *, short_empty_elements=True)
4.1. TreeBuilder与XMLParser阻塞解析
TreeBuilder负责构建Element对象树,XMLParser负责解析xml内容。
TreeBuilder提供基本的树的构建功能,我们只要返回root Element元素即可得到Element树。
# coding = utf-8
from xml.etree.ElementTree import TreeBuilder
from xml.etree.ElementTree import XMLParser
class MyBuilder(TreeBuilder):
is_root = True
root_element = None
def start(self, tag, attrs):
elem = super().start(tag, attrs)
if self.is_root:
self.root_element = elem
self.is_root = False
return elem
builder = MyBuilder()
parser = XMLParser(target=builder)
fd = open('codes/books.xml', 'r')
xml_data = fd.read()
parser.feed(xml_data)
root = builder.root_element
for item in root.getchildren():
print(item.tag, ':', item.attrib)
for it in item.getchildren():
print('\t|-', it, ':', it.tag,':', it.text)
book : {'category': 'Python'}
|- <Element 'title' at 0x105a62e58> : title : 网络爬虫开发
|- <Element 'author' at 0x105a623b8> : author : 蜘蛛精
|- <Element 'year' at 0x105a620e8> : year : 2018
|- <Element 'price' at 0x105a621d8> : price : 66.50
|- <Element 'publisher' at 0x105a62048> : publisher : 清华大学出版社
book : {'category': '系统运维'}
|- <Element 'title' at 0x105a62a98> : title : K8S运维指南r
|- <Element 'author' at 0x105a62728> : author : 马哥教育
|- <Element 'year' at 0x105a624a8> : year : 2018
|- <Element 'price' at 0x105a62408> : price : 99.00
|- <Element 'publisher' at 0x105a62458> : publisher : 机械版社
book : {'category': '区块链'}
|- <Element 'title' at 0x105a625e8> : title : 以太坊智能合约开发
|- <Element 'author' at 0x105a624f8> : author : 钱多多
|- <Element 'year' at 0x105a62598> : year : 2019
|- <Element 'price' at 0x105a62548> : price : 88.95
|- <Element 'publisher' at 0x105a723b8> : publisher : 邮电出版社
4.2. XMLPullParser 非阻塞解析
XMLPullParser与XMLParser的区别是非阻塞,阻塞的特点在指定回调事件,非阻塞的特点是产生事件列表。
XMLPullParser的read_events(self)返回的是一个数据生成器,也是迭代器。
其中第一个节点就是root节点,可以直接遍历。
# coding = utf-8
from xml.etree.ElementTree import XMLPullParser
events = ("start", "end", "start-ns", "end-ns")
parser = XMLPullParser(events=events)
fd = open('codes/books.xml', 'r')
xml_data = fd.read()
parser.feed(xml_data)
# 转换成列表操作
re_events = list(parser.read_events())
# 构造xml的root
root_element = re_events[0][1]
# 从根节点偏离element树
def list_tree(element, depth):
print('\t' * depth, element.tag, ":", element.text if element.text.strip() != '' else '')
children_elements = element.getchildren()
if children_elements:
for e_ in children_elements:
list_tree(e_, depth+1)
list_tree(root_element, 0)
books :
book :
title : 网络爬虫开发
author : 蜘蛛精
year : 2018
price : 66.50
publisher : 清华大学出版社
book :
title : K8S运维指南r
author : 马哥教育
year : 2018
price : 99.00
publisher : 机械版社
book :
title : 以太坊智能合约开发
author : 钱多多
year : 2019
price : 88.95
publisher : 邮电出版社
4.3. Element对象与ElementTree对象
ElementTree实际是Element的封装,从上面的XMLParser与XMLPullParser可以看出,已经实现基本的Element树结构。
ElementTree与XMLParser、XMLPullParser返回的Element都是Element树结构。但ElementTree提供更加快捷的XMLParser、XMLPullParser的解析功能与xml加载功能,同时提供xml保存功能。
Element对象就是每个标签的封装,提供如下基本数据封装。
| -attrib
| 类型是字典,用来封装元素的所有属性。
|
| -tag
| 类型字符串,用来封装元素的标签名。
|
| -tail
| 类型字符串,标签结束后的文本,也可以是None。
|
| -text
| 类型字符串,开始标签后的文本,也可以是None。
构造器:
| __init__(self, /, *args, **kwargs)
同时提供了一组对封装数据的操作函数:
|
| -makeelement(self, tag, attrib, /)
|
| -append(self, subelement, /)
|
| -insert(self, index, subelement, /)
|
| -remove(self, subelement, /)
|
| -set(self, key, value, /)
|
| -clear(self, /)
|
| -extend(self, elements, /)
|
| -find(self, /, path, namespaces=None)
|
| -findall(self, /, path, namespaces=None)
|
| -findtext(self, /, path, default=None, namespaces=None)
|
| -get(self, /, key, default=None)
|
| -getchildren(self, /)
|
| -getiterator(...)
| iter($self, /, tag=None)
| --
|
| -items(self, /)
|
| -iter(self, /, tag=None)
|
| -iterfind(self, /, path, namespaces=None)
|
| -itertext(self, /)
|
| -keys(self, /)
| - __init__(self, element=None, file=None)
| - find(self, path, namespaces=None)
| - findall(self, path, namespaces=None)
| - findtext(self, path, default=None, namespaces=None)
| - getiterator(self, tag=None)
| - getroot(self)
| - iter(self, tag=None)
| - iterfind(self, path, namespaces=None)
| - parse(self, source, parser=None)
| - write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True)
| - write_c14n(self, file)
注意:
c14n说明:W3C推出了C14n标准用于XML数据的规范化。
目前还没有c14n真正的实现。
# coding = utf-8
from xml.etree.ElementTree import ElementTree
from xml.etree.ElementTree import Element
tree = ElementTree()
tree.parse('codes/books.xml')
root_element = tree.getroot()
# 从根节点偏离element树
def list_tree(element, depth):
print('\t' * depth, element.tag, ":", element.text if element.text.strip() != '' else '')
children_elements = element.getchildren()
if children_elements:
for e_ in children_elements:
list_tree(e_, depth+1)
list_tree(root_element, 0)
books :
book :
title : 网络爬虫开发
author : 蜘蛛精
year : 2018
price : 66.50
publisher : 清华大学出版社
book :
title : K8S运维指南r
author : 马哥教育
year : 2018
price : 99.00
publisher : 机械版社
book :
title : 以太坊智能合约开发
author : 钱多多
year : 2019
price : 88.95
publisher : 邮电出版社
ElementTree还可以通过构造器封装root元素,提供更加方便的操作。
五、cElementTree模块
cElementTree模块是xml.etree.ElementTree模块的别名,目前已经不推荐使用。