5.scrapy過濾器

scrapy過濾器

1. 過濾器

當我們在爬取網頁的時候可能會遇到一個調轉連接會在不同頁面出現,這個時候如果我們的爬蟲程序不能識別出

該鏈接是已經爬取過的話,就會造成一種重復不必要的爬取。所以我們要對我們即將要爬取的網頁進行過濾,把重

復的網頁鏈接過濾掉。

2. 指紋過濾器

去重處理可以避免將重復性的數據保存到數據庫中以造成大量的冗余性數據。不要在獲得爬蟲的結果后進行內容過

濾,這樣做只不過是避免后端數據庫出現重復數據。

去重處理對于一次性爬取是有效的,但對于增量式爬網則恰恰相反。對于持續性長的增量式爬網,應該進行"前置過

濾",這樣可以有效地減少爬蟲出動的次數。在發出請求之前檢查詢爬蟲是否曾爬取過該URL,如果已爬取過,則讓爬

蟲直接跳過該請求以避免重復出動爬蟲。

Scrapy 提供了一個很好的請求指紋過濾器(Request Fingerprint duplicates filter)

scrapy.dupefilters.ReppupeFilter ,當它被啟用后,會自動記錄所有成功返回響應的請求的URL,并將其以文件

(requests.seen)方式保存在項目目錄中。請求指紋過濾器的原理是為每個URL生成一個指紋并記錄下來,一旦

當前請求的URL在指紋庫中有記錄,就自動跳過該請求。

默認情況下這個過濾器是自動啟用的,但是在start_requests中是關閉的,當然也可以根據自身的需求編寫自定義的過濾器。

起始url,默認過濾器關閉的地方:

    def start_requests(self):
        
        for url in self.start_urls:
            # 每一個url封裝成Request對象,交給調度器
            # 這里的dont_filter=True 默認關閉scrapy自帶的過濾器,
            yield Request(url, dont_filter=True)  

2.1 過濾器源碼

2.1.1 基本過濾器

這是一個基類,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

2.2.2 默認的過濾器

繼承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類源碼的執行流程差不多,對沒錯就是差不多。

  1. 執行 from_settings(cls, settings): 這個類方法
  2. 執行 _ _init _ _ (self, path=None, debug=False): 實例化對象
  3. 執行 request_seen(self, request): 拿到由請求生產的指紋
  4. 執行 log(self, request, spider): 記錄日志
  5. 執行 close(self, reason): 關閉記錄指紋的文件

由于 scrapy.dupefilters.RFPDupeFilter 采用文件方式保存指紋庫,對于增量爬取且只用于短期運行的項目還能
應對。一旦遇到爬取量巨大的場景時,這個過濾器就顯得不太適用了,因為指紋庫文件會變得越來越大,過濾器在啟動時會一次性將指紋庫中所有的URL讀入,導致消耗大量內存。

所以我們情況下,在使用scrapy過濾器的時候,都是自己重新自定義。

3. 自定義過濾器

雖然自帶的過濾器不好用,但是我們可以用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 中的請求,默認是不過濾的。

4. 總結

之前我們在管道中,講到的數據去重,是對結果的去重,這里我們講的過濾是對請求的去重。

一定一定要會看源碼,會自定義一些組件。因為自帶的公司一般都不會用的,因為不好用。

posted @ 2020-07-06 22:41  Mn猿  閱讀(...)  評論(...編輯  收藏
色网站直播