设为首页 收藏本站
查看: 535|回复: 0

[经验分享] python xml

[复制链接]

尚未签到

发表于 2015-10-26 12:14:01 | 显示全部楼层 |阅读模式

概述#



这本书的大部分章节都是以样例代码为中心的。但是xml这章不是;它以数据为中心。最常见的xml应用为“聚合订阅(syndication feeds)”,它用来展示博客,论坛或者其他会经常更新的网站的最新内容。大多数的博客软件都会在新文章,新的讨论区,或者新博文发布的时候自动生成和更新feed。我们可以通过“订阅(subscribe)”feed来关注它们,还可以使用专门的“feed聚合工具(feed
aggregator)”,比如Google Reader。


以下的xml数据是我们这一章中要用到的。它是一个feed — 更确切地说是一个Atom聚合feed


跳过该代码清单


[隐藏] [在新窗口中打开] [download feed.xml]
<?xml version='1.0' encoding='utf-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'>
  
<title>dive into mark</title>
  
<subtitle>currently between addictions</subtitle>
  
<id>tag:diveintomark.org,2001-07-29:/</id>
  
<updated>2009-03-27T21:56:07Z</updated>
  
<link rel='alternate' type='text/html' href='http://diveintomark.org/'/>
  
<link rel='self' type='application/atom&#43;xml' href='http://diveintomark.org/feed/'/>
  
<entry>
   
<author>
      
<name>Mark</name>
      
<uri>http://diveintomark.org/</uri>
   
</author>
   
<title>Dive into history, 2009 edition</title>
   
<link rel='alternate' type='text/html'
      
href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/>
   
<id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id>
   
<updated>2009-03-27T21:56:07Z</updated>
   
<published>2009-03-27T17:20:42Z</published>
   
<category scheme='http://diveintomark.org' term='diveintopython'/>
   
<category scheme='http://diveintomark.org' term='docbook'/>
   
<category scheme='http://diveintomark.org' term='html'/>
  
<summary type='html'>Putting an entire chapter on one page sounds
    bloated, but consider this &amp;mdash; my longest chapter so far
    would be 75 printed pages, and it loads in under 5 seconds&amp;hellip;
    On dialup.
</summary>
  
</entry>
  
<entry>
   
<author>
      
<name>Mark</name>
      
<uri>http://diveintomark.org/</uri>
   
</author>
   
<title>Accessibility is a harsh mistress</title>
   
<link rel='alternate' type='text/html'
      
href='http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress'/>
   
<id>tag:diveintomark.org,2009-03-21:/archives/20090321200928</id>
   
<updated>2009-03-22T01:05:37Z</updated>
   
<published>2009-03-21T20:09:28Z</published>
   
<category scheme='http://diveintomark.org' term='accessibility'/>
   
<summary type='html'>The accessibility orthodoxy does not permit people to
      question the value of features that are rarely useful and rarely used.
</summary>
  
</entry>
  
<entry>
   
<author>
      
<name>Mark</name>
   
</author>
   
<title>A gentle introduction to video encoding, part 1: container formats</title>
   
<link rel='alternate' type='text/html'
      
href='http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats'/>
   
<id>tag:diveintomark.org,2008-12-18:/archives/20081218155422</id>
   
<updated>2009-01-11T19:39:22Z</updated>
   
<published>2008-12-18T15:54:22Z</published>
   
<category scheme='http://diveintomark.org' term='asf'/>
   
<category scheme='http://diveintomark.org' term='avi'/>
   
<category scheme='http://diveintomark.org' term='encoding'/>
   
<category scheme='http://diveintomark.org' term='flv'/>
   
<category scheme='http://diveintomark.org' term='GIVE'/>
   
<category scheme='http://diveintomark.org' term='mp4'/>
   
<category scheme='http://diveintomark.org' term='ogg'/>
   
<category scheme='http://diveintomark.org' term='video'/>
   
<summary type='html'>These notes will eventually become part of a
      tech talk on video encoding.
</summary>
  
</entry>
</feed>




5分钟XML速成#



如果你已经了解xml,可以跳过这一部分。


xml是一种描述层次结构化数据的通用方法。xml文档包含由起始和结束标签(tag)分隔的一个或多个元素(element)。以下也是一个完整的(虽然空洞)xml文件:


<foo>   
</foo>  


这是foo元素的起始标签

这是foo元素对应的结束标签。就如写作、数学或者代码中需要平衡括号一样,每一个起始标签必须有对应的结束标签来闭合(匹配)。

元素可以嵌套到任意层次。位于foo中的元素bar可以被称作其子元素


<foo>
  
<bar></bar>
</foo>


xml文档中的第一个元素叫做根元素(root element)。并且每份xml文档只能有一个根元素。以下不是一个xml文档,因为它存在两个“根元素”。


<foo></foo>
<bar></bar>

元素可以有其属性(attribute),它们是一些名字-&#20540;(name-value)对。属性由空&#26684;分隔列举在元素的起始标签中。一个元素中属性名不能重复。属性&#20540;必须用引号包围起来。单引号、双引号都是可以。


<foo lang='en'>                          
  <bar id='papayawhip' lang=&quot;fr&quot;></bar>  
</foo>



foo元素有一个叫做lang的属性。lang的&#20540;为en

bar元素则有两个属性,分别为id和lang。其中lang属性的&#20540;为fr。它不会与foo的那个属性产生冲突。每个元素都其独立的属性集。

如果元素有多个属性,书写的顺序并不重要。元素的属性是一个无序的键-&#20540;对集,跟Python中的列表对象一样。另外,元素中属性的个数是没有限制的。


元素可以有其文本内容(text content)


<foo lang='en'>
  
<bar lang='fr'>PapayaWhip</bar>
</foo>


如果某一元素既没有文本内容,也没有子元素,它也叫做空元素


<foo></foo>

表达空元素有一种简洁的方法。通过在起始标签的尾部添加/字符,我们可以省略结束标签。上一个例子中的xml文档可以写成这样:


<foo/>

就像Python函数可以在不同的模块(modules)中声明一样,也可以在不同的名字空间(namespace)中声明xml元素。xml文档的名字空间通常看起来像URL。我们可以通过声明xmlns来定义默认名字空间。名字空间声明跟元素属性看起来很相&#20284;,但是它们的作用是不一样的。


<feed xmlns='http://www.w3.org/2005/Atom'>  
  <title>dive into mark</title>            
</feed>



feed元素处在名字空间http://www.w3.org/2005/Atom中。

title元素也是。名字空间声明不仅会作用于当前声明它的元素,还会影响到该元素的所有子元素。

也可以通过xmlns:prefix声明来定义一个名字空间并取其名为prefix。然后该名字空间中的每个元素都必须显式地使用这个前缀(prefix)来声明。


<atom:feed xmlns:atom='http://www.w3.org/2005/Atom'>  
  <atom:title>dive into mark</atom:title>            
</atom:feed>


feed元素属于名字空间http://www.w3.org/2005/Atom。

title元素也在那个名字空间。

对于xml解析器而言,以上两个xml文档是一样的。名字空间 &#43; 元素名 = xml标识。前缀只是用来引用名字空间的,所以对于解析器来说,这些前缀名(atom:)其实无关紧要的。名字空间相同,元素名相同,属性(或者没有属性)相同,每个元素的文本内容相同,则xml文档相同。


最后,在根元素之前,字符编码信息可以出现在xml文档的第一行。(这里存在一个两难的局面(catch-22),直观上来说,解析xml文档需要这些编码信息,而这些信息又存在于xml文档中,如果你对xml如何解决此问题有兴趣,请参阅xml规范中
F 章节)


<?xml version='1.0' encoding='utf-8'?>

现在我们已经知道足够多的xml知识,可以开始探险了!






Atom Feed的结构#



想像一下网络上的博客,或者互联网上任何需要频繁更新的网站,比如CNN.com。该站点有一个标题(“CNN.com”),一个子标题(“Breaking News, U.S., World,
Weather, Entertainment &Video News”),包含上次更新的日期(“updated 12:43 p.m. EDT, Sat May
16, 2009”),还有在不同时期发布的文章的列表。每一篇文章也有自己的标题,第一次发布的日期(如果曾经修订过或者改正过某个输入错误,或许也有一个上次更新的日期),并且每篇文章有自己唯一的URL。


Atom聚合&#26684;式被设计成可以包含所有这些信息的标准&#26684;式。我的博客无论在设计,主题还是读者上都与CNN.com大不相同,但是它们的基本结构是相同的。CNN.com能做的事情,我的博客也能做…


每一个Atom订阅都共享着一个根元素:即在名字空间http://www.w3.org/2005/Atom中的元素feed。



跳过该代码清单


[隐藏] [在新窗口中打开]<feed xmlns='http://www.w3.org/2005/Atom'  
      xml:lang='en'>                       


http://www.w3.org/2005/Atom表示名字空间Atom。

每一个元素都可以包含xml:lang属性,它用来声明该元素及其子元素使用的语言。在当前样例中,xml:lang在根元素中被声明了一次,也就意味着,整个feed都使用英文。

描述Atom feed自身的一些信息在根元素feed的子元素中被声明。


跳过该代码清单


[隐藏] [在新窗口中打开]<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'>
  <title>dive into mark</title>                                             
  <subtitle>currently between addictions</subtitle>                        
  <id>tag:diveintomark.org,2001-07-29:/</id>                                
  <updated>2009-03-27T21:56:07Z</updated>                                   
  <link rel='alternate' type='text/html' href='http://diveintomark.org/'/>  


该行表示这个feed的标题为dive into mark。

这一行表示子标题为currently between addictions。

每一个feed都要有一个全局唯一标识符(globally unique identifier)。想要知道如何创建它,请查阅RFC
4151。

表示当前feed上次更新的时间为March 27, 2009, at 21:56 GMT。通常来说,它与最近一篇文章最后一次被修改的时间是一样的。

事情开始变得有趣了…link元素没有文本内容,但是它有三个属性:rel,type和href。rel元素的&#20540;能告诉我们链接的类型;rel='alternate'表示这个链接指向当前feed的另外一个版本。type='text/html'表示链接的目标是一个html页面。然后目标地址在href属性中指出。

现在我们知道这个feed上一更新是在on March 27, 2009,它是为一个叫做“dive into mark”的站点准备的,并且站点的地址为http://diveintomark.org/。



☞在有一些xml文档中,元素的排列顺序是有意义的,但是Atom
feed中不需要这样做。



feed级的元数据后边就是最近文章的列表了。单独的一篇文章就像这样:


跳过该代码清单


[隐藏] [在新窗口中打开]<entry>
  <author>                                                                 
   
<name>Mark</name>
   
<uri>http://diveintomark.org/</uri>
  
</author>
  <title>Dive into history, 2009 edition</title>                           
  <link rel='alternate' type='text/html'                                   
   
href='http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition'/>
  <id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id>        
  <updated>2009-03-27T21:56:07Z</updated>                                 
  
<published>2009-03-27T17:20:42Z</published>        
  <category scheme='http://diveintomark.org' term='diveintopython'/>      
  
<category scheme='http://diveintomark.org' term='docbook'/>
  
<category scheme='http://diveintomark.org' term='html'/>
  <summary type='html'>Putting an entire chapter on one page sounds        
    bloated, but consider this &amp;mdash; my longest chapter so far
    would be 75 printed pages, and it loads in under 5 seconds&amp;hellip;
    On dialup.
</summary>
</entry>                                                                  


author元素指示文章的作者:一个叫做Mark的伙计,并且我们可以在http://diveintomark.org/找到他的事迹。(这就像是feed元素里的备用链接,但是没有规定一定要这样。许多网络日志由多个作者完成,他们都有自己的个人主页。)

title元素给出这篇文章的标题,即“Dive into history, 2009 edition”。

如feed元素中的备用链接一样,link元素给出这篇文章的html版本地址。

每个条目也像feed一样,需要一个唯一的标识。

每个条目有两个日期与其相关:第一次发布日期(published)和上次修改日期(updated)。

条目可以属于任意多个类别。这篇文章被归类到diveintopython,docbook,和html。

summary元素中有这篇文章的概要性描述。(还有一个元素这里没有展示出来,即content,我们可以把整篇文章的内容都放在里边。)当前样例中,summary元素含有一个Atom特有的type='html'属性,它用来告知这份概要为html&#26684;式,而非纯文本。这非常重要,因为概要内容中包含了html中特有的实体(&mdash;和&hellip;),它们不应该以纯文本直接显示,正确的形式应该为“—”和“…”。

最后就是entry元素的结束标记了,它指示文章元数据的结尾。




解析XML#



Python可以使用几种不同的方式解析xml文档。它包含了dom和sax解析器,但是我们焦点将放在另外一个叫做ElementTree的库上边。


跳过该代码清单


[隐藏] [在新窗口中打开] [download feed.xml]
>>> import xml.etree.ElementTree as etree    ①
>>> tree = etree.parse('examples/feed.xml')  ②
>>> root = tree.getroot()                    ③
>>> root                                     ④
<Element {http://www.w3.org/2005/Atom}feed at cd1eb0>


ElementTree属于Python标准库的一部分,它的位置为xml.etree.ElementTree。

parse()函数是ElementTree库的主要入口,它使用文件名或者流对象作为参数。parse()函数会立即解析完整个文档。如果内存资源紧张,也可以增量式地解析xml文档

parse()函数会返回一个能代表整篇文档的对象。这不是根元素。要获得根元素的引用可以调用getroot()方法。

如预期的那样,根元素即http://www.w3.org/2005/Atom名字空间中的feed。该字符串表示再次重申了非常重要的一点:xml元素由名字空间和标签名(也称作本地名(local
name)
)组成。这篇文档中的每个元素都在名字空间Atom中,所以根元素被表示为{http://www.w3.org/2005/Atom}feed。

☞ElementTree使用{namespace}localname来表达xml元素。我们将会在ElementTree的api中多次见到这种形式。



元素即列表#



在ElementTree API中,元素的行为就像列表一样。列表中的项即该元素的子元素。


跳过该代码清单


[隐藏] [在新窗口中打开]# continued from the previous example
>>> root.tag                        ①
'{http://www.w3.org/2005/Atom}feed'
>>> len(root)                       ②
8
>>> for child in root:              ③
...   print(child)                  ④
...
<Element {http://www.w3.org/2005/Atom}title at e2b5d0>
<Element {http://www.w3.org/2005/Atom}subtitle at e2b4e0>
<Element {http://www.w3.org/2005/Atom}id at e2b6c0>
<Element {http://www.w3.org/2005/Atom}updated at e2b6f0>
<Element {http://www.w3.org/2005/Atom}link at e2b4b0>
<Element {http://www.w3.org/2005/Atom}entry at e2b720>
<Element {http://www.w3.org/2005/Atom}entry at e2b510>
<Element {http://www.w3.org/2005/Atom}entry at e2b750>


紧接前一例子,根元素为{http://www.w3.org/2005/Atom}feed。

根元素的“长度”即子元素的个数。

我们可以像使用迭代器一样来遍历其子元素。

从输出可以看到,根元素总共有8个子元素:所有feed级的元数据(title,subtitle,id,updated和link),还有紧接着的三个entry元素。

也许你已经注意到了,但我还是想要指出来:该列表只包含直接子元素。每一个entry元素都有其子元素,但是并没有包括在这个列表中。这些子元素本可以包括在entry元素的列表中,但是确实不属于feed的子元素。但是,无论这些元素嵌套的层次有多深,总是有办法定位到它们的;在这章的后续部分我们会介绍两种方法。



属性即字典#



xml不只是元素的集合;每一个元素还有其属性集。一旦获取了某个元素的引用,我们可以像操作Python的字典一样轻松获取到其属性。


跳过该代码清单


[隐藏] [在新窗口中打开]# continuing from the previous example
>>> root.attrib                           ①
{'{http://www.w3.org/XML/1998/namespace}lang': 'en'}
>>> root[4]                               ②
<Element {http://www.w3.org/2005/Atom}link at e181b0>
>>> root[4].attrib                        ③
{'href': 'http://diveintomark.org/',
'type': 'text/html',
'rel': 'alternate'}
>>> root[3]                               ④
<Element {http://www.w3.org/2005/Atom}updated at e2b4e0>
>>> root[3].attrib                        ⑤
{}


attrib是一个代表元素属性的字典。这个地方原来的标记语言是这样描述的:<feed
xmlns='http://www.w3.org/2005/Atom' xml:lang='en'>。前缀xml:指示一个内置的名字空间,每一个xml不需要声明就可以使用它。

第五个子元素 — 以0为起始的列表中即[4] — 为元素link。

link元素有三个属性:href,type,和rel。

第四个子元素 — [3] — 为updated。

元素updated没有子元素,所以.attrib是一个空的字典对象。




在XML文档中查找结点#



到目前为止,我们已经“自顶向下“地从根元素开始,一直到其子元素,走完了整个文档。但是许多情况下我们需要找到xml中特定的元素。Etree也能完成这项工作。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> import xml.etree.ElementTree as etree
>>> tree = etree.parse('examples/feed.xml')
>>> root = tree.getroot()
>>> root.findall('{http://www.w3.org/2005/Atom}entry')    ①
[<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>,
<Element {http://www.w3.org/2005/Atom}entry at e2b510>,
<Element {http://www.w3.org/2005/Atom}entry at e2b540>]
>>> root.tag
'{http://www.w3.org/2005/Atom}feed'
>>> root.findall('{http://www.w3.org/2005/Atom}feed')     ②
[]
>>> root.findall('{http://www.w3.org/2005/Atom}author')   ③
[]


findfall()方法查找匹配特定&#26684;式的子元素。(关于查询的&#26684;式稍后会讲到。)

每个元素 — 包括根元素及其子元素 — 都有findall()方法。它会找到所有匹配的子元素。但是为什么没有看到任何结果呢?也许不太明显,这个查询只会搜索其子元素。由于根元素feed中不存在任何叫做feed的子元素,所以查询的结果为一个空的列表。

这个结果也许也在你的意料之外。在这篇文档中确实存在author元素;事实上总共有三个(每个entry元素中都有一个)。但是那些author元素不是根元素的直接子元素。我们可以在任意嵌套层次中查找author元素,但是查询的&#26684;式会有些不同。

跳过该代码清单


[隐藏] [在新窗口中打开]>>> tree.findall('{http://www.w3.org/2005/Atom}entry')    ①
[<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>,
<Element {http://www.w3.org/2005/Atom}entry at e2b510>,
<Element {http://www.w3.org/2005/Atom}entry at e2b540>]
>>> tree.findall('{http://www.w3.org/2005/Atom}author')   ②
[]



为了方便,对象tree(调用etree.parse()的返回&#20540;)中的一些方法是根元素中这些方法的镜像。在这里,如果调用tree.getroot().findall(),则返回&#20540;是一样的。

也许有些意外,这个查询请求也没有找到文档中的author元素。为什么没有呢?因为它只是tree.getroot().findall('{http://www.w3.org/2005/Atom}author')的一种简洁表示,即“查询所有是根元素的子元素的author”。因为这些author是entry元素的子元素,所以查询没有找到任何匹配的。

find()方法用来返回第一个匹配到的元素。当我们认为只会有一个匹配,或者有多个匹配但我们只关心第一个的时候,这个方法是很有用的。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> entries = tree.findall('{http://www.w3.org/2005/Atom}entry')           ①
>>> len(entries)
3
>>> title_element = entries[0].find('{http://www.w3.org/2005/Atom}title')  ②
>>> title_element.text
'Dive into history, 2009 edition'
>>> foo_element = entries[0].find('{http://www.w3.org/2005/Atom}foo')      ③
>>> foo_element
>>> type(foo_element)
<class 'NoneType'>



在前一样例中已经看到。这一句返回所有的atom:entry元素。

find()方法使用ElementTree作为参数,返回第一个匹配到的元素。

在entries[0]中没有叫做foo的元素,所以返回&#20540;为None。

☞可逮住你了,在这里find()方法非常容易被误解。在布尔上下文中,如果ElementTree元素对象不包含子元素,其&#20540;则会被认为是False(如果len(element)等于0)。这就意味着if
element.find('...')并非在测试是否find()方法找到了匹配项;这条语句是在测试匹配到的元素是否包含子元素!想要测试find()方法是否返回了一个元素,则需使用if
element.find('...') is not None。



可以在所有派生(descendant)元素中搜索,任意嵌套层次的子元素,孙子元素等…


跳过该代码清单


[隐藏] [在新窗口中打开]>>> all_links = tree.findall('//{http://www.w3.org/2005/Atom}link')  ①
>>> all_links
[<Element {http://www.w3.org/2005/Atom}link at e181b0>,
<Element {http://www.w3.org/2005/Atom}link at e2b570>,
<Element {http://www.w3.org/2005/Atom}link at e2b480>,
<Element {http://www.w3.org/2005/Atom}link at e2b5a0>]
>>> all_links[0].attrib                                              ②
{'href': 'http://diveintomark.org/',
'type': 'text/html',
'rel': 'alternate'}
>>> all_links[1].attrib                                              ③
{'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'}


//{http://www.w3.org/2005/Atom}link与前一样例很相&#20284;,除了开头的两条斜线。这两条斜线告诉findall()方法“不要只在直接子元素中查找;查找的范围可以是任意嵌套层次”。

查询到的第一个结果根元素的直接子元素。从它的属性中可以看出,它是一个指向该feed的html版本的备用链接。

其他的三个结果分别是低一级的备用链接。每一个entry都有单独一个link子元素,由于在查询语句前的两条斜线的作用,我们也能定位到他们。

总的来说,ElementTree的findall()方法是其一个非常强大的特性,但是它的查询语言却让人有些出乎意料。官方描述它为“有限的XPath支持。”XPath是一种用于查询xml文档的W3C标准。对于基础地查询来说,ElementTree与XPath语法上足够相&#20284;,但是如果已经会XPath的话,它们之间的差异可能会使你感到不快。现在,我们来看一看另外一个第三方xml库,它扩展了ElementTree的api以提供对XPath的全面支持。





深入lxml#



lxml是一个开源的第三方库,以流行的libxml2
解析器为基础开发。提供了与ElementTree完全兼容的api,并且扩展它以提供了对XPath 1.0的全面支持,以及改进了一些其他精巧的细节。提供Windows的安装程序;Linux用户推荐使用特定发行版自带的工具比如yum或者apt-get从它们的程序库中安装预编译好了的二进制文件。要不然,你就得手工安装他们了。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> from lxml import etree                   ①
>>> tree = etree.parse('examples/feed.xml')  ②
>>> root = tree.getroot()                    ③
>>> root.findall('{http://www.w3.org/2005/Atom}entry')  ④
[<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>,
<Element {http://www.w3.org/2005/Atom}entry at e2b510>,
<Element {http://www.w3.org/2005/Atom}entry at e2b540>]


导入lxml以后,可以发现它与内置的ElementTree库提供相同的api。

parse()函数:与ElementTree相同。

getroot()方法:相同。

findall()方法:完全相同。

对于大型的xml文档,lxml明显比内置的ElementTree快了许多。如果现在只用到了ElementTree的api,并且想要使用其最快的实现(implementation),我们可以尝试导入lxml,并且将内置的ElementTree作为备用。


try:
   
from lxml import etree
except ImportError:
   
import xml.etree.ElementTree as etree

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


跳过该代码清单


[隐藏] [在新窗口中打开]>>> import lxml.etree                                                                   ①
>>> tree = lxml.etree.parse('examples/feed.xml')
>>> tree.findall('//{http://www.w3.org/2005/Atom}*[@href]')                             ②
[<Element {http://www.w3.org/2005/Atom}link at eeb8a0>,
<Element {http://www.w3.org/2005/Atom}link at eeb990>,
<Element {http://www.w3.org/2005/Atom}link at eeb960>,
<Element {http://www.w3.org/2005/Atom}link at eeb9c0>]
>>> tree.findall(&quot;//{http://www.w3.org/2005/Atom}*[@href='http://diveintomark.org/']&quot;)  ③
[<Element {http://www.w3.org/2005/Atom}link at eeb930>]
>>> NS = '{http://www.w3.org/2005/Atom}'
>>> tree.findall('//{NS}author[{NS}uri]'.format(NS=NS))                                 ④
[<Element {http://www.w3.org/2005/Atom}author at eeba80>,
<Element {http://www.w3.org/2005/Atom}author at eebba0>]


在这个样例中,我使用了import lxml.etree(而非from
lxml import etree),以强调这些特性只限于lxml。

这一句在整个文档范围内搜索名字空间Atom中具有href属性的所有元素。在查询语句开头的//表示“搜索的范围为整个文档(不只是根元素的子元素)。”{http://www.w3.org/2005/Atom}指示“搜索范围仅在名字空间Atom中。” * 表示“任意本地名(local
name)的元素。” [@href]表示“含有href属性。”

该查询找出所有包含href属性并且其&#20540;为http://diveintomark.org/的Atom元素。

在简单的字符串&#26684;式化后(要不然这条复合查询语句会变得特别长),它搜索名字空间Atom中包含uri元素作为子元素的author元素。该条语句只返回了第一个和第二个entry元素中的author元素。最后一个entry元素中的author只包含有name属性,没有uri。

仍然不够用?lxml也集成了对任意XPath 1.0表达式的支持。我们不会深入讲解XPath的语法;那可能需要一整本书!但是我会给你展示它是如何集成到lxml去的。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> import lxml.etree
>>> tree = lxml.etree.parse('examples/feed.xml')
>>> NSMAP = {'atom': 'http://www.w3.org/2005/Atom'}                    ①
>>> entries = tree.xpath(&quot;//atom:category[@term='accessibility']/..&quot;,  ②
...     namespaces=NSMAP)
>>> entries                                                            ③
[<Element {http://www.w3.org/2005/Atom}entry at e2b630>]
>>> entry = entries[0]
>>> entry.xpath('./atom:title/text()', namespaces=NSMAP)               ④
['Accessibility is a harsh mistress']


要查询名字空间中的元素,首先需要定义一个名字空间前缀映射。它就是一个Python字典对象。

这就是一个XPath查询请求。这个XPath表达式目的在于搜索category元素,并且该元素包含有&#20540;为accessibility的term属性。但是那并不是查询的结果。请看查询字符串的尾端;是否注意到了/..这一块?它的意思是,“然后返回已经找到的category元素的父元素。”所以这条XPath查询语句会找到所有包含<category
term='accessibility'>作为子元素的条目。

xpath()函数返回一个ElementTree对象列表。在这篇文档中,只有一个category元素,并且它的term属性&#20540;为accessibility。

XPath表达式并不总是会返回一个元素列表。技术上说,一个解析了的xml文档的dom模型并不包含元素;它只包含结点(node)。依据它们的类型,结点可以是元素,属性,甚至是文本内容。XPath查询的结果是一个结点列表。当前查询返回一个文本结点列表:title元素(atom:title)的文本内容(text()),并且title元素必须是当前元素的子元素(./)。




生成XML#



Python对xml的支持不只限于解析已存在的文档。我们也可以从头来创建xml文档。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> import xml.etree.ElementTree as etree
>>> new_feed = etree.Element('{http://www.w3.org/2005/Atom}feed',     ①
...     attrib={'{http://www.w3.org/XML/1998/namespace}lang': 'en'})  ②
>>> print(etree.tostring(new_feed))                                   ③
<ns0:feed xmlns:ns0='http://www.w3.org/2005/Atom' xml:lang='en'/>


实例化Element类来创建一个新元素。可以将元素的名字(名字空间 &#43; 本地名)作为其第一个参数。当前语句在Atom名字空间中创建一个feed元素。它将会成为我们文档的根元素。

将属性名和&#20540;构成的字典对象传递给attrib参数,这样就可以给新创建的元素添加属性。请注意,属性名应该使用标准的ElementTree&#26684;式,{namespace}localname。

在任何时候,我们可以使用ElementTree的tostring()函数序列化任意元素(还有它的子元素)。

这种序列化结果有使你感到意外吗?技术上说,ElementTree使用的序列化方法是精确的,但却不是最理想的。在本章开头给出的xml样例文档中定义了一个默认名字空间(default namespace)(xmlns='http://www.w3.org/2005/Atom')。对于每个元素都在同一个名字空间中的文档 — 比如Atom
feeds — 定义默认的名字空间非常有用,因为只需要声明一次名字空间,然后在声明每个元素的时候只需要使用其本地名即可(<feed>,<link>,<entry>)。除非想要定义另外一个名字空间中的元素,否则没有必要使用前缀。


对于xml解析器来说,它不会“注意”到使用默认名字空间和使用前缀名字空间的xml文档之间有什么不同。当前序列化结果的dom为:


<ns0:feed xmlns:ns0='http://www.w3.org/2005/Atom' xml:lang='en'/>

与下列序列化的DOM是一模一样的:



<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'/>

实际上唯一不同的只是第二个序列化短了几个字符长度。如果我们改动整个样例feed,使每一个起始和结束标签都有一个ns0:前缀,这将为每个起始标签增加
4 个字符 × 79 个标签 &#43; 4 个名字空间声明本身用到的字符,总共320个字符。假设我们使用UTF-8编码,那将是320个额外的字节。(使用gzip压缩以后,大小可以降到21个字节,但是,21个字节也是字节。)也许对个人来说这算不了什么,但是对于像Atom
feed这样的东西,只要稍有改变就有可能被下载上千次,每一个请求节约的几个字节就会迅速累加起来。


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


跳过该代码清单


[隐藏] [在新窗口中打开]>>> import lxml.etree
>>> NSMAP = {None: 'http://www.w3.org/2005/Atom'}                     ①
>>> new_feed = lxml.etree.Element('feed', nsmap=NSMAP)                ②
>>> 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')  ④
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'/>


首先,定义一个用于名字空间映射的字典对象。其&#20540;为名字空间;字典中的键即为所需要的前缀。使用None作为前缀来定义默认的名字空间。

现在我们可以在创建元素的时候,给lxml专有的nsmap参数传&#20540;,并且lxml会参照我们所定义的名字空间前缀。

如所预期的那样,该序列化使用Atom作为默认的名字空间,并且在声明feed元素的时候没有使用名字空间前缀。

啊噢… 我们忘了加上xml:lang属性。我们可以使用set()方法来随时给元素添加所需属性。该方法使用两个参数:标准ElementTree&#26684;式的属性名,然后,属性&#20540;。(该方法不是lxml特有的。在该样例中,只有nsmap参数是lxml特有的,它用来控制序列化输出时名字空间的前缀。)

难道每个xml文档只能有一个元素吗?当然不了。我们可以创建子元素。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> title = lxml.etree.SubElement(new_feed, 'title',          ①
...     attrib={'type':'html'})                               ②
>>> 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>
>>> 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>


给已有元素创建子元素,我们需要实例化SubElement类。它只要求两个参数,父元素(即该样例中的new_feed)和子元素的名字。由于该子元素会从父元素那儿继承名字空间的映射关系,所以这里不需要再声明名字空间前缀。

我们也可以传递属性字典给它。字典的键即属性名;&#20540;为属性的&#20540;。

如预期的那样,新创建的title元素在Atom名字空间中,并且它作为子元素插入到feed元素中。由于title元素没有文件内容,也没有其子元素,所以lxml将其序列化为一个空元素(使用/>)。

设定元素的文本内容,只需要设定其.text属性。

当前title元素序列化的时候就使用了其文本内容。任何包含了<或者&符号的内容在序列化的时候需要被转义。lxml会自动处理转义。

☞你也许也想要看一看xmlwitch,它也是用来生成xml的另外一个第三方库。它大量地使用了with语句来使生成的xml代码更具可读性。






解析破损的XML#



xml规范文档中指出,要求所有遵循xml规范的解析器使用“严厉的(draconian)错误处理”。即,当它们在xml文档中检测到任何编排良好性(wellformedness)错误的时候,应当立即停止解析。编排良好性错误包括不匹配的起始和结束标签,未定义的实体(entity),非法的Unicode字符,还有一些只有内行才懂的规则(esoteric
rules)。这与其他的常见&#26684;式,比如html,形成了鲜明的对比 — 即使忘记了封闭html标签,或者在属性&#20540;中忘了转义&字符,我们的浏览器也不会停止渲染一个Web页面。(通常大家认为html没有错误处理机制,这是一个常见的误解。html的错误处理实际上被很好的定义了,但是它比“遇见第一个错误即停止”这种机制要复杂得多。)


一些人(包括我自己)认为xml的设计者强制实行这种严&#26684;的错误处理本身是一个失误。请不要误解我;我当然能看到简化错误处理机制的优势。但是在现实中,“编排良好性”这种构想比乍听上去更加复杂,特别是对xml(比如Atom
feeds)这种发布在网络上,通过http传播的文档。早在1997年xml就标准化了这种严厉的错误处理,尽管xml已经非常成熟,研究一直表明,网络上相当一部分的Atom
feeds仍然存在着编排完整性错误。


所以,从理论上和实际应用两种角度来看,我有理由“不惜任何代价”来解析xml文档,即,当遇到编排良好性错误时,不会中断解析操作。如果你认为你也需要这样做,lxml可以助你一臂之力。


以下是一个破损的xml文档的片断。其中的编排良好性错误已经被高亮标出来了。


<?xml version='1.0' encoding='utf-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'>
  
<title>dive into &hellip;</title>
...
</feed>

因为实体&hellip;并没有在xml中被定义,所以这算作一个错误。(它在html中被定义。)如果我们尝试使用默认的设置来解析该破损的feed,lxml会因为这个未定义的实体而停下来。


>>> import lxml.etree
>>> tree = lxml.etree.parse('examples/feed-broken.xml')
Traceback (most recent call last):
File &quot;<stdin>&quot;, line 1, in <module>
File &quot;lxml.etree.pyx&quot;, line 2693, in lxml.etree.parse (src/lxml/lxml.etree.c:52591)
File &quot;parser.pxi&quot;, line 1478, in lxml.etree._parseDocument (src/lxml/lxml.etree.c:75665)
File &quot;parser.pxi&quot;, line 1507, in lxml.etree._parseDocumentFromURL (src/lxml/lxml.etree.c:75993)
File &quot;parser.pxi&quot;, line 1407, in lxml.etree._parseDocFromFile (src/lxml/lxml.etree.c:75002)
File &quot;parser.pxi&quot;, line 965, in lxml.etree._BaseParser._parseDocFromFile (src/lxml/lxml.etree.c:72023)
File &quot;parser.pxi&quot;, line 539, in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:67830)
File &quot;parser.pxi&quot;, line 625, in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:68877)
File &quot;parser.pxi&quot;, line 565, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:68125)
lxml.etree.XMLSyntaxError: Entity 'hellip' not defined, line 3, column 28

为了解析该破损的xml文档,忽略它的编排良好性错误,我们需要创建一个自定义的xml解析器。


跳过该代码清单


[隐藏] [在新窗口中打开]>>> parser = lxml.etree.XMLParser(recover=True)                  ①
>>> tree = lxml.etree.parse('examples/feed-broken.xml', parser)  ②
>>> parser.error_log                                             ③
examples/feed-broken.xml:3:28:FATAL:PARSER:ERR_UNDECLARED_ENTITY: Entity 'hellip' not defined
>>> tree.findall('{http://www.w3.org/2005/Atom}title')
[<Element {http://www.w3.org/2005/Atom}title at ead510>]
>>> title = tree.findall('{http://www.w3.org/2005/Atom}title')[0]
>>> title.text                                                   ④
'dive into '
>>> print(lxml.etree.tounicode(tree.getroot()))                  ⑤
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'>
  
<title>dive into </title>
.
. [rest of serialization snipped for brevity]
.



实例化lxml.etree.XMLParser类来创建一个自定义的解析器。它可以使用许多不同的命名参数。在此,我们感兴趣的为recover参数。当它的&#20540;被设为True,xml解析器会尽力尝试从编排良好性错误中“恢复”。

为使用自定的解析器来处理xml文档,将对象parser作为第二个参数传递给parse()函数。注意,lxml没有因为那个未定义的&hellip;实体而抛出异常。

解析器会记录它所遇到的所有编排良好性错误。(无论它是否被设置为需要从错误中恢复,这个记录总会存在。)

由于不知道如果处理该未定义的&hellip;实体,解析器默认会将其省略掉。title元素的文本内容变成了'dive
into '。

从序列化的结果可以看出,实体&hellip;并没有被移到其他地方去;它就是被省略了。

在此,必须反复强调,这种“可恢复的”xml解析器没有互用性(interoperability)保证。另一个不同的解析器可能就会认为&hellip;来自html,然后将其替换为&amp;hellip;。这样“更好”吗?也许吧。这样“更正确”吗?不,两种处理方法都不正确。正确的行为(根据xml规范)应该是终止解析操作。如果你已经决定不按规范来,你得自己负责。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-130946-1-1.html 上篇帖子: windows OpenCV 2.3.1 Python 2.7配置 下篇帖子: Install Python2.5 (including tkinter)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表