Scrapy 中判斷重複內容的方法(RFPDupeFilter)
這個處理的程式碼是編寫在 dupefilter.py 檔案中的,其中定義了處理重複 url 的方法。
在 scrapy 啟動時,如果配置了重複 url 寫入檔案(requests.seen
),那麼就會以追加的方式開啟這個檔案,並且從這個檔案中載入以前的資料到記憶體 set() 中儲存,當遇到一個新來的 url 時,通過指紋計算,在已抓取 url 集合中查詢,如果不存在,就新增進去,如果需要寫入檔案,就寫入檔案;如果已經存在了,告訴上層呼叫 url 已經抓取過了。
具體可以參考 class RFPDupeFilter(BaseDupeFilter)
類。
那麼在 scrapy 中是如何來使用這個類的方法的呢?什麼時候使用,這個流程是怎樣的呢?
這個可以追溯到 scrapy.core.scheduler 中定義的 Scheduler 類來決定。
現在就來看看 Scheduler 類中和過濾重複 url 有關的內容。
在 Scheduler 類中,在排程時,採用了 memory queue 和 disk queue 的儲存方法,所以,有一個入隊的方法,在入隊前,就要對 request
進行檢查,檢查是否是重複,如果已經重複了,就不入隊了。
1 | if not request.dont_filter and self .df.request_seen(request) |
這裡兩個條件控制,首先是配置中 dont_filter,如果它是 True,就說明是不篩選的,如果是 False,才是要篩選的。
後面的 request_seen() 在預設內建的篩選方法中,就是 RFPDupeFilter() 中的方法,檢查 request 是否已經存在。
只有要篩選且沒有見過這個 request,才會去篩選 url。
所以這裡已經很清晰了,排程器收到了 enqueue_request()
呼叫時,會檢查這個 url 重複的判斷開關,如果要篩選,就要檢查這個 request 是否已經存在了;這裡的檢查 if 如果成立,就直接返回了,只有不成立時,才會有後續的儲存操作,也就是入隊。
下面來看看 scrapy 中是如何判斷兩個 url 重複的。
關鍵的函式是 request_fingerprint
,這個是判斷是否重複的關鍵實現方法。(scrapy.utils.request.request_fingerprint()
)。
1234567891011121314151617 | def request_fingerprint(request, include_headers = None ): if include_headers: include_headers = tuple ([h.lower() for h in sorted (include_headers)]) cache = _fingerprint_cache.setdefault(request, {}) if include_headers not in cache: fp = hashlib.sha1() fp.update(request.method) fp.update(canonicalize_url(request.url)) fp.update(request.body or '') if include_headers: for hdr in include_headers: if hdr in request.headers: fp.update(hdr) for v in request.headers.getlist(hdr): fp.update(v) cache[include_headers] = fp.hexdigest() return cache[include_headers] |
預設的呼叫情況下,計算的內容包括 method、格式化後的 url、請求正文,還有就是 http headers 是可選的。
和通常情況下不一樣的是,這裡的計算指紋,不是單純的比較了 url 是否一致。計算的結果是一串 hash 16 進位制數字。
這裡自然產生了一個疑問,如果說計算指紋不是單純的比較 url,那麼 request 物件是個什麼東西?當呼叫 request_fingerprint() 時, request 經過了哪些計算,是不是 request 傳遞到這裡的時候,url 已經被下載過了?還是說沒有下載?如果說已經下載過了,就出現了重複下載的問題,那去重的意義就很小很小了;如果沒有下載過,method、header、body 的內容又是如何得知的呢?