1. 程式人生 > >scrapy-redis整合scrapy-splash使用教程

scrapy-redis整合scrapy-splash使用教程

本文對scrapy-redis和scrapy-splash的配置描述不會很詳細,主要在於講解scrapy-redis整合scrapy-splash方法

scrapy-redis使用

配置

可以採用全域性配置,也可以採用custom_settings方式覆蓋

全域性配置

區域性配置

當一個工程裡面有多個spider時,無法全域性配置的方式來使用scrapy-redis,只能採用區域性配置。最簡單的方法是將scrapy-redis的配置寫在custom_settings中。

custom_settings= {
            "REDIS_URL"
: "redis://192.168.5.174:6379/3", "SCHEDULER" : "scrapy_redis.scheduler.Scheduler", "DUPEFILTER_CLASS" : "scrapy_redis.dupefilter.RFPDupeFilter", #redis中資料型別為set時設定此項為True,預設為False "REDIS_START_URLS_AS_SET" : True, "ITEM_PIPELINES" : { 'scrapy_redis.pipelines.RedisPipeline'
: 300 } }

但是在每個spider中都加上這個配置又顯得太繁瑣,因此採用繼承RedisSpider的方法來重寫相關配置

重寫update_settings方法

'''
封裝了scrapy-redis的基本配置,無需修改setting.py即可使用scrapy-redis分散式爬蟲
覆蓋順序:custom_settings>redis_settings>setting.py
因此仍然可以使用setting.py中的DOWNLOADER_MIDDLEWARES,如果不需要可以在custom_settings覆蓋
'''
class
MyRedisSpider(RedisSpider):
redis_url = None def __init__(self , *args, **kwargs): super(MyRedisSpider, self).__init__(*args, **kwargs) @classmethod def update_settings(cls, settings): redis_settings = { "REDIS_URL" : None, "SCHEDULER" : "scrapy_redis.scheduler.Scheduler", "DUPEFILTER_CLASS" : "scrapy_redis.dupefilter.RFPDupeFilter", #redis中資料型別為set時設定此項為True,預設為False "REDIS_START_URLS_AS_SET" : True, "ITEM_PIPELINES" : { 'scrapy_redis.pipelines.RedisPipeline': 300 } } #子類的配置可以覆蓋redis_settings #redis_url必須配置custom_settings或類變數中 if(cls.custom_settings is not None): cls.custom_settings = dict(redis_settings, **cls.custom_settings) else: cls.custom_settings = redis_settings if(cls.redis_url is not None): cls.custom_settings["REDIS_URL"] = cls.redis_url settings.setdict(cls.custom_settings or {}, priority='spider')

在spider中使用

scrapy-redis預設一次從redis中取出20條url資料,並通過yield Request方式執行,但是在實際使用過程中,redis中並不會直接儲存url,而是在程式中拼接,因此需要重寫make_request_from_data方法。

'''
實際業務中,在redis中使用set型別儲存的公司名稱,並且需要結合selenium呼叫瀏覽器傳送請求
'''
class MySpider(MyRedisSpider):

    name="my_spider"
    allowed_domains = ["localhost"]

    redis_key = 'companies'
    redis_url = 'redis://localhost:6379/3'
    #scrapy第一次請求的地址,可以為任意可訪問的地址。公司名稱會拼接到該地址的param中
    url = "http://localhost"
#     custom_settings = {
#         "REDIS_URL" : "redis://localhost:6379/3"
#     }

    '''
    此處重寫的RedisSpider中的方法,data為redis中的一行資料
    注:此處因為需要呼叫瀏覽器,只能通過url來進行傳遞company引數,url為任意可訪問地址即可。
    正常請求網頁拼接url的方式相同
    '''
    def make_request_from_data(self, data):
        '''
        :params data bytes, Message from redis
        '''
        #url為任意可以訪問的地址即可
        company = bytes_to_str(data, self.redis_encoding)
        url = self.url +"?company=" + company
        return self.make_requests_from_url(url)


    def parse(self,response):
        #解析地址中的公司名稱,response實際body並不需要
        rs = urlparse.urlparse(response.url)
        params = urlparse.parse_qs(rs.query,True)
        company = params['company'][0].decode(self.redis_encoding)
        self.logger.debug(company)
        #呼叫瀏覽器及爬蟲程式碼省略

scrapy-splash

配置

scrapy-redis整合scrapy-splash

Spider

class MySplashSpider(MyRedisSpider):

    name="my_splash_spider"
    allowed_domains = ["localhost"]

    url = "http://localhost"
    redis_url = 'redis://locaohost:6379/3'
    redis_key = 'companies'

    '''
        redis中儲存的為set型別的公司名稱,使用SplashRequest去請求網頁。
        注意:不能在make_request_from_data方法中直接使用SplashRequest(其他第三方的也不支援),會導致方法無法執行,也不丟擲異常
        但是同時重寫make_request_from_data和make_requests_from_url方法則可以執行
    '''
    def make_request_from_data(self, data):
        '''
        :params data bytes, Message from redis
        '''
        company = bytes_to_str(data, self.redis_encoding)
        url = self.url+'/company/basic.jspx?company='+company
        return self.make_requests_from_url(url)

    def make_requests_from_url(self, url):
        return SplashRequest(url,callback=self.parse,args={'wait':3, 'html':1})

    def parse(self,response):
        soup = pq(response.body_as_unicode())
        #以下省略

DUPEFILTER_CLASS

scrapy-redis中配置了”DUPEFILTER_CLASS” : “scrapy_redis.dupefilter.RFPDupeFilter”,會覆蓋scrapy-splash配置的DUPEFILTER_CLASS = ‘scrapy_splash.SplashAwareDupeFilter’
查看了scrapy_splash.SplashAwareDupeFilter原始碼後,發現他繼承了scrapy.dupefilter.RFPDupeFilter,並重寫了request_fingerprint()方法。比較scrapy.dupefilter.RFPDupeFilter和scrapy_redis.dupefilter.RFPDupeFilter中的request_fingerprint()方法後,發現是一樣的,因此重寫了一個SplashAwareDupeFilter,繼承scrapy_redis.dupefilter.RFPDupeFilter,其他程式碼不變。

# -*- coding: utf-8 -*-
"""
To handle "splash" Request meta key properly a custom DupeFilter must be set.
See https://github.com/scrapy/scrapy/issues/900 for more info.
"""
from __future__ import absolute_import

from copy import deepcopy

from scrapy.utils.request import request_fingerprint
from scrapy.utils.url import canonicalize_url
from scrapy_splash.utils import dict_hash

from scrapy_redis.dupefilter import RFPDupeFilter




def splash_request_fingerprint(request, include_headers=None):
    """ Request fingerprint which takes 'splash' meta key into account """

    fp = request_fingerprint(request, include_headers=include_headers)
    if 'splash' not in request.meta:
        return fp

    splash_options = deepcopy(request.meta['splash'])
    args = splash_options.setdefault('args', {})

    if 'url' in args:
        args['url'] = canonicalize_url(args['url'], keep_fragments=True)

    return dict_hash(splash_options, fp)


class SplashAwareDupeFilter(RFPDupeFilter):
    """
    DupeFilter that takes 'splash' meta key in account.
    It should be used with SplashMiddleware.
    """
    def request_fingerprint(self, request):
        return splash_request_fingerprint(request)

還需要修改MyRedisSpider,將裡面的DUPEFILTER_CLASS改為上述類路徑。
注:方法能否可行還待驗證