如何使用xpath表达式解析HTML和XML文档

html-css010

如何使用xpath表达式解析HTML和XML文档,第1张

看下面一些例子:

1)获取子节点:getchildren()等价于child::*

>>>doc.getchildren()

2)获取当前节点:"."等价于self::node()

>>>doc.xpath(".")

>>>doc.xpath("self::node()")

3)获取父节点:".."等价于parent::node()

>>>doc.head.xpath("..")

>>>doc.head.xpath("parent::node()")

4)ancestor轴和descendant轴

分别代表当前元素所有祖先元素、所有后代元素,比如:

>>>meta.xpath('ancestor::*')

>>>meta.xpath('ancestor::head')

>>>doc.xpath('descendant::table')

>>>doc.xpath('descendant::table[@id="tcdatafields"]')

>>>doc.xpath('//table[@id="tcdatafields"]')

5)ancestor-or-self和descendant-or-self轴

分别表示当前元素或其所有祖先元素、当前元素或其所有后代元素,比如:

>>>meta.xpath('ancestor-or-self::*')

,,]

6)child和parent轴

分别表示当前元素所有子元素、父元素:

>>>doc.xpath('child::head')

>>>head.xpath('child::meta[1]')

>>>head.xpath('child::meta[position()<3]')

7)attribute轴

表示当前元素的所有属性,例如下面是meta元素的name和content两个属性以及取值:

>>>meta.items()

[('name','googlebot'),('content','index,follow')]

获取所有属性取值:

>>>meta.xpath('attribute::*')

['googlebot','index,follow']

获取name属性的取值:

>>>meta.xpath('attribute::name')

['googlebot']

8)following和preceding

分别表示当前元素的所有后继元素、前置元素,比如:

>>>meta.xpath('following::*')

>>>meta.xpath('preceding::*')

9)following-sibling和preceding-sibling轴

分别表示当前元素的所有平级后继元素、平级前置元素,比如:

>>>meta.xpath('preceding-sibling::*')

>>>meta.xpath('following-sibling::*')

10)self轴

表示当前元素自身

>>>doc.xpath("self::*")

使用谓词(predicates)

谓词就是step中使用中括号[...]定义的那部分,使用谓词能实现精确查找,看下面的例子:

>>>doc.xpath('/html/head/meta')

,,,,,,]

1)位置谓词

>>>doc.xpath('/html/head/meta[1]')

>>>doc.xpath('/html/head/meta[2]')

>>>doc.xpath('/html/head/meta[last()]')

>>>doc.xpath('/html/head/meta[last()-1]')

>>>doc.xpath('/html/head/meta[position()<3]')

注:这里使用了last()和position()两个函数,xpath还支持更多的函数,结合这些函数可以获得非常强大的处理能力。

2)属性谓词

含有属性name的meta元素:

>>>doc.xpath('/html/head/meta[@name]')

,,,]

含有属性name而且其取值为robots的meta元素:

含有任意属性的meta元素:

>>>doc.xpath('/html/head/meta[@*]')

3)函数谓词

xpath内置很多函数,灵活使用这些函数,可以极大提升查找效率,比如:

-使用text()函数

>>>doc.xpath('//td[text()="2017-03-21"]')

-使用contains函数

>>>[td.textfortdindoc.xpath('//td[contains(text(),"2017-03-2")]')]

['2017-03-29','2017-03-28','2017-03-27','2017-03-24','2017-03-23','2017-03-22','2017-03-21','2017-03-20']

-使用starts-with函数

>>>[td.textfortdindoc.xpath('//td[starts-with(text(),"2017-02-2")]')]

['2017-02-28','2017-02-27','2017-02-24','2017-02-23','2017-02-22','2017-02-21','2017-02-20']

>>>[td.textfortdindoc.xpath('//td[text()>21.0andtext()<23.0]')]

['21.02']

>>>[td.textfortdindoc.xpath('//td[text()<-2.5ortext()>21.0]')]

['21.02','-2.64']

通配符

xpath也支持通配符"*",其中'*"可以匹配任何标签元素,"@*"可以匹配任何元素属性,node()可以匹配任何节点:

>>>head.xpath('./*')

,,,,,,,,,]

>>>head.xpath('./meta[@*]')

>>>head.xpath('./node()')

在进行网页抓取的时候,分析定位html节点是获取抓取信息的关键,目前我用的是lxml模块(用来分析XML文档结构的,当然也能分析html结构), 利用其lxml.html的xpath对html进行分析,获取抓取信息。

首先,我们需要安装一个支持xpath的python库。目前在libxml2的网站上被推荐的python binding是lxml,也有beautifulsoup,不嫌麻烦的话还可以自己用正则表达式去构建,本文以lxml为例讲解。

假设有如下的HTML文档:

1 <html>2   <body>3     <form>4       <div id='leftmenu'>5         <h3>text</h3>6         <ul id=’china’><!-- first location -->7           <li>...</li>8           <li>...</li>9              ......10         </ul>11         <ul id=’england’><!-- second location-->12           <li>...</li>13           <li>...</li>14              ......15         </ul>16       </div>17     </form>18   </body>19 </html>       

直接使用lxml处理:

1 import codecs2 from lxml import etree3 f=codecs.open("ceshi.html","r","utf-8")4 content=f.read()5 f.close()6 tree=etree.HTML(content)

etree提供了HTML这个解析函数,现在我们可以直接对HTML使用xpath了,是不是有点小激动,现在就尝试下吧。

在使用xpath之前我们先来看看作为对照的jQuery和RE。

在jQuery里要处理这种东西就很简单,特别是假如那个ul节点有id的话(比如是<ul id=’china’>):

$("#china").each(function(){...})

具体到此处是:

$("#leftmenu").children("h3:contains('text')").next("ul").each(function(){...})

找到id为leftmenu的节点,在其下找到一个内容包含为”text”的h3节点,再取其接下来的一个ul节点。

在python里要是用RE来处理就略麻烦一些:

block_pattern=re.compile(u"<h3>档案</h3>(.*?)<h3>", re.I | re.S)

m=block_pattern.findall(content)

item_pattern=re.compile(u"<li>(.*?)</li>", re.I | re.S)

items=item_pattern.findall(m[0])for i in items:    print i

那么用xpath要怎么做呢?其实跟jQuery是差不多的:

nodes=tree.xpath("/descendant::ul[@id='china']")

当然,现在没有id的话也就只能用类似于jQuery的方法了。完整的xpath应该是这样写的(注意,原文件中的TAG有大小写的情况,但是在XPATH里只能用小写):

nodes=tree.xpath(u"/html/body/form/div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")

更简单的方法就是像jQuery那样直接根据id定位:

nodes=tree.xpath(u"//div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")

这两种方法返回的结果中,nodes[0]就是那个“text”的h3节点后面紧跟的第一个ul节点,这样就可以列出后面所有的ul节点内容了。

如果ul节点下面还有其他的节点,我们要找到更深节点的内容,如下的循环就是把这些节点的文本内容列出:

nodes=nodes[0].xpath("li/a")for n in nodes:    print n.text

对比三种方法应该可以看出xpath和jQuery对于页面的解析都是基于XML的语义进行,而RE则纯粹是基于plain

text。RE对付简单的页面是没有问题,如果页面结构复杂度较高的时候(比如一堆的DIV来回嵌套之类),设计一个恰当的RE

pattern可能会远比写一个xpath要复杂。特别是目前主流的基于CSS的页面设计方式,其中大部分关键节点都会有id――对于使用jQuery的页面来说则更是如此,这时xpath相比RE就有了决定性的优势。

附录:基本XPATH语法介绍,详细请参考XPath的官方文档

XPATH基本上是用一种类似目录树的方法来描述在XML文档中的路径。比如用“/”来作为上下层级间的分隔。第一个“/”表示文档的根节点(注意,不是指文档最外层的tag节点,而是指文档本身)。比如对于一个HTML文件来说,最外层的节点应该是”/html”。

同样的,“..”和“.”分别被用来表示父节点和本节点。

XPATH返回的不一定就是唯一的节点,而是符合条件的所有节点。比如在HTML文档里使用“/html/head/scrpt”就会把head里的所有script节点都取出来。

为了缩小定位范围,往往还需要增加过滤条件。过滤的方法就是用“[”“]”把过滤条件加上。比如在HTML文档里使用“/html/body/div[@id='main']”,即可取出body里id为main的div节点。

其中@id表示属性id,类似的还可以使用如@name, @value, @href, @src, @class….

函数text()的意思则是取得节点包含的文本。比如:<div>hello<p>world</p><

/div>中,用”div[text()='hello']“即可取得这个div,而world则是p的text()。

函数position()的意思是取得节点的位置。比如“li[position()=2]”表示取得第二个li节点,它也可以被省略为“li[2]”。

不过要注意的是数字定位和过滤

条件的顺序。比如“ul/li[5][@name='hello']”表示取ul下第五项li,并且其name必须是hello,否则返回空。而如果用

“ul/li[@name='hello'][5]”的意思就不同,它表示寻找ul下第五个name为”hello“的li节点。

此外,“*”可以代替所有的节点名,比如用”/html/body/*/span”可以取出body下第二级的所有span,而不管它上一级是div还是p或是其它什么东东。

“descendant::”前缀可以指代任意多层的中间节点,它也可以被省略成一个“/”。比如在整个HTML文档中查找id为“leftmenu”的

div,可以用“/descendant::div[@id='leftmenu']”,也可以简单地使用“

//div[@id='leftmenu']”。

至于“following-sibling::”前缀就如其名所说,表示同一层的下一个节点。”following-sibling::*”就是任意下一个节点,而“following-sibling::ul”就是下一个ul节点。