XPath与正则表达式在文本数据提取时该如何选择?

  从互联网上下载到网页,只是咱们迈向成功的第一步。拿到网页数据之后,咱们须要从中提取咱们想要的具体信息,html

好比标题、内容、时间、做者等。最多见的的提取方式有两种:XPath和正则表达式。node

  先简单介绍一下XPATH和正则表达式。python

  XPath即为 XML 路径语言(XML Path Language),它是一种用来肯定XML文档中某部分位置的语言。 XPath基于正则表达式

XML的树状结构,提供在数据结构树中找寻节点的能力(见维基百科 XPath)。数据结构

  正则表达式英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正post

则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。优化

 

  发表一下我的看法:网站

  XPath主要是用来处理 XML 格式文档的,它是基于 XML 文档的层次结构来肯定一个到达指定节点的路径的,所以特别url

适合处理这种层级结构明显的数据(起初 XPath 的提出的初衷是将其做为一个通用的、介于 XPointer 与 XSLT 间的语法模型。spa

可是 XPath 很快的被开发者采用来看成小型查询语言)。

  正则表达式能够处理任何格式的字符串文档,它是基于文本的特征来匹配、查找指定数据的。

 

  在网上看到一个很形象的比喻:若是提取信息就像找一个建筑,那么正则表达式就是告诉你,这个建筑的左边是什么、右

边是什么、以及这个建筑自己有哪些特征,但这样的描述在全国范围内可能有不少地方的建筑都符合条件,找起来仍是不太方

便,除非你限制范围,好比指定北京海淀区等。而 XPath 就是告诉你这个建筑在中国-北京-海淀区-中关村-中关村大街-1

号,这样找起来就方便了不少,固然这不是说XPath就比正则表达式要好用,具体选择还得看应用场景,好比让你在一个广场

上等人,告诉对方你在哪里的时候,你总不会说我在广场上从东数第23块砖从北数第16块砖上站着吧,你极可能会说我在一个

雕像旁边或喷泉旁边等。下面主要说一下在爬虫中通常如何选择使用XPath和正则表达式。

 

  在写爬虫的时候,通常会遇到 HTML、JSON、XML、纯文本等格式的文档,先来讲一下 HTML 和 XML,HTML 是 XML

的一个子集,所以它们的处理方式是同样的,首选使用 XPath 来获取信息,以博客园首页为例,若是咱们须要的数据是文章

的 url 列表,最好使用 XPath,见图

 

  文章 url 列表自己在视觉上是一个结构化特别明显的数据,并且经过分析 HTML DOM 树,咱们发现须要采集的a标签的

层次规律特别整齐,很容易就能使用XPath语法来标示出a标签的路径,python代码示例

#coding:utf8
from lxml import etree
import requests

url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

root = etree.HTML(html)
node_list = root.xpath("//div[@class='post_item_body']/h3/a")

for node in node_list:
    print node.attrib['href']


# 输出
'''
http://www.cnblogs.com/olivers/p/6073506.html
http://www.cnblogs.com/-free/p/6073496.html
...
'''

   若是使用正则表达式的话,也能够达成一样的效果,不过,要转换一下思路。因为正则表达式是使用特征匹配的,所以

最基本的思路有两个,1、是基于url自己的特征;2、基于url所在位置的特征。(其实看到这里,你就应该对该使用正则表

达式仍是XPath有所体会了,使用XPath想都不用想,正则表达式还得想一下子)下面咱们来分析一下,第一个思路基本行不

通,因为网页上并非只有咱们想要的这些主要的文章url,还有一些推荐的文章url。见图

 

  而这些url自己是没有什么明显的特征区别的,区别只在于位置和人为赋予的这个位置的意义,所以基于url自己特征来使

用正则表达式是不可行的。第二个思路基于位置特征,见图

 

  经过对比推荐和非推荐的url的a标签,能够找出一个特征就是,非推荐的url的a标签的class属性都是 "titlelnk",所以咱们

能够基于此构造正则表达式,python示例代码

#coding:utf8
import re
import requests
      
url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

urls = re.findall('class=\"titlelnk\"\s*[^>]*?href=\"([^\"]+)', html)

for url in urls:
    print url
         
# 输出
''' 
http://www.cnblogs.com/olivers/p/6073506.html
http://www.cnblogs.com/-free/p/6073496.html
...
'''

 

  固然使用正则表达式还有其余不少的形式,使用XPath也有不少其余的形式,但经过这个例子,咱们能够体会到在采集文

章列表时,使用XPath是比较方便的。而后让咱们改一下需求,如今咱们须要采集文章的发布时间,见图

 

  这个很明显咱们仅使用普通的XPath是没法取到时间的,会带上不少额外的字符,但太复杂的XPath写起来挺麻烦的,因

此咱们能够经过先取到带额外字符的时间,再对数据作清洗来获得时间。而后再看一下使用正则表达式,因为时间的格式很固

定,而且网页中不存在会干扰到咱们的时间,所以正则写起来就比较简单了,并且还不用过滤。python示例代码

#coding:utf8
import re
from lxml import etree 
import requests

url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

# XPath 用法
root = etree.HTML(html)
time_node_list = root.xpath("//div[@class='post_item_foot']/text()")
# 这个XPath匹配到的数据也是不对的,存在不少空白节点
# 咱们来处理一下
# 删除空白节点 并去除空格
time_node_list = [node.strip() for node in time_node_list if node.strip()]
# 提取时间字符串
time_list = [' '.join(node.split()[1:]) for node in time_node_list]

for time_str in time_list:
    print time_str
               
               
# 输出
'''
2016-11-17 15:23
2016-11-17 15:10
2016-11-17 14:34
2016-11-17 11:58
...
'''

# 正则用法
time_list = re.findall('(\d+-\d+-\d+\s*\d+:\d+)', html)
# 这样写是不对的,由于在源码中还有不少时间格式存在于标签中,所以对正则优化以下
time_list = re.findall(u'发布于\s*(\d+-\d+-\d+\s*\d+:\d+)', html)

for time_str in time_list:
    print time_str

# 输出
'''
2016-11-17 15:23
2016-11-17 15:10
2016-11-17 14:34
2016-11-17 11:58
...
'''

 

  在这个例子中咱们其实就能够体会到XPath的一些局限性了,下面咱们再来看一个XPath基本作不到的例子。

  需求改成获取页面中全部的英文单词。首先分析一下,这个需求能够说和页面的结构彻底不要紧,XPath这种基于页面层

次结构的语法能够说彻底没用,但使用正则的话却能够轻易达到目的。python示例代码

#coding:utf8
import re
from lxml import etree 
import requests

url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

# 首先替换掉源码中的各类标签
html = re.sub('<[^>]+/?>', '', html)

# 匹配英文单词
words = re.findall("[a-z]+", html, re.I)

print len(words)
print words[:10]

# 输出
'''
303
[u'DDD', u'Connect', u'Connect', u'Mac', u'Visual', u'Studio', u'MSSQL', u'Server', u'on', u'Linux']
'''

 

  在这三个例子中,第一个和第二个都会涉及到XPath和正则表达式的选择问题,第三个也会涉及到选择,但几乎立马就放

弃了XPath,下面咱们来总结一下 XPath 和正则表达式到底该如何选用。

  第一个例子中,目标数据所在的位置能够很方便的用 XPath 语法标示,而且不会产生误匹配。而使用正则表达式的时候

却可能会发生误匹配,须要收集更多的特征来保证匹配的准确性,这无疑就增长了复杂度,所以推荐使用XPath。第二个例子

中,目标数据所在位置使用XPath来不能彻底匹配,存在多余数据,须要进一步的处理。但使用正则表达式的时候,却能够一

步到位,节省了工做量,所以推荐使用正则表达式。

  也就是说,首先要分清目标数据是层次结构明显仍是特征明显,这个分清楚了,该选什么也就基本肯定了,后续要思考的

其实算是优化的步骤。

  简单来讲,在XPath和正则表达式均可以使用的状况下,选择的标准有这么几个(按优先级排序):

    1. 匹配准确度
    2. 语法复杂度(思考花费时间长短)
    3. 后续处理复杂度
    4. 可维护性
    5. 可读性

  标准只是死的,它不会告诉你该如何去应用,其实通常状况下我是这么作的:

      1. 首先明白目的是什么(拿到准确的目标数据),而后想一下使用XPath的话,加上后续处理须要写几行代码?大概

    估计一下,再估计一下使用正则须要写几行代码,这时候你差很少就有个判断了

      2.而后再想一下万一目标网站改了一下格式,我须要维护的话,怎么改起来方便?(这个推荐使用XPath,可读性和

    可维护行都是比较好的,正则表达式的可读性并不怎么好)

  其实在真正的工做中,一个网页通常须要提取不少个字段,所以XPath和正则表达式混用是很常常的,这个选择只是针对

匹配具体字段而言的。

  若是想让代码达到最优,须要考虑的东西还有不少,好比XPath和正则表达式的执行效率,通常状况下,正则表达式的效

率是比较高的,这个前提是不太复杂的正则表达式,有些特别复杂的正则表达式可能严重减慢执行效率。

 

 

 

参考文章:

《用 python 写的爬虫,有哪些提升的技能?》 https://www.zhihu.com/question/36832667

【Python爬虫】入门知识 http://www.jianshu.com/p/74b94eadae15

《xpath与正则表达式抽取网页信息的速度比较》http://pcliuyang.blog.51cto.com/8343567/1341117

相关文章
相关标签/搜索