前言
鄙人由于最近工作要求,需要爬一批安卓安装包做安全分析,遂不得不操刀写爬虫了。虽然久仰scrapy的大名,但一直没有用过。说来惭愧,2年前写的一个爬机票网站的爬虫还是用selenium+sh控制的。这次趁着机会,干脆体验一把scrapy的强劲效力,也分享给大家。
scrapy的工作流
我觉得scrapy做的最好的就是他对流程的模块化,把几个步骤分的很开,符合松耦合的设计原则。不过说实在话,它的文档有点乱,也没用一个比较好的API索引。首先让我们看一下Scrapy是怎么工作的。
发起请求->下载中间件处理请求->下载数据得到response->中间件对response做处理->parse函数解析response,提取你要的信息->生成item(或request)->pipeline处理item
我们一般要定制的主要是parse和pipeline,其他的scrapy都帮我们搞定了,所以只用专注于解析数据、生成新请求和存储了。现在scrapy的流程已经清楚了,scrapy还提供了一些额外的服务,比如日志模块,feed exports来帮助转换数据格式等,不过都是锦上添花了。
scrapy的重点
说完工作流,我们把目光放在常用的几个点上。
items模块:items用来定义你需要提取的数据的格式,方便进行一层一层的分析操作,你可以直接把它在使用上类比成python的字典,在概念上类比为mongodb的schema。
-
spider:就是我们的爬虫主体了,这里最重要的功能是对下载下来的数据做解析,生成符合规范的item或发起新的请求。我们首先实现的parse函数是解析spider的start_url请求的,对start_url默认的下载操作是直接拉下整个html,所以动态网页就呵呵了。那对于网页里嵌入了js怎么办呢,我先卖一个关子,等下再讲。
这里我们就要多讲一点了,对于我们手动发起的请求而非初始请求,我们是可以自己指定解析函数的,而非使用默认。同时我们也是可以通过继承scrapy的请求类构造自己的请求以附带一些信息的。这样就实现了请求和对返回结果解析的松耦合。
pipeline:pipeline是对得到的item做进一步处理的,并非你所得到的所有item都是合乎要求的,也有可能还要做一些计算工作才得到你想要的。scrapy在这一块牛逼在哪呢?在于他做处理的时候是传入spider对象做参数的,这样你可以把一个pipeline和多个spider拼接,不需要为一个spider另写一个pipeline,同时pipeline是可以有多个在一起的,只要你在processitem函数里返回这个item,它就会被下面的pipeline继续处理。
scrapy进阶
现在我们讲讲下载中间件和中间件的问题。上面我提到过对于动态页面怎么办,我们可以自己写一个下载中间件,继承最初始的,然后利用ghost.py在里面模拟用户行为,等html改变之后再拉取。
同样,我再举一个例子,比如我想用爬虫下文件怎么办,最好你是不要直接用scrapy默认的下载操作,对下载链接的拉取和解析应该和下载文件的处理分离开来。那么你可以自己写一个请求类,然后写一个下载中间件,中间件里维护一个进程池。在里面判断请求的类别,如果不带你定义的标记,就按照原来的默认操作,如果符合,是文件下载,就从进程池里拉出一个进程来做这件事,即使某个文件的下载失败了,你可以监控的,并且在response里表露出来的,更不会影响新的下载链接的生成何已有下载任务的执行。
最后中间件的问题,中间件可以包装response,这个我倒觉得对于做爬虫本身意义不是特别大,除非你的爬虫种类非常多,爬的网站也各不相同,就需要先做一点包装和处理了。
一个最简单的例子
下面给一个特别简单的例子,用于爬谷歌推出的所有安卓官方权限的一个小爬虫,帮助新手盆友了解一下。
-
item
python
class PermissionItem(scrapy.Item): permission=scrapy.Field() desc=scrapy.Field()
-
spider
python
__author__ = 'suemi' import scrapy from android.items import PermissionItem class PermissionSpider(scrapy.spider.Spider): name="permission" start_urls=["http://developer.android.com/reference/android/Manifest.permission.html"] def parse(self,response): for sel in response.xpath('//tr[re:testclass,"api apilevel")]'): item=PermissionItem() item['permission']=sel.xpath('tdclass="jd-linkcol"]/a/text()').extract()[0] item['desc']=sel.xpath('tdclass="jd-descrcol"]/text()').extract() yield item
-
pipeline
python
class PermissionPipeline(object): def __init__(self): self.file=open('../temp/permission.pk','wb') self.data=[] def process_item(self,item,spider): if item['permission']: self.data.append(item['permission']) else: raise DropItem("Missing Content ") def close_spider(self,spider): print self.data pickle.dump(self.data,self.file) self.file.close()