Scrapy小解

1855 查看

前言

鄙人由于最近工作要求,需要爬一批安卓安装包做安全分析,遂不得不操刀写爬虫了。虽然久仰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

    pythonclass 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

    pythonclass 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()