日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

第47讲:scrapy-redis分布式爬虫介绍

發布時間:2024/4/11 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第47讲:scrapy-redis分布式爬虫介绍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們在前面幾節課了解了 Scrapy 爬蟲框架的用法。但這些框架都是在同一臺主機上運行的,爬取效率比較低。如果能夠實現多臺主機協同爬取,那么爬取效率必然會成倍增長,這就是分布式爬蟲的優勢。

接下來我們就來了解一下分布式爬蟲的基本原理,以及 Scrapy 實現分布式爬蟲的流程。

我們在前面已經實現了 Scrapy 基本的爬蟲功能,雖然爬蟲是異步加多線程的,但是我們卻只能在一臺主機上運行,所以爬取效率還是有限的,而分布式爬蟲則是將多臺主機組合起來,共同完成一個爬取任務,這將大大提高爬取的效率。

1.分布式爬蟲架構

在了解分布式爬蟲架構之前,首先回顧一下 Scrapy 的架構,如圖所示。

Scrapy 單機爬蟲中有一個本地爬取隊列 Queue,這個隊列是利用 deque 模塊實現的。如果新的 Request 生成就會放到隊列里面,隨后 Request 被 Scheduler 調度。之后,Request 交給 Downloader 執行爬取,簡單的調度架構如圖所示。

如果兩個 Scheduler 同時從隊列里面獲取 Request,每個 Scheduler 都會有其對應的 Downloader,那么在帶寬足夠、正常爬取且不考慮隊列存取壓力的情況下,爬取效率會有什么變化呢?沒錯,爬取效率會翻倍。

這樣,Scheduler 可以擴展多個,Downloader 也可以擴展多個。而爬取隊列 Queue 必須始終為一個,也就是所謂的共享爬取隊列。這樣才能保證 Scheduer 從隊列里調度某個 Request 之后,其他 Scheduler 不會重復調度此 Request,就可以做到多個 Schduler 同步爬取。這就是分布式爬蟲的基本雛形,簡單調度架構如圖所示。

我們需要做的就是在多臺主機上同時運行爬蟲任務協同爬取,而協同爬取的前提就是共享爬取隊列。這樣各臺主機就不需要維護各自的爬取隊列了,而是從共享爬取隊列存取 Request。但是各臺主機還有各自的 Scheduler 和 Downloader,所以調度和下載功能是分別完成的。如果不考慮隊列存取性能消耗,爬取效率還是可以成倍提高的。

2.維護爬取隊列

那么如何維護這個隊列呢?我們首先需要考慮的就是性能問題,那什么數據庫存取效率高呢?這時我們自然想到了基于內存存儲的 Redis,而且 Redis 還支持多種數據結構,例如列表 List、集合 Set、有序集合 Sorted Set 等,存取的操作也非常簡單,所以在這里我們采用 Redis 來維護爬取隊列。

這幾種數據結構存儲實際各有千秋,分析如下:

  • 列表數據結構有 lpush、lpop、rpush、rpop 方法,所以我們可以用它實現一個先進先出的爬取隊列,也可以實現一個先進后出的棧式爬取隊列。

  • 集合的元素是無序且不重復的,這樣我們就可以非常方便地實現一個隨機排序的不重復的爬取隊列。

  • 有序集合帶有分數表示,而 Scrapy 的 Request 也有優先級的控制,所以我們用有序集合就可以實現一個帶優先級調度的隊列。

這些不同的隊列我們需要根據具體爬蟲的需求靈活選擇。

3.怎樣去重

Scrapy 有自動去重功能,它的去重使用了 Python 中的集合。這個集合記錄了 Scrapy 中每個 Request 的指紋,這個指紋實際上就是 Request 的散列值。我們可以看看 Scrapy 的源代碼,如下所示:

import hashlib def request_fingerprint(request, include_headers=None):if include_headers:include_headers = tuple(to_bytes(h.lower())for h in sorted(include_headers))cache = _fingerprint_cache.setdefault(request, {})if include_headers not in cache:fp = hashlib.sha1()fp.update(to_bytes(request.method))fp.update(to_bytes(canonicalize_url(request.url)))fp.update(request.body or b'')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]

request_fingerprint 就是計算 Request 指紋的方法,其方法內部使用的是 hashlib 的 sha1 方法。計算的字段包括 Request 的 Method、URL、Body、Headers 這幾部分內容,這里只要有一點不同,那么計算的結果就不同。計算得到的結果是加密后的字符串,也就是指紋。

每個 Request 都有獨有的指紋,指紋就是一個字符串,判定字符串是否重復比判定 Request 對象是否重復容易得多,所以指紋可以作為判定 Request 是否重復的依據。

那么我們如何判定是否重復呢?Scrapy 是這樣實現的,如下所示:

def __init__(self):self.fingerprints = set()def request_seen(self, request):fp = self.request_fingerprint(request)if fp in self.fingerprints:return Trueself.fingerprints.add(fp)

在去重的類 RFPDupeFilter 中,有一個 request_seen 方法,這個方法有一個參數 request,它的作用就是檢測該 Request 對象是否重復。這個方法調用 request_fingerprint 獲取該 Request 的指紋,檢測這個指紋是否存在于 fingerprints 變量中,而 fingerprints 是一個集合,集合的元素都是不重復的。

如果指紋存在,那么就返回 True,說明該 Request 是重復的,否則將這個指紋加入集合中。如果下次還有相同的 Request 傳遞過來,指紋也是相同的,那么這時指紋就已經存在于集合中,Request 對象就會直接判定為重復。這樣去重的目的就實現了。

Scrapy 的去重過程就是,利用集合元素的不重復特性來實現 Request 的去重。

對于分布式爬蟲來說,我們肯定不能再使用每個爬蟲各自的集合來去重了。因為這樣還是每臺主機單獨維護自己的集合,不能做到共享。多臺主機如果生成了相同的 Request,只能各自去重,各個主機之間就無法做到去重了。

那么要實現多臺主機去重,這個指紋集合也需要是共享的,Redis 正好有集合的存儲數據結構,我們可以利用 Redis 的集合作為指紋集合,那么這樣去重集合也是共享的。

每臺主機新生成 Request 之后,會把該 Request 的指紋與集合比對,如果指紋已經存在,說明該 Request 是重復的,否則將 Request 的指紋加入這個集合中即可。利用同樣的原理不同的存儲結構我們也可以實現分布式 Reqeust 的去重。

4.防止中斷

在 Scrapy 中,爬蟲運行時的 Request 隊列放在內存中。爬蟲運行中斷后,這個隊列的空間就被釋放,此隊列就被銷毀了。所以一旦爬蟲運行中斷,爬蟲再次運行就相當于全新的爬取過程。

要做到中斷后繼續爬取,我們可以將隊列中的 Request 保存起來,下次爬取直接讀取保存數據即可獲取上次爬取的隊列。我們在 Scrapy 中指定一個爬取隊列的存儲路徑即可,這個路徑使用 JOB_DIR 變量來標識,我們可以用如下命令來實現:

scrapy crawl spider -s JOBDIR=crawls/spider

更加詳細的使用方法可以參見官方文檔,鏈接為:https://doc.scrapy.org/en/latest/topics/jobs.html。

在 Scrapy 中,我們實際是把爬取隊列保存到本地,第二次爬取直接讀取并恢復隊列即可。那么在分布式架構中我們還用擔心這個問題嗎?不需要。因為爬取隊列本身就是用數據庫保存的,如果爬蟲中斷了,數據庫中的 Request 依然是存在的,下次啟動就會接著上次中斷的地方繼續爬取。

所以,當 Redis 的隊列為空時,爬蟲會重新爬取;當 Redis 的隊列不為空時,爬蟲便會接著上次中斷之處繼續爬取。

5.架構實現

我們接下來就需要在程序中實現這個架構了。首先需要實現一個共享的爬取隊列,還要實現去重功能。另外,還需要重寫一個 Scheduer 的實現,使之可以從共享的爬取隊列存取 Request。

幸運的是,已經有人實現了這些邏輯和架構,并發布成了叫作 Scrapy-Redis 的 Python 包。

在下一節,我們便看看 Scrapy-Redis 的源碼實現,以及它的詳細工作原理。

總結

以上是生活随笔為你收集整理的第47讲:scrapy-redis分布式爬虫介绍的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。