xpath在XHTML解析中的应用 XHTMLPythonjQuery正则表达式F#
前一阵参加了一个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格式化功能?
上一篇: Java中static的特点