轉載於http://www.leyle.com/archives/scrapy_dupefilter.html
scrapy 中判斷重複內容的方法(RFPDupeFilter)
scrapy 中判斷重複內容的方法(RFPDupeFilter)
爬蟲抓取數據時,重複肯定是存在的,scrapy 是如何來篩選這些重複的 url 的呢?
這個處理的代碼是編寫在 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()
)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 的內容又是如何得知的呢?
request 對象是什麼?它的生命週期是如何的?