python3編寫網絡爬蟲23-分布式爬蟲


一、分布式爬蟲

前面我們了解Scrapy爬蟲框架的基本用法 這些框架都是在同一台主機運行的 爬取效率有限 如果多台主機協同爬取 爬取效率必然成倍增長
這就是分布式爬蟲的優勢

1. 分布式爬蟲基本原理

1.1 分布式爬蟲架構

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

如果兩個 Scheduler同時從隊列中取 Request 每個 Scheduler 都有其對應的 Downloader 那么在帶寬足夠 正常爬取且不考慮隊列存取壓力
的情況下 爬取效率會翻倍

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

需要多台主機同時運行爬蟲任務協同爬取 而協同爬取的前提就是共享爬取隊列 這樣各台主機就不要各自維護爬取隊列 而從共享爬取隊列存取
Request 但是各台主機還是與各自的 Scheduler 和 Downloader 所以調度和下載功能分別完成 不考慮隊列存取性能消耗 爬取效率還是會成倍提高
如圖 主機與從機


1.2 維護爬取隊列

隊列用什么維護 首先考慮的就是性能問題 基於內存存儲的Redis 支持多種數據結構 例如 列表 集合 有序集合 等 存取操作也相對簡單

redis 支持的這幾種數據結構存儲各有優點

列表 有 lpush() lpop() rpush() rpop() 方法 我們可以用它來實現先進先出式爬取隊列 也可以實現先進后出棧式爬取隊列

集合 元素是無序不重復的 可以非常方便的實現隨機排序且不重復的爬取隊列

有序集合 帶有分數標識 而 Scrapy 的 Request 也有優先級的控制 可以用它來實現帶優先級的調度隊列

需要根據具體爬蟲的需求靈活選擇不同隊列

1.3 如何去重

scrapy 有自動去重 使用了python中的集合 集合記錄了 Scrapy中每個 Request的指紋
其內部使用的是hashlib 的 sha1 方法 計算的字段包括 Request 的 method URL Body Headers
這里面只要有一點不同 那么計算的結果就不同 計算得到的結果是加密后的字符串 也就是指紋
每個Request 都有獨有的指紋 指紋就是一個字符串 判斷字符串是否重復比判斷 Request 對象是否重復容易的多

scrapy中實現

def __init__(self):
    self.fingerprints = set()

def request_seen(self,request):
    fp = self.request_fingerprints(request)
    if fp in self.fingerprints:
        return True
    self.fingerprints.add(fp)

 

對於分布式爬蟲 肯定不能利用每個爬蟲各自的集合來去重 這樣做還是每個主機單獨維護自己的集合 不能做到共享 多台主機
如果生成了相同的request 只能各自去重 各個主機之間就無法做到去重

redis集合

redis提供集合數據結構 在redis集合中存儲每個 Request的指紋

在向 Request 隊列中加入 Request 前首先驗證這個 Request的指紋是否已經加入集合中
如果已存在 則不添加 Request到隊列 如果不存在 則將 Request 添加入隊列並將指紋加入集合
利用同樣的原理 不同的存儲結構 實現了分布式 Request的去重

1.4 防止中斷

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

要做到中斷后繼續爬取 可以將隊列保存起來 下次爬取 直接讀取保存數據即可獲取上次爬取隊列 在scrapy中指定爬取隊列存儲路徑即可
路徑使用JOB_DIR變量標識 可以使用命令實現

scrapy crawl spider -s JOB_DIR=crawlS/spider

 

詳細設置 參考官方文檔 https://doc.scrapy.org/en/latest/topics/jobs.html

在 scrapy 實際把爬取隊列保存到本地 第二次爬取直接讀取並恢復隊列 分布式中爬取隊列本身就是數據庫保存 如果中斷了
數據庫中request依然存在 下次啟動就會接着上次中斷的地方繼續爬取

1.5 架構實現

實現這個架構 首先要實現共享的爬取隊列 還要實現去重 重寫 Scheduler 可以從共享爬取隊列存儲 Request

Scrapy-Redis 提供了分布式的隊列 調度器 去重等功能 GitHub地址 

https://github.com/rmax/scrapy-redis

 

2. Scrapy-Redis 源碼解析

首先下載 源代碼

核心源碼在 

scrapy-redis/src/scrapy_redis

 

2.1 爬取隊列

源碼文件為 queue.py

父類Base 中 _encode_request 和 _decode_request 分別可以實現序列化和反序列化
原因 把Request對象存儲到數據庫中 數據庫無法直接存儲對象 需要先將 Request 序列化轉成字符串

父類中__len__ push pop 都是未實現的 直接使用會報異常
源碼中有三個子類實現

FifoQueue 類 繼承父類 重寫三個方法 都是對server 對象的操作 此爬取隊列使用了Redis的列表 序列化后的 Request存入列表中
push調用 lpush 從列表左側存儲數據 pop調用rpop 操作 從列表右側取出數據
Request 在列表中存取順序是 左側進 右側出 是有序的進出 先進先出

LifoQueue 類 與 FifoQueue相反 使用lpop操作 左側出 push 依然使用lpush 左側入 先進后出 后進先出 存取方式類似棧

PriorityQueue 類 優先級隊列 存儲結果是有序集合

2.2 去重過濾

源碼文件 dupefilter.py

使用的是redis中的集合數據結構

request_seen 和 scrapy中 request_seen 方法類似 使用的是數據庫存儲方式

鑒別重復方式還是使用指紋 依靠request_fingerprint 方法獲取 直接向集合添加指紋 添加成功返回1 表示指紋不存在集合中
代碼中最后返回結果判定添加結果是否為0 如果返回1 判定false 不重復 否則判定重復

2.4 調度器

源碼文件 scheduler.py

核心方法存取方法
enqueue_request向隊列中添加 Request 調用 Queue 的push 操作 還有統計和日志操作

next_request 從隊列取出 Request 調用 Queue 的pop操作 此隊里中如果還有 Request 則直接取出 爬取繼續 如果為空 爬取重新開始


總結

1.爬取隊列的實現 提供三種隊列 使用redis的列表或者集合來維護

2.去重的實現 使用redis集合來保存 Request 的指紋 提供重復過濾

3.中斷就重新爬取的實現 中斷后 reids的隊列沒有清空 爬取再次啟動 調度器 next_request 會從隊列中取到下一個 Request 爬取繼續

以上就是 scrapy-redis中的源碼解析 Scrapy-Redis還提供了 Spider Item Pipline 的實現 不過它們並不是必須使用


3.分布式爬蟲實現

利用 Scrapy-Redis 實現分布式對接

需要安裝 Scrapy-Redis pip install scrapy-redis

驗證 import scrapy_redis 無報錯表示安裝成功


3.1 搭建 Redis服務器

要實現分布式部署 多台主機需要共享爬取隊列和去重集合 而在兩部分內容都是存於 Redis數據庫中的 需要搭建一個公網訪問的 Redis服務器

推薦使用Linux服務器 可以購買阿里雲 騰訊雲 等提供的雲主機 一般都會配有公網IP

需要記錄redis 的運行 IP 端口 地址

3.2 配置 Scrapy-Redis

修改 settings 配置文件

將調度器的類和去重類替換為 Scrapy-Redis 提供的類

SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'

 

配置redis連接信息

REDIS_URL = 'redis://password@host:port'

 

配置調度隊列 (可選)

默認使用 PriorityQueue 可在 settings中修改

SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'

 

配置持久化 (可選)

默認false 會在爬取完成后 清空爬取隊列 和去重指紋集合

SCHEDULER_PERSIST = True (不清空)

 

在強制中斷爬蟲運行時 不會自動清空

配置重爬 (可選)

默認false

SCHEDULER_FLUSH_ON_START = True #每次爬取后清空隊列和指紋

 

單機爬蟲 比較方便 分布式不常用

Pipline配置 (可選)

默認不啟動 scrapy-redis 實現一個存儲到 Redis 的 item pipeline 如果啟用 爬蟲會把生成的item 存儲到 redis數據庫中
數據量比較大的情況下一般不這么做 因為redis是基於內存的 利用它是處理速度快的特性 存儲就太浪費了

ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipline:300',}

 

配置存儲目標

可以在服務器搭建一個MongoDB 服務 存儲目標放在同一個MongoDB中

配置修改

MONGO_URL = 'mongodb://user:password@host:port'

 

3.3 運行
將爬蟲代碼部署到各台主機 即可啟動爬取

每台主機啟動爬蟲后 就會配置redis數據庫中調度request 做到爬取隊列共享和指紋集合共享 同時每台主機占用各自的帶寬和處理器
不會互相影響。

拓展
scrapy-redis 的去重機制是占用內存的 指紋存儲到redis集合中每個指紋長度40 每一位都是16進制
每個十六進制占用4b 一個指紋占用空間20B 一億個占用2GB 爬取數量達到上億級別時 redis占用的內存就會變的很大 僅僅只是指紋存儲
還有隊列存儲的占用 如果多個Scrapy項目同時爬取 內存開銷就是問題

了解 Bloom Filter 中文名布隆過濾器 檢測元素是否在集合中 空間利用效率非常高 大大節省存儲空間

使用位數組表示帶檢測集合 快速用概率算法判斷一個元素是否在集合中 達到去重效果

初始狀態下 聲明一個包含m位的為數組 所有位都是0
有一個待檢測集合 表示為 S={X1,X2,...Xn}需要檢測X是否已經存在集合S中 在 Bloom Filter 算法中 首先使用K個相互獨立 隨機的散列函數
將集合S中的每個元素 X1,X2,...Xn 映射到長度為M的位數組上 散列函數得到結果記作位置索引 然后將位數組該位置索引的位置1

例如 取K為3 表示三個散列函數 X1經過三個散列函數映射得到 結果分別為 1,4,8, X2經過三個散列函數 映射得到結果分別為 4,6,10
位數組的 1,4,6,8,10 五位就會置1

如果有新的元素X 判斷X是否在S集合 仍然用K個散列函數求X映射結果
如果所有結果對應的位數組位置均為1 那么X屬於S集合 如果有一個不為1 則X不屬於S集合

M,n,K 滿足關系 M>nK 位數組的長度M要比集合元素n和散列函數K的乘積還要大
判斷的方法很高效 可以解決Redis內存不足的問題

 

二、分布式爬蟲的部署

將scrapy項目 放到各個主機運行時 可能采用文件上傳或者GIT同步的方式 都需要各台主機都進行操作 如果有100台 1000台 工作量無法預計

1. scrapyd分布式部署

是一個運行Scrapy爬蟲的服務程序提供了一系列HTTP接口 幫助部署 啟動 停止 刪除 爬蟲程序 支持版本管理 同時可以管理多個爬蟲任務
使用時需要調用接口 官方文檔 https://scrapyd.readthedocs.io

daemonstatus.json 查看scrapyd服務和狀態
addversion.json 部署 scrapy項目 打包Egg文件 傳入項目名和版本
schedule.json 負責調度 scrapy項目運行
cancel.json 取消某個爬蟲任務
listprojects.json 列出部署的項目描述
listversions.json 獲取某個項目的所有版本
listspiders.json 獲取某個項目的最新版本
listjobs.json 獲取某個項目運行的所有任務詳情
delversion.json 刪除某個項目版本
delproject.json 刪除某個項目

 

1.2版本后不會自動生成配置文件 需要手動添加 文件名scrapy.conf
內容配置 參考https://scrapyd.readthedocs.io/en/stable/config.html

2. scrapyd API的使用

對scrapyd的封裝 官方文檔 http://python-scrapyd-api.readthedocs.io

3. Scrapy-Client的使用

使用說明 https://github.com/scrapy/scrapyd-client#scrapyd-deploy

4. 雲主機部署

很多服務商都提供雲主機服務 例如 阿里雲 騰訊雲 Azure Amazon 等不同服務商提供了不同的批量部署雲主機的方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM