當咱們在爬取網頁的時候可能會遇到一個調轉鏈接會在不一樣頁面出現,這個時候若是咱們的爬蟲程序不能識別出python
該連接是已經爬取過的話,就會形成一種重複沒必要要的爬取。因此咱們要對咱們即將要爬取的網頁進行過濾,把重數據庫
復的網頁連接過濾掉。後端
去重處理能夠避免將重複性的數據保存到數據庫中以形成大量的冗餘性數據。不要在得到爬蟲的結果後進行內容過框架
濾,這樣作只不過是避免後端數據庫出現重複數據。scrapy
去重處理對於一次性爬取是有效的,但對於增量式爬網則偏偏相反。對於持續性長的增量式爬網,應該進行"前置過ide
濾",這樣能夠有效地減小爬蟲出動的次數。在發出請求以前檢查詢爬蟲是否曾爬取過該URL,若是已爬取過,則讓爬函數
蟲直接跳過該請求以免重複出動爬蟲。url
Scrapy 提供了一個很好的請求指紋過濾器(Request Fingerprint duplicates filter)
debug
scrapy.dupefilters.ReppupeFilter ,當它被啓用後,會自動記錄全部成功返回響應的請求的URL,並將其以文件日誌
(requests.seen)
方式保存在項目目錄中。請求指紋過濾器的原理是爲每一個URL生成一個指紋並記錄下來,一旦
當前請求的URL在指紋庫中有記錄,就自動跳過該請求。
默認狀況下這個過濾器是自動啓用的,固然也能夠根據自身的需求編寫自定義的過濾器。
默認過濾器開啓的地方:
def start_requests(self): for url in self.start_urls: # 每個url封裝成Request對象,交給調度器 # 這裏的dont_filter=True 就是默認開啓scrapy自帶的過濾器 yield Request(url, dont_filter=True)
這是一個基類,scrapy自帶的指紋過濾器就是繼承這個類,而後重寫這些方法實現的。
若是你常常看源碼的話,你話發現不少功能的實現都有一個最基礎的基類,而後實現功能的那個類,繼承它
並對他的方法進行重寫。scrapy框架中就是這樣的。
class BaseDupeFilter: # 基本的過濾器 @classmethod def from_settings(cls, settings): """這個方法能夠從settings.py中獲取數據""" return cls() def request_seen(self, request): # 對 request 去重的方法 return False def open(self): # can return deferred 爬蟲打開的時候 pass def close(self, reason): # can return a deferred 爬蟲關閉的時候 pass def log(self, request, spider): # log that a request has been filtered 爬蟲的日誌 pass
繼承BaseDupeFilter類,而後對內部重寫。
class RFPDupeFilter(BaseDupeFilter): """Request Fingerprint duplicates filter 指紋過濾器 對整個request的去重""" # 默認的話是一個指紋過濾器, 會對整個request對象進行過濾 (url/method/params...) def __init__(self, path=None, debug=False): self.file = None # 內存型的集合 存在於內存 self.fingerprints = set() self.logdupes = True self.debug = debug self.logger = logging.getLogger(__name__) if path: self.file = open(os.path.join(path, 'requests.seen'), 'a+') # 打開文件 self.file.seek(0) self.fingerprints.update(x.rstrip() for x in self.file) # 更新文件 @classmethod def from_settings(cls, settings): # 從配置文件中取到要應用的過濾器。 debug = settings.getbool('DUPEFILTER_DEBUG') return cls(job_dir(settings), debug) def request_seen(self, request): # 拿到request 傳到 request_fingerprint方法 摘要出來的指紋 fp fp = self.request_fingerprint(request) # 若是指紋在集合中 if fp in self.fingerprints: # 返回 True return True # 不在就追加到集合 self.fingerprints.add(fp) if self.file: # 指紋寫入到文件 self.file.write(fp + '\n') def request_fingerprint(self, request): # 返回請求生成的指紋 return request_fingerprint(request) def close(self, reason): # 爬蟲結束關閉存放指紋的文件 if self.file: self.file.close() def log(self, request, spider): # 爬蟲日誌 if self.debug: msg = "Filtered duplicate request: %(request)s (referer: %(referer)s)" args = {'request': request, 'referer': referer_str(request)} self.logger.debug(msg, args, extra={'spider': spider}) elif self.logdupes: msg = ("Filtered duplicate request: %(request)s" " - no more duplicates will be shown" " (see DUPEFILTER_DEBUG to show all duplicates)") self.logger.debug(msg, {'request': request}, extra={'spider': spider}) self.logdupes = False spider.crawler.stats.inc_value('dupefilter/filtered', spider=spider)
執行流程:
細心的小夥伴可能發現了,這個和pipeline類源碼的執行流程差很少,對沒錯就是差很少。
因爲 scrapy.dupefilters.RFPDupeFilter 採用文件方式保存指紋庫,對於增量爬取且只用於短時間運行的項目還能
應對。一旦遇到爬取量巨大的場景時,這個過濾器就顯得不太適用了,由於指紋庫文件會變得愈來愈大,過濾器在啓動時會一次性將指紋庫中全部的URL讀入,致使消耗大量內存。
因此咱們狀況下,在使用scrapy過濾器的時候,都是本身從新自定義。
雖然自帶的過濾器很差用,可是咱們能夠用Scrapy提供的 request_fingerprint
函數爲請求生成指紋,而後將
指紋寫入內存,這樣會從內存中存取數據會很快。而後寫好的這個類,位置能夠隨便放,可是必定要在settings.py
文件中重新指定過濾器。
# 這裏是放在了當前項目的中間件裏面了. DUPEFILTER_CLASS = 'qd_04_english.middlewares.URLFilter'
# 過濾器先啓動,再執行爬蟲 import hashlib from scrapy.dupefilters import BaseDupeFilter class URLFilter(BaseDupeFilter): """根據URL過濾""" @classmethod def from_settings(cls, settings): # 從settings裏面取到配置文件 debug = settings.getbool('DUPEFILTER_DEBUG') return cls() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 過濾url的集合 self.url_set = set() def request_seen(self, request): """對每個請求進行過濾""" url = self.request_fingerprint(request) if url in self.url_set: # 返回True就表明這個url已經被請求過了 return True else: self.url_set.add(request.url) def request_fingerprint(self, request): # 返回由url摘要後的字符串 return hashlib.md5(request.url.encode()).hexdigest()
注意:start_urls 中的請求,默認是不過濾的。
以前咱們在管道中,講到的數據去重,是對結果的去重,這裏咱們講的過濾是對請求的去重。
必定必定要會看源碼,會自定義一些組件。由於自帶的公司通常都不會用的,由於很差用。