1. 程式人生 > >Scrapy 中判斷重複內容的方法(RFPDupeFilter)

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 進行檢查,檢查是否是重複,如果已經重複了,就不入隊了。

1ifnot  request.dont_filter andself.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())。

1234567891011121314151617defrequest_fingerprint(request, include_headers=None):ifinclude_headers:include_headers =tuple([h.lower() forinsorted(include_headers)])cache =_fingerprint_cache.setdefault(request, {})ifinclude_headers notin  cache:fp =hashlib.sha1()fp.update(request.method)fp.update(canonicalize_url(request.url))fp.update(request.body or'')ifinclude_headers:forhdr ininclude_headers:ifhdr inrequest.headers:fp.update(hdr)forinrequest.headers.getlist(hdr):fp.update(v)cache[include_headers] =fp.hexdigest()returncache[include_headers]

預設的呼叫情況下,計算的內容包括 method、格式化後的 url、請求正文,還有就是 http headers 是可選的。

和通常情況下不一樣的是,這裡的計算指紋,不是單純的比較了 url 是否一致。計算的結果是一串 hash 16 進位制數字。

這裡自然產生了一個疑問,如果說計算指紋不是單純的比較 url,那麼 request 物件是個什麼東西?當呼叫 request_fingerprint() 時, request 經過了哪些計算,是不是 request 傳遞到這裡的時候,url 已經被下載過了?還是說沒有下載?如果說已經下載過了,就出現了重複下載的問題,那去重的意義就很小很小了;如果沒有下載過,method、header、body 的內容又是如何得知的呢?