scrapy-redis(二)

在scrapy中帮助我们进行一些预处理的组件称之为middleware。比如将request对象交给下载器下载之前,就会通过下载中间件的process_request()方法。这些中间件的用处非常大,我们可以详细的了解,以方便编写自己的中间件。

1.spider-middleware
该中间件位于spider和engine之间,按照我们给出的数字依次从小到大,顺序执行。数字越小的中间件越靠近engine,数字越大的中间件越靠近spider.

scrapy默认给出了几个中间件,分别是:

'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50,
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500,
'scrapy.spidermiddlewares.referer.RefererMiddleware': 550,
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800,
'scrapy.spidermiddlewares.depth.DepthMiddleware': 900,

默认情况下这些中间件都是启用的,当然我们也可以编写自己的中间件并加入到上面的字典中。
A.HttpErrorMiddleware:指的是过滤出我们希望spider不用去处理的response,比如状态码为404的。默认情况下,spider会处理所有的response,但我们也可以加一些配置,在settings.py这个文件中做如下配置:

HTTPERROR_ALLOWED_CODES = [200, 301, 302, 500, 503]

这样如果response不在列表中,那么该response就会被过滤掉,spider就不会做任何处理。但我个人的想法是,应该禁用这个默认的中间件,原因在介绍下载中间件的时候会详细介绍。

B.RefererMiddleware:这个中间件是给request对象添加referer头部的,照理来说,这个中间件是非常有用的。但是,有可能会出现这样一种情况,在不同的页面中,会出现相同的url,而这个url又符合我们抓取的规则,由于是在不同的页面,所以会分别创建request对象,然后经过这个中间件的时候,就会加上一个referer头部,这样这两个request对象就不一样了,实际上指向的是同一个url。然后在调度器中生成指纹对象的时候,这两个request对象的指纹是不一样的,因为期间有一个referer头部是不一样的。所以这两个request对象就会分别进行抓取,这就照成了资源浪费。所以建议还是禁用这个中间件。当然,如果你能修改scrapy去重request对象的时间点,那就更好了。
更改:以上说明有误,在将request对象转换成指纹的时候,是可以决定是否去除headers头部的

C.UrlLengthMiddleware:这表示了我们希望抓取的url的最大长度,看个人需求了,如果你只想抓取长度为100以下的url,在settings.py中作如下配置即可:

URLLENGTH_LIMIT = 100

D.DepthMiddleware:表示的是抓取的最大深度,我个人觉得这个没啥必要,因为url就是从页面上点击的,难道点击了5次之后的页面就不重要了吗?
可通过如下设置自定义这个中间件的一些状态:

DEPTH_LIMIT = 5
DEPTH_STATS = True
DEPTH_PRIORITY = False

E.OffsiteMiddleware:我们在定义spider的时候,会定义allowed_domains这个数据属性,所以这个中间件就是为我们过滤出不在allowed_domains中的站点以及相关的url,非常的实用。
注意,如果request对象的dont_filter属性设置为True,那么这个中间件就没用了。

如果我们要自定义自己的spider中间件,也非常的简单:

class MyCustomSpiderMiddleWares(object):

    def process_spider_input(self, response, spider):
        #该方法处理spider接受到的response
        #可以在这里将response存放到数据库中,实现缓存
        return None

    def process_spider_output(self, response, result, spider):
        #该方法处理spider解析response的输出,
        #所以result有可能是item或者是可迭代的requests对象
        #这里就可以处理一些请求对象或者处理tiem
        return result

    def process_spider_exception(self, response, exception, spider):
        return None

    #这个方法必须返回一个可迭代的request对象
    #最好还是忽略这个方法
    #def  process_start_requests(self, start_requests, spider):
    #   pass

我们只要实现上述方法中的某几个就可以了,当然也可以实现其他方法,比如from_crawler()这个类方法。
process_spider_input(self, response, spider)这个方法接受从engine传过来的response,最终会传给spider进行处理,比如提取links,或者items

process_spider_output(self, response, result, spider)这个方法接受spider处理response之后,传出的数据,比如items或者links

process_spider_exception(self, response, exception, spider)这个方法处理spider或者其他中间件引发的异常

process_start_requests(self, start_requests, spider)这个方法处理start_requests中传递出的requests对象,建议不要实现这个方法,没什么必要。

所以,我们可以自定义一个spider中间件用来实现http缓存,将下载下来的网页保存的数据库中,比如mysql或者mongodb。当然scrapy有默认的http缓存,但好像没有实现mysql和mongodb的。

2.doanload-middleware
该中间件位于engine和下载器之间,数据越小的越靠近engine.
scrapy也给出了默认的实现:

'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 550,
'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
'scrapy.downloadermiddlewares.chunked.ChunkedTransferMiddleware': 830,
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': None,
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': None,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': None,   
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': None,
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': None,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': None,

我禁用下载超时,重定向,重试,是因为我在request.meta里面进行了相应的设置,另外scrapy默认的用户代理是要禁掉的,这样就可以准备大量的用户代理,已被每次随机使用。至于http缓存,我禁用了scrapy自带的,可自行编写一个在spider-middleware实现的http缓存。
作为爬虫,cookie是一定要禁掉的,这只会增加下载延迟。除非你抓取的是登录的页面。

这里重点讲解一下关于处理下载器刚下载下来的response。在spidermiddleware中,我禁掉了httperror中间件,其中的原因是,我禁止了scrapy自带下载中间件中重定向,重试以及metarefeash中间件。原因是这非常的影响爬虫的性能,只会增加爬虫的消耗,而不会带来任何好处。为什么这么说呢?

每一次的重定向,都有可能增加dns解析,tcp/ip链接,然后才是发送http请求。我们为什么要浪费这么多的时间,没任何理由。所以我的做法是,接受任何响应,然后在下载中间件中处理这个响应,过滤出200状态码的相应交给engine.对于那么重定向的(301, 302,meta-refreash等),我提取出响应头部中的’location’等,然后重新生成一个request对象,交给调度器重新调度。对于404响应,直接抛弃。对于500+响应,把初始request对象重新交给调度器。这样,既不会影响爬虫的正常抓取,也不会落下需要再次抓取的request对象。

那么对于返回的request对象,重新回到调度器,会不会被去重过滤掉呢?然后官方文档上说的是,返回的request对象是一定会被下载的,姑且认为不会被去重吧。

def process_response(self, request, response, spider):
        #处理下载完成的response
        #排除状态码不是304的所有以3为开头的响应
        http_code = response.status
        if http_code // 100 == 2:
            return response

        if http_code // 100 == 3 and http_code != 304:
            #获取重定向的url
            url = response.headers['location']
            domain = urlparse.urlparse(url).netloc
            #判断重定向的url的domain是否在allowed_domains中
            if domain in spider.allowed_domains:        
                return Request(url=url, meta=request.meta)
            else:
                raise IgnoreRequest(u'not allowed to crawl')

        if http_code // 100 == 4:
            #需要注意403不是响应错误,是无权访问
            raise IgnoreRequest(u'404')

        if http_code // 100 == 5:                       
            return request

        #处理网页meta refresh的问题        
        url = html.get_html_meta_refresh(response)
        if url:
            domain = urlparse.urlparse(url).netloc
            #判断meta refresh重定向的url的domain是否在allowed_domains中
            if domain in spider.allowed_domains:        
                return Request(url=url, meta=request.meta)

上述就是我自定义下载中间件中处理response的方法,只返回200的响应,其他响应分别做各自的处理。

3.总结
对于中间件,我们能做的事情非常多,目前限于水平,能想到的一些方法就这点。当然,以上是我自己的一些想法,可能与你的想法完全不同,并没有对错之分,每个人都能找到适合自己的最优的方法。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值