Tips 23

XML

XML是一种描述层次结构话数据的方法。XML文档包含由起始和结束标签分割的一个或多个元素。下面这个就是个简单的XML文件:

<foo>     # 这是foo元素的起始标签
</foo>  # 这是foo元素对应的结束标签

元素可以嵌套到任意层次

<foo>
  <bar> </bar>  # 可以作为foo元素的子元素

XML文档中第一个元素叫做根元素,每份XML文档只能有一个根元素。以下就不是一个xml文档。     # 两个根元素 元素可以有其属性,类似字典的键值对。属性由空格分割列举在元素的起始标签中。一个元素中属性名不恩能够重复,属性值必须用括号包括起来,单引号或双引号都可以。

<foo lang='en'> # foo元素有一个叫做lang的属性,属性值为'en'
  <bar id='papayawhip' lang="fr"></bar> # bar元素分别两个属性,id和lang,每个属性都有对应值,每个元素具有独立的属性集
</foo>

如果元素存在多个属性,书写的顺序不重要。元素的属性都是无序的键值对集,和字典类似。元素的个数是没有限制的。 元素可以有其文本内容  

可以在所以派生元素中搜索,即任意嵌套层次的子元素、孙子元素...

>>> all_links = tree.findall('.//{http://www.w3.org/2005/Atom}link') # .//指在任意嵌套层次查找
>>> all_links
[<Element '{http://www.w3.org/2005/Atom}link' at 0x7fc09da09638>, <Element '{http://www.w3.org/2005/Atom}link' at 0x7fc09da098b8>, <Element '{http://www.w3.org/2005/Atom}link' at 0x7fc09da09d18>, <Element '{http://www.w3.org/2005/Atom}link' at 0x7fc09da0c098>]
>>> all_links[0].attrib # 直接子元素的link
{'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate'}
>>> all_links[1].attrib # 子元素entry子元素link
{'href': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition', 'type': 'text/html', 'rel': 'alternate'}                                                           
>>> all_links[2].attrib                                                                     
{'href': 'http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress', 'type': 'text/html', 'rel': 'alternate'}                                                        
>>> all_links[3].attrib
{'href': 'http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats', 'type': 'text/html', 'rel': 'alternate'}

ElementTree的findall()方法是其一个非常强大的特性,但它的查询方式有些出乎意料,“有限的XPath支持" XPath是一种用于查询XML文档的W3C标准。另外一个第三方库对ElementTree的API进行了扩展,提供了对XPath的全面支持。

LXML

lxml是一个开源的第三方库,以流行的libxml2解析器为基础开发。它提供了对XPath1.0的全面支持。

>>> from lxml import etree # 导入lxml,和ElementTree提供相同的API
>>> tree  = etree.parse('feed.xml') # parse函数和ElementTree
>>> root = tree.getroot() # 相同
>>> root.findall('{http://www.w3.org/2005/Atom}entry') # 相同
[<Element {http://www.w3.org/2005/Atom}entry at 0x7fc09da04b48>, <Element {http://www.w3.org/2005/Atom}entry at 0x7fc09da04a08>, <Element {http://www.w3.org/2005/Atom}entry at 0x7fc09bd92ec8>]

对应大型的xml文件,lxml明显比内置的ElementTree快了许多。如果现在只用到了ElementTree的API,并且想要使用最快的实现,可以尝试导入lxml。

比如 try: from lxml import etree except ImportError: import xml.etree.ElementTree as etree 这种方法同样适用于其他备选模块的方法

lxml不只是一个更快速的ElementTree,它的findall()方法能够支持更加复杂的表达式。

>>> import lxml.etree # 直接导入了lxml.etree,强调这些特性只限于lxml
>>> tree = lxml.etree.parse('feed.xml')
>>> tree.findall('.//{http://www.w3.org/2005/Atom}*[@href]')# 查找整个文档范围内搜索命名空间Atom中具有href属性的元素 {http://www.w3.org/2005/Atom}指示"搜索范围仅在命名空间Atom中。”*表示“任意本地名的元素”[@href]表示含有href属性“
[<Element {http://www.w3.org/2005/Atom}link at 0x7fc09bd99088>, <Element {http://www.w3.org/2005/Atom}link at 0x7fc09bd99108>, <Element {http://www.w3.org/2005/Atom}link at 0x7fc09bd990c8>, <Element {http://www.w3.org/2005/Atom}link at 0x7fc09bd92f08>]
>>> tree.findall(".//{http://www.w3.org/2005/Atom}*[@href='http://diveintomark.org/']") # 查找所有包含href属性并且值为"http://diveintomark.org/"的Atom元素
[<Element {http://www.w3.org/2005/Atom}link at 0x7fc09bd99088>]
>>> NS = '{http://www.w3.org/2005/Atom}'
>>> tree.findall('.//{NS}author[{NS}uri]'.format(NS=NS)) #搜索命名空间Atom中包含uri元素作为子元素的author元素。该语句只返回第一个和第二个entry元素中的author元素,最后的entry元素没有uri 
[<Element {http://www.w3.org/2005/Atom}author at 0x7fc09bd991c8>, <Element {http://www.w3.org/2005/Atom}author at 0x7fc09bd99048>]

lxml集成了对任意XPath1.0表达式的支持

>>> import lxml.etree
>>> tree = lxml.etree.parse('feed.xml')
>>> NSMAP = { 'atom': 'http://www.w3.org/2005/Atom'} # 要查询命名空间的元素,定义一个命名空间前缀映射。
>>> entries = tree.xpath(".//atom:category[@term='accessibility']/..", namespaces=NSMAP) #  XPath查询请求。这个XPath表达式目的在于搜索category元素,并且该元素包含值为accessibility的term属性”/.."意思是返回找到的category元素的父元素。这条语句会找到所有包含<category term = 'accessibility'>作为子元素的条目
>>> entries # xpath()函数返回一个ElementTree对象列表
[<Element {http://www.w3.org/2005/Atom}entry at 0x7fc09bd99188>] 
>>> entry = entries[0]
>>> entry.xpath('./atom:title/text()', namespaces=NSMAP) # XPath表达式并总是返回一个元素列表。一个解析了的xml文档的DOM模型并不包含元素;只包含结点。结点可以是元素,属性,甚至是文本内容。XPath查询的结果是一个结点列表。当前查询返回一个文本结点列表:title元素(atom:title)的文本内容(text()),并且title元素必须是当前元素的子元素(./)

生成XML

python同样可以创建xml文档

>>> import xml.etree.ElementTree as etree 
>>> new_feed = etree.Element('{http://www.w3.org/2005/Atom}feed', # 实例化Element类创建一个新元素。将元素的名字(命名空间+本地名)作为参数。
attrib = {'{http://www.w3.org/XML/1998/namespace}lang':'en'}) # 将属性名和值构成的字典传递给attrib参数
>>> print(etree.tostring(new_feed)) 
b'<ns0:feed xmlns:ns0="http://www.w3.org/2005/Atom" xml:lang="en" />'
['Accessibility is a harsh mistress']

内置的ElementTree库没有提供细粒度地对序列化时命名空间内元素的控制,但是lxml有这样的功能。

>>> import lxml.etree 
>>> NSMAP = {None: 'http://www.w3.org/2005/Atom'} # 首先,定义一个用于命名空间映射的字典对象,使用None前缀来定义默认的命名空间
>>> new_feed = lxml.etree.Element('feed', nsmap=NSMAP) # 我们创建元素的时候,给lxml专有的nsmap参数传值,并且lxml会参照我们定义的命名空间前缀
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom"/>
>>> new_feed.set('{http://www.w3.org/XML/1998/namespace}lang', 'en')# 使用set方法随时给元素添加所需属性
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"/>

>>> title = lxml.etree.SubElement(new_feed, 'title', attrib={'type':'html'}) # 给已有元素创建子元素,实例化SubElement类,两个参数,父元素(new_feed)和子元素的名字
>>> print(lxml.etree.tounicode(new_feed))# 传递属性字典进去
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html"/></feed>
>>> title.text = 'dive into &hellip;'
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">dive into &amp;hellip;</title></feed>
# 新创建title元素在Atom命名空间中,并作为子元素插入到feed元素中。设定元素的文本内容,只需设定其.text属性;当前title元素序列化时就使用了其文本内容,任何包含<或则&符号的内容在序列化时都需要转义
>>> print(lxml.etree.tounicode(new_feed, pretty_print=True)
... )
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title type="html">dive into &amp;hellip;</title>
</feed>
"pretty_print=True,"会在每个结束的末尾或含义子元素但没有文本内容的末尾添加换行符

解析损坏的XML

不再说明了,尽量保证xml格式符合标准吧

by 李鹏