饭否消息解析之从minidom到xpath
抛板砖,引白玉:为何不用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只能解析实体内容,不能”囫囵吞枣”地解析。例如:
1 2 3 | <li> <a href='http://a.com'>hello world</a> </li> |
在view xpath 下,使用/li/a,得到的是
1 | <a href='http://a.com'>hello world</a> |
全部内容;
但是在python下,使用
1 | 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下把上述所有的事情都处理掉,是最好的。
经过了一点点的修补、改进,最终的饭否消息程序如下(核心代码部分):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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),也是进步的必然。我不是说使用正则式就低级——我从来没有说过诸如此类的话,不论是对正则表达式,还是对正则表达式的使用者;事实上,正则表达式一直是我的箧中飞刃;我爱正则表达式!——只是说,不同的工具在合适的场合,有不同的效用。不单要知道某种工具的缺点以便能够避其短,更重要的是要知道它的优点以便扬其长。这样才能从容地调兵遣将,手下无不可用之工具。
相关链接:
- W3C关于XPath的介绍
- xpath教程,有中文版,图文并茂,清晰易懂。
- 4suite,python的xpath套件
- perl其实也有xpath的。未测试试。
- XPath Checker
RegEx好比树叶,Xpath好比树枝。
RegEx可以精确描述树叶肥瘦大小颜色,树叶太多太杂的时候则是Xapth顺藤摸瓜,快速定位。都是居家旅行常备工具。
看到你的文章有感而发,哈哈。博客很专业有内涵,以后有问题还要多请教。
[Reply]
.rex Reply:
March 31st, 2009 at 1:08 am
比喻很形象,呵呵。
欢迎常来。
[Reply]