抛板砖,引白玉:为何不用xpath,什么是xpath?

最近拾起了以前的小项目,在完善上篇文章发布后,“那个谁”的回复让我很感兴趣。他问,“为什么不用xpath?”

xpath是什么东东?我反问。反问之前,当然少不了先google一番,以免……那个啥。

首先映入眼帘的是w3c ,对xpath的介绍如下:

XPath is a language for addressing parts of an XML document, designed to be used by both XSLT and XPointer.

直译为中文就是,

XPath 是一种语言,用于在XML文档中定位各部分内容,可由XSLT或XPointer调用。

还搜索到xpath的教程,在这里。草草看过,当时并未着意。

虽如此,但是python里的minidom模块,也有此功效呀。为什么非要使用xpath呢?尤其是考虑到在python中还需要额外安装,不如minidom之放之四海而皆可运行。

跟那个谁再交流,意见仍是“力荐”。还推荐我细读教程,并在firefox里使用XPath Checker插件。

于是就照办了。

发硎新试,其快可知

一试XPath Checker,果然石破天惊。选中部分网页文字后,在右键菜单中选”View Xpath”,立即显示出该节点的XPath路径。层次清晰,定位精准。只是我对其语法尚未了了。于是细读教程,边学边用;半小时后,已经能够运用到之前写的饭否信息抓取程序上。虽然写代码还有些吃力,但是思路很清晰,不会纠缠于细节中无法脱身。

那个谁还提议,一般的html文档不是标准的xml文档,因此用xpath解析时,最好格式化一下。

我也注意到这个问题了。从饭否html中取出的有用内容,只占全文的一小部分;额外的部分白白拖慢速度,增强析取难度。

经过实验,我将原代码改进如下:

1. 仍用原来的minidom模块下载、分析文档,只取<ol>与</ol>之间的部分。这部分保存成字符串格式,备用。只取需要的那部分,使结构清晰,层次浅显。

2. 使用xpath来解析上一步取出的字串。

到现在,/,//,@,[],=,等等,每个符号都从原来的meaningless变成helpful,在我的工具箱中有了合适的位置,随取随用,十分方便。我已经成了xpath的受益者。现在才觉得学习xpath真是很有趣、有用。

目前还有个小问题,无法使用纯粹的xpath语法解决。问题描述如下:

xpath只能解析实体内容,不能”囫囵吞枣”地解析。例如:

<li>
	<a href='http://a.com'>hello world</a>
</li>

在view xpath 下,使用/li/a,得到的是

<a href='http://a.com'>hello world</a>

全部内容;

但是在python下,使用

method=doc.xpath(u'''string(/li/a)''')

虽然,也能通过/li/a/@href得到’http://a.com’的内容。

却只能得到hello world。xpath把所有的<>之内的东西给消灭掉了。很诡异。

遇到这种情况,如果我想得到整条的信息,就使用list.childNodes[index-1].firstChild.toxml()[22:-7]这种变通方式。不过,之前的doc = Parse(str(list.toxml()))我觉得用得挺好,是自己的一个”创举”,在程序中再度使用一下传统的xml解析方式,也无可厚非。当然,如果能够在xpath下把上述所有的事情都处理掉,是最好的。

经过了一点点的修补、改进,最终的饭否消息程序如下(核心代码部分):

    def __getMsgByPage__(self,page):
 
        url="http://fanfou.com/"+self.user+"/p."+str(page)
        node = minidom.parse(urllib2.urlopen(url))
        list = node.getElementsByTagName("ol")[0]
        doc = Parse(str(list.toxml()))
        cu=self.sql.cursor()
        max=doc.xpath(u'''count(/ol/li)''')
        max=int(max)+1
        if max==1:
            return 0
        max=int(max)
        for index in range(1,max):
            method=doc.xpath(u'''string(/ol/li[%d]//span[@class='method'])''' % index)[2:]
            method=method.replace(' ','')
            if method=="彩信":
                time=doc.xpath(u'''string(/ol/li[%d]//span[@class="time"]/@title)'''\
                        % index)
                uuid=doc.xpath(u'''string(/ol/li[%d]//a[@class='photo']/@href)'''\
                        % index) [-11:]
            else:
                time=doc.xpath(u'''string(/ol/li[%d]//a[@class='time']/@title)'''\
                        % index)
                uuid=doc.xpath(u'''string(/ol/li[%d]//a[@class='time']/@href)''' % index)[-11:]
 
            content = list.childNodes[index-1].firstChild.toxml()[22:-7]
            # content, uuid, time, method are now available for further use.

最关键的代码,只有几行而已。省掉了原来长篇累牍的coding。效率也错,我将自己近3000条饭否消息批量下载,共150余页,历时86秒。饭否服务器也很给面子,中途没有封锁我。

总结一下:Xpath很适合在xml中定位各部分内容,定位精准,描述性极佳,是xml中的搜索利器。经常做xml解析的,不妨尝试一把。

个人感言

从纯手工正则表达式解析,到使用minidom解析,再到使用xpath,看似弯路,其实蛮有收获。从自己事必躬亲精确控制每一个细节(用手工作),再到借助工具实现一部分功能(手脑并用),再到完全用合适的工具来处理全部事情(用脑工作),似乎正是良性的发展路径。自豪地说,由于我已经使用过纯手工正则表达式的解析,即使现有的工具不适合我,我进可攻,退可守;我知道解析的细节,现有的工具(好看的封装而已嘛)骗不了我,即使它包装得再好,还是正则表达式在作引擎(曾经读过python处理xml的相关库文件的python代码,感谢开源);从追求实现(it works!)到追求卓越的实现(the excellent solution),也是进步的必然。我不是说使用正则式就低级——我从来没有说过诸如此类的话,不论是对正则表达式,还是对正则表达式的使用者;事实上,正则表达式一直是我的箧中飞刃;我爱正则表达式!——只是说,不同的工具在合适的场合,有不同的效用。不单要知道某种工具的缺点以便能够避其短,更重要的是要知道它的优点以便扬其长。这样才能从容地调兵遣将,手下无不可用之工具。

相关链接: