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改為上述類路徑。
注:方法能否可行還待驗證