从互联网上下载到网页,只是我们迈向成功的第一步。拿到网页数据以后,我们需要从中提取我们想要的具体信息,
比如标题、内容、时间、作者等。最常见的提取方式有两种:XPath和正则表达式。
先简单介绍一下XPATH和正则表达式。
XPath即为 XML 路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。 XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力(见维基百科 XPath)。
正则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
发表一下个人见解:
XPath主要是用来处理 XML 格式文档的,它是基于 XML 文档的层次结构来确定一个到达指定节点的路径的,因此特别
适合处理这种层级结构明显的数据(起初 XPath 的提出的初衷是将其作为一个通用的、介于 XPointer 与 XSLT 间的语法模型。
但是 XPath 很快的被开发者采用来当作小型查询语言)。
正则表达式可以处理任何格式的字符串文档,它是基于文本的特征来匹配、查找指定数据的。
如果提取信息就像找一个建筑,那么正则表达式就是告诉你,
这个建筑的左边是什么、右边是什么、以及这个建筑本身有哪些特征,
但这样的描述在全国范围内可能有很多地方的建筑都符合条件,找起来还是不太方便,除非你限制范围,比如指定北京海淀区等。
而 XPath 就是告诉你这个建筑在中国-北京-海淀区-中关村-中关村大街-1号,这样找起来就方便了很多,
当然这不是说XPath就比正则表达式要好用,具体选择还得看应用场景,
比如让你在一个广场上等人,告诉对方你在哪里的时候,你总不会说我在广场上从东数第23块砖从北数第16块砖上站着吧,
你很可能会说我在一个雕像旁边或喷泉旁边等。下面主要说一下在爬虫中一般如何选择使用XPath和正则表达式。
在写爬虫的时候,一般会遇到 HTML、JSON、XML、纯文本等格式的文档,先来说一下 HTML 和 XML,HTML 是 XML的一个子集,因此它们的处理方式是一样的,首选使用 XPath 来获取信息,以博客园首页为例,如果我们需要的数据是文章的 url 列表,最好使用 XPath。
见图:
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
'''
如果使用正则表达式的话,也可以达成同样的效果,不过,要转换一下思路。由于正则表达式是使用特征匹配的,因此
最基本的思路有两个,一、是基于url本身的特征;二、基于url所在位置的特征。(其实看到这里,你就应该对该使用正则表达式还是XPath有所体会了,使用XPath想都不用想,正则表达式还得想一会儿)
下面我们来分析一下,第一个思路基本行不通,由于网页上并不是只有我们想要的这些主要的文章url,还有一些推荐的文章url。
见图
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]
# 输出
'''
[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和正则表达式混用是很经常的,这个选择只是针对
匹配具体字段而言的。
如果想让代码达到最优,需要考虑的东西还有很多,比如XPath和正则表达式的执行效率,一般情况下,正则表达式的效率是比较高的,这个前提是不太复杂的正则表达式,有些特别复杂的正则表达式可能严重减慢执行效率。
参考文章:
《用 python 写的爬虫,有哪些提高的技能?》 https://www.zhihu.com/question/36832667
《【Python爬虫】入门知识》 http://www.jianshu.com/p/74b94eadae15
《xpath与正则表达式抽取网页信息的速度比较》http://pcliuyang.blog.51cto.com/8343567/1341117
网友评论