pyspider優(yōu)勢所在
pyspider非常適合那種很小很雜的爬蟲的管理,比如有100個小網(wǎng)站,規(guī)則又各不相同,我要獲取他的一些很簡單的內(nèi)容,如標題,所有的圖片,正文內(nèi)容。他分為幾個模塊:scheduler,fetcher,processor,resultworker以及一個ui,前三者各自分離,用消息隊列連接,因此很容易做成分布式(或者說設計之初就是為了分布式的)。
scheduler
了解scheduler之前,先了解兩個概念,一個是project,代表著一個項目,如百度爬蟲項目;一個是task,代表一個爬取任務,如爬取百度首頁,爬取某一個新聞業(yè),都是一個task。
與scheduler相關(guān)的隊列有三個
- scheduler2fetcher 也就是scheduler中的out queue,用于發(fā)送task給fetcher
- status_queue 用于從processor中獲取已經(jīng)爬取的task的狀態(tài)并做相應處理
- newtask_queue 新產(chǎn)生的task
scheduler負責調(diào)度,與scrapy或者其他的爬蟲框架類似,調(diào)度器負責調(diào)度需要爬取的內(nèi)容,決定哪些內(nèi)容在哪些時候進行爬取。我們從代碼入手看下pyspider的調(diào)度器做了啥。
def run(self):
while not self._quit:
self.run_once()
def run_once(self):
self._update_projects()
self._check_task_done()
self._check_request()
while self._check_cronjob():
pass
self._check_select()
self._check_delete()
self._try_dump_cnt()
入口為run函數(shù),真正有用的是run_once函數(shù)。我們可以看到,每一輪調(diào)度都會依次調(diào)用幾個方法。
_update_projects
該方法會從projectdb中讀取是有有新的project更新,如果更新了就得處理這個project
_check_task_done ?
該方法會消費status queue,爬取失敗的task,檢查下要不要重新爬,標記一下,存起來。爬取成功的task,看下是否要再爬一次,標記一下,存起來。
_check_request
消費newtask_queue,該隊列為待爬取的隊列,任務取出來,處理處理,標記一下,存起來。
_check_cronjob
看下有沒有什么定時任務觸發(fā)了,有的話,丟到out queue(scheduler2fetcher)給fetcher爬去。
_check_select
之前不是標記并存了好多要爬取的任務咩,取出來,丟給out queue給fetcher爬去。
_check_delete
處理一些被標記為刪除的project
_try_dump_cnt
本輪結(jié)束,記個數(shù)。
scheduler邏輯相當清晰,分工也很明確:找到需要爬取的任務給fetcher。
fetcher
fetcher的職責更為清晰:下載。
與他相關(guān)的有兩個隊列
- scheduler2fetcher 也是fetcher中的inqueue,調(diào)度器傳給fetcher的任務
- fetcher2processor 也是fetcher中的outqueue,fetcher傳給processor的任務
fetcher的入口也是run方法,會從inqueue中讀取任務去爬取。整個fetcher是基于tornado實現(xiàn)的(說真,tornado在py3 async的時代看起來顯得好丑..)并提供了幾種爬取的方式。這部分代碼很簡單,不細說了,就是下載下來,爬取結(jié)束之后發(fā)送到outqueue中。
processor
涉及到四個隊列
- fetcher2processor 也是inqueue,為fetcher的輸入
- status_queue 把fetcher爬到的內(nèi)容輸出給scheduler
- newtask_queue 新任務隊列,一個task可能會產(chǎn)生多個新的task,傳遞給scheduler
- processor2result 也是result_queue,輸出獲取到的需要的數(shù)據(jù),為最終的輸出
程序的入口同樣為run,核心方法只有一個,就是on_task,處理唯一的輸入inqueue中獲取到的task,主要做了這么幾件事
處理下task,該找外鏈的找外鏈,該獲取格式化數(shù)據(jù)的獲取數(shù)據(jù),并發(fā)送到result_queue中。(這部分在ProjectManager這個類的on_result方法中完成)
把task的內(nèi)容做一些處理,形成一個新的dict,包含爬取狀態(tài),時間等信息,發(fā)到status_queue
處理找到的外鏈(如果有需要的話,即在回調(diào)中有調(diào)用
self.crawl)包裝一下,發(fā)送給newtask_queue
result worker
result worker只涉及到一個隊列,就是processor中輸出的result queue。
這部分我覺得是pyspider比較弱的一部分,類似于scrapy中的Pipeline,對輸出的數(shù)據(jù)進行一些處理,如保存數(shù)據(jù)庫等。需要繼承實現(xiàn)一個ResultWorker類。默認的這個類會把數(shù)據(jù)保存到resultdb中,但我們實際需要的肯定不止如此,可以重寫on_result方法做一些處理。
不過因為所有的輸出都在一個隊列,所以result worker也只能有一類(并不是一個,可以做分布式處理),處理一個類似的邏輯,比如統(tǒng)統(tǒng)都保存到mongo?;蛘咴谝粋€result worker中寫判斷語句,進行不同的邏輯處理。但這樣就不夠優(yōu)雅了。
總結(jié)
pyspider應該算是一個相當不錯的框架,代碼很清晰,很適合去讀。不過適合的場景還是比較有限,著重于調(diào)度,分布式爬取,弱化了對數(shù)據(jù)的處理部分(當然,這部分也可以很方便的擴展)。