如何构建一个分布式爬虫:理论篇
## 前言
本系列文章計劃分三個章節進行講述,分別是理論篇、基礎篇和實戰篇。理論篇主要為構建分布式爬蟲而儲備的理論知識,基礎篇會基于理論篇的知識寫一個簡易的分布式爬蟲,實戰篇則會以微博為例,教大家做一個比較完整且足夠健壯的分布式微博爬蟲。通過這三篇文章,希望大家**能掌握如何構建一個分布式爬蟲的方法**;能舉一反三,將`celery`用于除爬蟲外的其它場景。目前基本上的博客都是教大家使用scrapyd(scrapy/scrapyd)或者scrapy-redis(rolando/scrapy-redis)構建分布式爬蟲,本系列文章會從另外一個角度講述如何用requests+celery構建一個健壯的、可伸縮并且可擴展的分布式爬蟲。
本系列文章屬于爬蟲進階文章,期望受眾是具有一定Python基礎知識和編程能力、有爬蟲經驗并且希望提升自己的同學。小白要是感興趣,也可以看看,看不懂的話,可以等有了一定基礎和經驗后回過頭來再看。
另外一點說明,本系列文章不是旨在構建一個分布式爬蟲框架或者分布式任務調度框架,而是利用現有的分布式任務調度工具來實現分布式爬蟲,所以請輕噴。
## 分布式爬蟲概覽
1. 何謂分布式爬蟲?
通俗的講,分布式爬蟲就是多臺機器多個 spider 對多個 url 的同時處理問題,分布式的方式可以極大提高程序的抓取效率。
2. 構建分布式爬蟲通暢需要考慮的問題
(1)如何能保證多臺機器同時抓取同一個URL?
(2)如果某個節點掛掉,會不會影響其它節點,任務如何繼續?
(3)既然是分布式,如何保證架構的可伸縮性和可擴展性?不同優先級的抓取任務如何進行資源分配和調度?
基于上述問題,我選擇使用celery(Celery - Distributed Task Queue)作為分布式任務調度工具,是分布式爬蟲中任務和資源調度的核心模塊。它會把所有任務都通過消息隊列發送給各個分布式節點進行執行,所以可以很好的保證url不會被重復抓取;它在檢測到worker掛掉的情況下,會嘗試向其他的worker重新發送這個任務信息,這樣第二個問題也可以得到解決;celery自帶任務路由,我們可以根據實際情況在不同的節點上運行不同的抓取任務(在實戰篇我會講到)。本文主要就是帶大家了解一下celery的方方面面(有celery相關經驗的同學和大牛可以直接跳過了)
Celery知識儲備
celery基礎講解
按celery官網的介紹來說
Celery 是一個簡單、靈活且可靠的,處理大量消息的分布式系統,并且提供維護這樣一個系統的必需工具。它是一個專注于實時處理的任務隊列,同時也支持任務調度。下面幾個關于celery的核心知識點
1. broker:翻譯過來叫做中間人。它是一個消息傳輸的中間件,可以理解為一個郵箱。每當應用程序調用celery的異步任務的時候,會向broker傳遞消息,而后celery的worker將會取到消息,執行相應程序。這其實就是消費者和生產者之間的橋梁。
2. backend: 通常程序發送的消息,發完就完了,可能都不知道對方時候接受了。為此,celery實現了一個backend,用于存儲這些消息以及celery執行的一些消息和結果。
3. worker: Celery類的實例,作用就是執行各種任務。注意在celery3.1.25后windows是不支持celery worker的!
4. producer: 發送任務,將其傳遞給broker
5. beat: celery實現的定時任務。可以將其理解為一個producer,因為它也是通過網絡調用定時將任務發送給worker執行。注意在windows上celery是不支持定時任務的!
下面是關于celery的架構示意圖,結合上面文字的話應該會更好理解
由于celery只是任務隊列,而不是真正意義上的消息隊列,它自身不具有存儲數據的功能,所以broker和backend需要通過第三方工具來存儲信息,celery官方推薦的是 RabbitMQ(Messaging that just works)和Redis(Redis),另外mongodb等也可以作為broker或者backend,可能不會很穩定,我們這里選擇Redis作為broker兼backend。
關于redis的安裝和配置可以查看這里(ResolveWang/WeiboSpider)
實際例子
先安裝celery
pip install celery我們以官網給出的例子來做說明,并對其進行擴展。首先在項目根目錄下,這里我新建一個項目叫做celerystudy,然后切換到該項目目錄下,新建文件tasks.py,然后在其中輸入下面代碼
from celery import Celeryapp = Celery('tasks', broker='redis://:''@223.129.0.190:6379/2', backend='redis://:''@223.129.0.190:6379/3')@app.task def add(x, y):return x + y這里我詳細講一下代碼:我們先通過app=Celery()來實例化一個celery對象,在這個過程中,我們指定了它的broker,是redis的db 2,也指定了它的backend,是redis的db3, broker和backend的連接形式大概是這樣
redis://:password@hostname:port/db_number然后定義了一個add函數,重點是@app.task,它的作用在我看來就是將add()
注冊為一個類似服務的東西,本來只能通過本地調用的函數被它裝飾后,就可以通過網絡來調用。這個tasks.py中的app就是一個worker。它可以有很多任務,比如這里的任務函數add。我們再通過在命令行切換到項目根目錄,執行
celery -A tasks worker -l info啟動成功后就是下圖所示的樣子
這里我說一下各個參數的意思,-A指定的是app(即Celery實例)所在的文件模塊,我們的app是放在tasks.py中,所以這里是 tasks;worker表示當前以worker的方式運行,難道還有別的方式?對的,比如運行定時任務就不用指定worker這個關鍵字; -l info表示該worker節點的日志等級是`info`,更多關于啟動worker的參數(比如`-c`、`-Q`等常用的)請使用
celery worker --help進行查看
將worker啟動起來后,我們就可以通過網絡來調用`add`函數了。我們在后面的分布式爬蟲構建中也是采用這種方式分發和消費url的。在命令行先切換到項目根目錄,然后打開python交互端
from tasks import addrs = add.delay(2, 2) # 這里的add.delay就是通過網絡調用將任務發送給`add`所在的worker執行
這個時候我們可以在worker的界面看到接收的任務和計算的結果。
[2017-05-19 14:22:43,038: INFO/MainProcess] Received task: tasks.add[c0dfcd0b-d05f-4285-b944-0a8aba3e7e61] # worker接收的任務[2017-05-19 14:22:43,065: INFO/MainProcess] Task tasks.add[c0dfcd0b-d05f-4285-b944-0a8aba3e7e61] succeeded in 0.025274309000451467s: 4 # 執行結果
這里是異步調用,如果我們需要返回的結果,那么要等`rs`的`ready`狀態`true`才行。這里`add`看不出效果,不過試想一下,如果我們是調用的比較占時間的io任務,那么異步任務就比較有價值了
rs # <AsyncResult: c0dfcd0b-d05f-4285-b944-0a8aba3e7e61>rs.ready() # true 表示已經返回結果了
rs.status # 'SUCCESS' 任務執行狀態,失敗還是成功rs.successful() # True 表示執行成功
rs.result # 4 返回的結果rs.get() # 4 返回的結果
<celery.backends.redis.RedisBackend object at 0x30033ec> #這里我們backend 結果存儲在redis里
上面講的是從Python交互終端中調用`add`函數,如果我們要從另外一個py文件調用呢?除了通過```import```然后```add.delay()```這種方式,我們還可以通過```send_task()```這種方式,我們在項目根目錄另外新建一個py文件叫做 `excute_tasks.py`,在其中寫下如下的代碼
from tasks import addif __name__ == '__main__':add.delay(5, 10)
這時候可以在celery的worker界面看到執行的結果
[2017-05-19 14:25:48,039: INFO/MainProcess] Received task: tasks.add[f5ed0d5e-a337-45a2-a6b3-38a58efd9760]
[2017-05-19 14:25:48,074: INFO/MainProcess] Task tasks.add[f5ed0d5e-a337-45a2-a6b3-38a58efd9760] succeeded in 0.03369094600020617s: 15
此外,我們還可以通過```send_task()```來調用,將```excute_tasks.py```改成這樣
from tasks import app if __name__ == '__main__':app.send_task('tasks.add', args=(10, 15),)
這種方式也是可以的。`send_task()`還可能接收到為注冊(即通過`@app.task`裝飾)的任務,這個時候worker會忽略這個消息
[2017-05-19 14:34:15,352: ERROR/MainProcess] Received unregistered task of type 'tasks.adds'.
The message has been ignored and discarded.
定時任務
上面部分講了怎么啟動worker和調用worker的相關函數,這里再講一下celery的定時任務。
爬蟲由于其特殊性,可能需要定時做增量抓取,也可能需要定時做模擬登陸,以防止cookie過期,而celery恰恰就實現了定時任務的功能。在上述基礎上,我們將`tasks.py`文件改成如下內容
from celery import Celery app = Celery('add_tasks', broker='redis:''//223.129.0.190:6379/2', backend='redis:''//223.129.0.190:6379/3') app.conf.update(# 配置所在時區 CELERY_TIMEZONE='Asia/Shanghai',CELERY_ENABLE_UTC=True,# 官網推薦消息序列化方式為json CELERY_ACCEPT_CONTENT=['json'],CELERY_TASK_SERIALIZER='json',CELERY_RESULT_SERIALIZER='json',# 配置定時任務CELERYBEAT_SCHEDULE={'my_task': {'task': 'tasks.add', # tasks.py模塊下的add方法'schedule': 60, # 每隔60運行一次'args': (23, 12),}} ) @app.task def add(x, y):return x + y
然后先通過`ctrl+c`停掉前一個worker,因為我們代碼改了,需要重啟worker才會生效。我們再次以`celery -A tasks worker -l info`這個命令開啟worker。
這個時候我們只是開啟了worker,如果要讓worker執行任務,那么還需要通過beat給它定時發送,我們再開一個命令行,切換到項目根目錄,通過
celery beat -A tasks -l infocelery beat v3.1.25 (Cipater) is starting.
__ - ... __ - _
Configuration ->
. broker -> redis://223.129.0.190:6379/2
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> now (0s)
[2017-05-19 15:56:57,125: INFO/MainProcess] beat: Starting...
這樣就表示定時任務已經開始運行了。
眼尖的同學可能看到我這里celery的版本是`3.1.25`,這是因為celery支持的`windows`最高版本是3.1.25。由于我的分布式微博爬蟲的worker也同時部署在了windows上,所以我選擇了使用 `3.1.25`。如果全是linux系統,建議使用celery4。
此外,還有一點需要注意,在celery4后,定時任務(通過schedule調度的會這樣,通過crontab調度的會馬上執行)會在當前時間再過定時間隔執行第一次任務,比如我這里設置的是60秒的間隔,那么第一次執行`add`會在我們通過`celery beat -A tasks -l info`啟動定時任務后60秒才執行;celery3.1.25則會馬上執行該任務。
關于定時任務更詳細的請看官方文檔celery定時任務(Periodic Tasks - Celery 4.0.2 documentation)
至此,我們把構建一個分布式爬蟲的理論知識都講了一遍,主要就是對于`celery`的了解和使用,這里并未涉及到celery的一些高級特性,實戰篇可能會講解一些我自己使用的特性。
下一篇我將介紹如何使用celery寫一個簡單的分布式爬蟲,希望大家能有所收獲。
此外,打一個廣告,我寫了一個分布式的微博爬蟲(ResolveWang/WeiboSpider),主要就是利用celery做的分布式實戰篇我也將會以該項目其中一個模塊進行講解,有興趣的可以點擊看看,也歡迎有需求的朋友試用。
https://zhuanlan.zhihu.com/p/27148122
總結
以上是生活随笔為你收集整理的如何构建一个分布式爬虫:理论篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bips
- 下一篇: 如何构建一个分布式爬虫:基础篇