欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

xpath在XHTML解析中的应用 XHTMLPythonjQuery正则表达式F# 

程序员文章站 2024-03-04 17:41:53
...

前一阵参加了一个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格式化功能?