xpath在XHTML解析中的应用

前一阵参加了一个Python的活动,其间老董的讲座是讨论网页爬虫技术的。其中提到了一下关于页面解析的问题,他推荐了三种技术。其中有用到libxml2里的xpath来处理,我就跟令狐谈到我曾经也用过这个东东。令狐建议我把这个东东说一下,于是我就写了这一篇。

惭 愧的是我最初在python里用xpath时用的不是libxml2,而是一个不记得是什么的XML库。后来因为那个库不知道为什么找不到了或者是新版本 不再提供xpath支持等原因,才通过google找到libxml2。就像对pcre的误解一样,我原来还以为libxml2是python的库,后来 才知道它是C的库,我用的只是python的包装而已。

很多对XML接触不多的人,刚开始的印象都会觉得XML不就是一种数据存储方式嘛,但是实际上在一些极端XMLer看来,XML是一种强大的编程语言。不相信的话试着写一个XSL就知道了。我对xpath的了解其实也是源于多年写的一个程序需要而对XSLT作了点研究。

关于xpath的权威资料,当然要算W3C的XPath官方文档

下面以一个例子来说明吧。假设现在我们需要解析这样一个“页面 ”,取出其中所有“档案”段的内容,并且解析为一个个月份值。这个功能当然也可以用正则表达来式来实现——这也是老董提到的三种技术之一——而且事实上并不会比用xpath麻烦,不过这里只是举例说明,就不这么讲究了。

首先,我们需要安装一个支持xpath的python库。目前在libxml2的网站上被推荐的python binding是lxml(我以前用的不是这个,不过我也不记得是哪个了),所以现在就以这个为例吧。

安装方法很简单: easy_install lxml 即可——什么?你不知道什么叫easy_install?那就猛戳PEAK这里 学习一下吧。

个么然后是不是就可以直接用了呢?试试看吧:

import codecs
from lxml import etree

f=codecs.open("raptor.htm","r","utf-8")
tree=etree.parse(f)

很不幸,可耻滴失败鸟。因为这个页面并不是一个qualified的XHTML——别说中国了,就算在外国也没有那么多合格的XHTML页面,所以还是要可耻滴操起RE(正则表达式)来做一个预处理。

# encoding=utf-8
import codecs
import re
from StringIO import StringIO
from lxml import etree

f=codecs.open("raptor.htm","r","utf-8")
content=f.read()
f.close()
block_pattern=re.compile(u"<h3>档案</h3>(.*?)<h3>", re.I | re.S)
m=block_pattern.findall(content)
f=StringIO(m[0])
tree=etree.parse(f)

这回就不出错了嘛。现在被etree所处理的XML其实就是这么一段内容,很简单。

<ul>
<li><a id="_ctl0_LeftColumn-5_Categories_CatList__ctl1_LinkList__ctl1_Link" href="http://borland.mblogger.cn/raptor/archive/072009.aspx">2009年7月 (1)</a></li>
...
</ul>

在jQuery里要处理这种东西就很简单了,比如这样:

$("li").each(function(){...});

但是在python里要是用RE来处理就略麻烦一些,比如这样:

...
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要怎么做呢?是这样的:

...
m=block_pattern.findall(content)
f=StringIO(m[0])
tree=etree.parse(f)
nodes=tree.xpath("/ul/li")
for n in nodes:
print n.getchildren()[0].text

看上去不比用RE简单嘛,那为什么要用xpath呢?

这次换个需求,比如这次要取的是id为“_ctl0_LeftColumn-5_Categories_CatList__ctl1_LinkList__ctl4_Link”的链接。

jQuery版:

$("#_ctl0_LeftColumn-5_Categories_CatList__ctl1_LinkList__ctl4_Link").text();

RE版:

...
m=block_pattern.findall(content)
id_pattern=re.compile(u"<a[^>]*id=/"_ctl0_LeftColumn-5_Categories_CatList__ctl1_LinkList__ctl4_Link/"[^>]*>(.*?)</a>", re.I | re.S)
print id_pattern.findall(m[0])[0]

xpath版:

...
m=block_pattern.findall(content)
f=StringIO(m[0])
tree=etree.parse(f)
nodes=tree.xpath("//a[@id='_ctl0_LeftColumn-5_Categories_CatList__ctl1_LinkList__ctl4_Link']")
print nodes[0].text

对 比三段代码与前一个版本的区别,应该可以看出xpath和jQuery对于页面的解析都是基于XML的语义进行,而RE则纯粹是基于plain text,如果页面结构复杂度较高的时候(比如一堆的DIV来回嵌套之类),设计一个恰当的RE pattern可能会远比写一个xpath要复杂。

当然,xpath的最大问题就是对页面要求比较高,必须是一个合格的XHTML,否则还是需要用一个RE去取出合格的片段来进行处理。不知道有没有什么库可以提供页面的XHTML格式化功能?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值