前言
本系列文章計劃分三個章節進行講述,分別是理論篇、基礎篇和實戰篇。理論篇主要為構建分散式爬蟲而儲備的理論知識,基礎篇會基於理論篇的知識寫一個簡易的分散式爬蟲,實戰篇則會以微博為例,教大家做一個比較完整且足夠健壯的分散式微博爬蟲。通過這三篇文章,希望大家能掌握如何構建一個分散式爬蟲的方法;能舉一反三,將celery
用於除爬蟲外的其它場景。目前基本上的部落格都是教大家使用scrapyd或者scrapy-redis構建分散式爬蟲,本系列文章會從另外一個角度講述如何用requests+celery構建一個健壯的、可伸縮並且可擴充套件的分散式爬蟲。
本系列文章屬於爬蟲進階文章,期望受眾是具有一定Python基礎知識和程式設計能力、有爬蟲經驗並且希望提升自己的同學。小白要是感興趣,也可以看看,看不懂的話,可以等有了一定基礎和經驗後回過頭來再看。
另外一點說明,本系列文章不是旨在構建一個分散式爬蟲框架或者分散式任務排程框架,而是利用現有的分散式任務排程工具來實現分散式爬蟲,所以請輕噴。
分散式爬蟲概覽
- 何謂分散式爬蟲?
通俗的講,分散式爬蟲就是多臺機器多個 spider 對多個 url 的同時處理問題,分散式的方式可以極大提高程式的抓取效率。 - 構建分散式爬蟲通暢需要考慮的問題
(1)如何能保證多臺機器同時抓取同一個URL?
(2)如果某個節點掛掉,會不會影響其它節點,任務如何繼續?
(3)既然是分散式,如何保證架構的可伸縮性和可擴充套件性?不同優先順序的抓取任務如何進行資源分配和排程?
基於上述問題,我選擇使用celery作為分散式任務排程工具,是分散式爬蟲中任務和資源排程的核心模組。它會把所有任務都通過訊息佇列傳送給各個分散式節點進行執行,所以可以很好的保證url不會被重複抓取;它在檢測到worker掛掉的情況下,會嘗試向其他的worker重新傳送這個任務資訊,這樣第二個問題也可以得到解決;celery自帶任務路由,我們可以根據實際情況在不同的節點上執行不同的抓取任務(在實戰篇我會講到)。本文主要就是帶大家瞭解一下celery的方方面面(有celery相關經驗的同學和大牛可以直接跳過了)
Celery知識儲備
celery基礎講解
按celery官網的介紹來說
Celery 是一個簡單、靈活且可靠的,處理大量訊息的分散式系統,並且提供維護這樣一個系統的必需工具。它是一個專注於實時處理的任務佇列,同時也支援任務排程。
下面幾個關於celery的核心知識點
- broker:翻譯過來叫做中間人。它是一個訊息傳輸的中介軟體,可以理解為一個郵箱。每當應用程式呼叫celery的非同步任務的時候,會向broker傳遞訊息,而後celery的worker將會取到訊息,執行相應程式。這其實就是消費者和生產者之間的橋樑。
- backend: 通常程式傳送的訊息,發完就完了,可能都不知道對方時候接受了。為此,celery實現了一個backend,用於儲存這些訊息以及celery執行的一些訊息和結果。
- worker: Celery類的例項,作用就是執行各種任務。注意在celery3.1.25後windows是不支援celery worker的!
- producer: 傳送任務,將其傳遞給broker
- beat: celery實現的定時任務。可以將其理解為一個producer,因為它也是通過網路呼叫定時將任務傳送給worker執行。注意在windows上celery是不支援定時任務的!
下面是關於celery的架構示意圖,結合上面文字的話應該會更好理解
由於celery只是任務佇列,而不是真正意義上的訊息佇列,它自身不具有儲存資料的功能,所以broker和backend需要通過第三方工具來儲存資訊,celery官方推薦的是 RabbitMQ和Redis,另外mongodb等也可以作為broker或者backend,可能不會很穩定,我們這裡選擇Redis作為broker兼backend。
關於redis的安裝和配置可以檢視這裡
實際例子
先安裝celery
1 |
pip install celery |
我們以官網給出的例子來做說明,並對其進行擴充套件。首先在專案根目錄下,這裡我新建一個專案叫做celerystudy
,然後切換到該專案目錄下,新建檔案tasks.py
,然後在其中輸入下面程式碼
1 2 3 4 5 6 7 |
from celery import Celery app = 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的連線形式大概是這樣
1 |
redis://:password@hostname:port/db_number |
然後定義了一個add
函式,重點是@app.task
,它的作用在我看來就是將add()
註冊為一個類似服務的東西,本來只能通過本地呼叫的函式被它裝飾後,就可以通過網路來呼叫。這個tasks.py
中的app就是一個worker。它可以有很多工,比如這裡的任務函式add
。我們再通過在命令列切換到專案根目錄,執行
1 |
celery -A tasks worker -l info |
啟動成功後就是下圖所示的樣子
這裡我說一下各個引數的意思,-A
指定的是app(即Celery例項)所在的檔案模組,我們的app是放在tasks.py
中,所以這裡是 tasks
;worker表示當前以worke
r的方式執行,難道還有別的方式?對的,比如執行定時任務就不用指定worker
這個關鍵字; -l info
表示該worker節點的日誌等級是info
,更多關於啟動worker的引數(比如-c
、-Q
等常用的)請使用
1 |
celery worker --help |
進行檢視
將worker啟動起來後,我們就可以通過網路來呼叫add
函式了。我們在後面的分散式爬蟲構建中也是採用這種方式分發和消費url的。在命令列先切換到專案根目錄,然後開啟python互動端
1 2 |
from tasks import add rs = add.delay(2, 2) # 這裡的add.delay就是通過網路呼叫將任務傳送給add所在的worker執行 |
這個時候我們可以在worker的介面看到接收的任務和計算的結果。
1 2 3 |
[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任務,那麼非同步任務就比較有價值了
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
,在其中寫下如下的程式碼
1 2 3 4 |
from tasks import add if __name__ == '__main__': add.delay(5, 10) |
這時候可以在celery的worker介面看到執行的結果
1 2 |
[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
改成這樣
1 2 3 |
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
檔案改成如下內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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給它定時傳送,我們再開一個命令列,切換到專案根目錄,通過
1 |
celery beat -A tasks -l info |
1 2 3 4 5 6 7 8 9 10 |
celery 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定時任務
至此,我們把構建一個分散式爬蟲的理論知識都講了一遍,主要就是對於celery
的瞭解和使用,這裡並未涉及到celery的一些高階特性,實戰篇可能會講解一些我自己使用的特性。
下一篇我將介紹如何使用celery寫一個簡單的分散式爬蟲,希望大家能有所收穫。
此外,打一個廣告,我寫了一個分散式的微博爬蟲,主要就是利用celery做的分散式實戰篇我也將會以該專案其中一個模組進行講解,有興趣的可以點選看看,也歡迎有需求的朋友試用。