一般咱們在一個站站點進行採集的時候,若是是小站的話 咱們使用scrapy自己就能夠知足。html
可是若是在面對一些比較大型的站點的時候,單個scrapy就顯得力不從心了。ios
要是咱們可以多個Scrapy一塊兒採集該多好啊 人多力量大。git
很遺憾Scrapy官方並不支持多個同時採集一個站點,雖然官方給出一個方法:github
**將一個站點的分割成幾部分 交給不一樣的scrapy去採集**redis
彷佛是個解決辦法,可是很麻煩誒!畢竟分割很麻煩的哇json
下面就改輪到咱們的額主角Scrapy-Redis登場了!cookie
好吧 爲了簡單起見 就用官方圖來簡單說明一下:app
這張圖你們相信你們都很熟悉了。重點看一下SCHEDULERscrapy
1. 先來看看官方對於SCHEDULER的定義:ide
**SCHEDULER接受來自Engine的Requests,並將它們放入隊列(能夠按順序優先級),以便在以後將其提供給Engine**
2. 如今咱們來看看SCHEDULER都提供了些什麼功能:
根據官方文檔說明 在咱們沒有沒有指定 SCHEDULER 參數時,默認使用:’scrapy.core.scheduler.Scheduler’ 做爲SCHEDULER(調度器)
scrapy.core.scheduler.py:
只挑了一些重點的寫了一些註釋剩下你們本身領會(纔不是我懶哦 )
從上面的代碼 咱們能夠很清楚的知道 SCHEDULER的主要是完成了 push Request pop Request 和 去重的操做。
並且queue 操做是在內存隊列中完成的。
你們看queuelib.queue就會發現基於內存的(deque)
那麼去重呢?
按照正常流程就是你們都會進行重複的採集;咱們都知道進程之間內存中的數據不可共享的,那麼你在開啓多個Scrapy的時候,它們相互之間並不知道對方採集了些什麼那些沒有沒采集。那就你們夥兒本身玩本身的了。徹底沒沒有效率的提高啊!
怎麼解決呢?
這就是咱們Scrapy-Redis解決的問題了,不能協做不就是由於Request 和 去重這兩個 不能共享嗎?
那我把這兩個獨立出來好了。
將Scrapy中的SCHEDULER組件獨立放到你們都能訪問的地方不就OK啦!加上scrapy-redis後流程圖就應該變成這樣了?
So············· 這樣是否是看起來就清楚多了???
下面咱們來看看Scrapy-Redis是怎麼處理的?
scrapy_redis.scheduler.py:
class Scheduler(object): """Redis-based scheduler Settings -------- SCHEDULER_PERSIST : bool (default: False) Whether to persist or clear redis queue. SCHEDULER_FLUSH_ON_START : bool (default: False) Whether to flush redis queue on start. SCHEDULER_IDLE_BEFORE_CLOSE : int (default: 0) How many seconds to wait before closing if no message is received. SCHEDULER_QUEUE_KEY : str Scheduler redis key. SCHEDULER_QUEUE_CLASS : str Scheduler queue class. SCHEDULER_DUPEFILTER_KEY : str Scheduler dupefilter redis key. SCHEDULER_DUPEFILTER_CLASS : str Scheduler dupefilter class. SCHEDULER_SERIALIZER : str Scheduler serializer. """ def __init__(self, server, persist=False, flush_on_start=False, queue_key=defaults.SCHEDULER_QUEUE_KEY, queue_cls=defaults.SCHEDULER_QUEUE_CLASS, dupefilter_key=defaults.SCHEDULER_DUPEFILTER_KEY, dupefilter_cls=defaults.SCHEDULER_DUPEFILTER_CLASS, idle_before_close=0, serializer=None): """Initialize scheduler. Parameters ---------- server : Redis 這是Redis實例 persist : bool 是否在關閉時清空Requests.默認值是False。 flush_on_start : bool 是否在啓動時清空Requests。 默認值是False。 queue_key : str Request隊列的Key名字 queue_cls : str 隊列的可導入路徑(就是使用什麼隊列) dupefilter_key : str 去重隊列的Key dupefilter_cls : str 去重類的可導入路徑。 idle_before_close : int 等待多久關閉 """ if idle_before_close < 0: raise TypeError("idle_before_close cannot be negative") self.server = server self.persist = persist self.flush_on_start = flush_on_start self.queue_key = queue_key self.queue_cls = queue_cls self.dupefilter_cls = dupefilter_cls self.dupefilter_key = dupefilter_key self.idle_before_close = idle_before_close self.serializer = serializer self.stats = None def __len__(self): return len(self.queue) @classmethod def from_settings(cls, settings): kwargs = { 'persist': settings.getbool('SCHEDULER_PERSIST'), 'flush_on_start': settings.getbool('SCHEDULER_FLUSH_ON_START'), 'idle_before_close': settings.getint('SCHEDULER_IDLE_BEFORE_CLOSE'), } # If these values are missing, it means we want to use the defaults. optional = { # TODO: Use custom prefixes for this settings to note that are # specific to scrapy-redis. 'queue_key': 'SCHEDULER_QUEUE_KEY', 'queue_cls': 'SCHEDULER_QUEUE_CLASS', 'dupefilter_key': 'SCHEDULER_DUPEFILTER_KEY', # We use the default setting name to keep compatibility. 'dupefilter_cls': 'DUPEFILTER_CLASS', 'serializer': 'SCHEDULER_SERIALIZER', } # 從setting中獲取配置組裝成dict(具體獲取那些配置是optional字典中key) for name, setting_name in optional.items(): val = settings.get(setting_name) if val: kwargs[name] = val # Support serializer as a path to a module. if isinstance(kwargs.get('serializer'), six.string_types): kwargs['serializer'] = importlib.import_module(kwargs['serializer']) # 或得一個Redis鏈接 server = connection.from_settings(settings) # Ensure the connection is working. server.ping() return cls(server=server, **kwargs) @classmethod def from_crawler(cls, crawler): instance = cls.from_settings(crawler.settings) # FIXME: for now, stats are only supported from this constructor instance.stats = crawler.stats return instance def open(self, spider): self.spider = spider try: # 根據self.queue_cls這個能夠導入的類 實例化一個隊列 self.queue = load_object(self.queue_cls)( server=self.server, spider=spider, key=self.queue_key % {'spider': spider.name}, serializer=self.serializer, ) except TypeError as e: raise ValueError("Failed to instantiate queue class '%s': %s", self.queue_cls, e) try: # 根據self.dupefilter_cls這個能夠導入的類 實例一個去重集合 # 默認是集合 能夠實現本身的去重方式 好比 bool 去重 self.df = load_object(self.dupefilter_cls)( server=self.server, key=self.dupefilter_key % {'spider': spider.name}, debug=spider.settings.getbool('DUPEFILTER_DEBUG'), ) except TypeError as e: raise ValueError("Failed to instantiate dupefilter class '%s': %s", self.dupefilter_cls, e) if self.flush_on_start: self.flush() # notice if there are requests already in the queue to resume the crawl if len(self.queue): spider.log("Resuming crawl (%d requests scheduled)" % len(self.queue)) def close(self, reason): if not self.persist: self.flush() def flush(self): self.df.clear() self.queue.clear() def enqueue_request(self, request): """這個和Scrapy自己的同樣""" if not request.dont_filter and self.df.request_seen(request): self.df.log(request, self.spider) return False if self.stats: self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider) # 向隊列裏面添加一個Request self.queue.push(request) return True def next_request(self): """獲取一個Request""" block_pop_timeout = self.idle_before_close # block_pop_timeout 是一個等待參數 隊列沒有東西會等待這個時間 超時就會關閉 request = self.queue.pop(block_pop_timeout) if request and self.stats: self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider) return request def has_pending_requests(self): return len(self) > 0
來先來看看
以上就是Scrapy-Redis中的SCHEDULER模塊。下面咱們來看看queue和自己的什麼不一樣:
scrapy_redis.queue.py
以最經常使用的優先級隊列 PriorityQueue 舉例:
以上就是SCHEDULER在處理Request的時候作的操做了。
是時候來看看SCHEDULER是怎麼處理去重的了!
只須要注意這個?方法便可:
這樣你們就均可以訪問同一個Redis 獲取同一個spider的Request 在同一個位置去重,就不用擔憂重複啦
大概就像這樣:
可能有些小夥兒會產生疑問了~~!spider2拿到了別人的Request了 怎麼能正確的執行呢?邏輯不會錯嗎?
這個不用擔憂啦 由於整Request當中包含了,全部的邏輯,回去看看上面那個序列化的字典。
總結一下:
另外Scrapy-Redis自己不支持Redis-Cluster,大量網站去重的話會給單機很大的壓力(就算使用boolfilter 內存也不夠整啊!)
改造方式很簡單:
以上!完畢
對於懶人小夥伴兒 看看這個我改好的: 集羣版Scrapy-Redis **PS: 支持Python3.6+ 哦 ! 其他的版本沒測試過**
轉載請註明:靜覓 » 小白進階之Scrapy第六篇Scrapy-Redis詳解