pyspider框架的架構(gòu)
1.概述
下圖顯示了pyspider體系結(jié)構(gòu)及其組件的概述,以及系統(tǒng)內(nèi)部發(fā)生的數(shù)據(jù)流的概要安疗。
組件之間通過(guò)消息隊(duì)列進(jìn)行連接
人芽。每一個(gè)組件都包含消息隊(duì)列棉姐,都在它們自己的進(jìn)程/線程中運(yùn)行,并且是可以替換的鸵赖。這意味者毕贼,當(dāng)處理速度緩慢時(shí)温赔,這個(gè)時(shí)候我們可以通過(guò)啟動(dòng)多個(gè)processor
實(shí)例來(lái)充分利用多核cpu來(lái)進(jìn)行提高效率,或者進(jìn)行分布式部署來(lái)提高效率鬼癣。
2.組件
(1)Scheduler(調(diào)度器)
調(diào)度器從processor
返回的新任務(wù)隊(duì)列中接收任務(wù)陶贼。判斷是新任務(wù)還是需要重新爬取啤贩。通過(guò)優(yōu)先級(jí)對(duì)任務(wù)進(jìn)行分類,并且利用令牌桶算法
將任務(wù)發(fā)送給fetcher
拜秧, 處理周期任務(wù)痹屹,丟失的任務(wù)和失敗的任務(wù),并且稍后重試枉氮。
以上都可以通過(guò)self.crawl
進(jìn)行設(shè)置(我們的第一個(gè)腳本就是一個(gè)processor)志衍。
注意,在當(dāng)前的調(diào)度器實(shí)現(xiàn)中聊替,只允許一個(gè)調(diào)度器去進(jìn)行運(yùn)行楼肪。
(2)Fetcher(本質(zhì)就是一個(gè)請(qǐng)求,只是將Scheduler發(fā)出的請(qǐng)求進(jìn)行處理后變成結(jié)果惹悄,而對(duì)于Processor來(lái)說(shuō)它是一個(gè)請(qǐng)求)---爬取器
Fetcher
的職責(zé)是獲取web頁(yè)面然后把結(jié)果發(fā)送給processor
春叫。請(qǐng)求method, headers泣港, cookies暂殖, proxy, etag
等当纱,都可以設(shè)置央星。
(3)Processor(如果返回結(jié)果就輸出,如果返回進(jìn)行craw()方法就傳給Sheduler)---處理器
處理器的職責(zé)是運(yùn)行用戶編寫(xiě)的腳本
惫东,去解析和提取信息
。您的腳本在無(wú)限制的環(huán)境中運(yùn)行毙石。盡管我們有各種各樣的工具(如PyQuery廉沮,還可以用xpath和beautifulsoup)供您提取信息和連接,您可以使用任何您想使用的方法來(lái)處理響應(yīng)徐矩。
處理器會(huì)捕捉異常和記錄日志滞时,發(fā)送狀態(tài)(任務(wù)跟蹤)和新的任務(wù)給調(diào)度器,發(fā)送結(jié)果給Result Worker
滤灯。
(4)Result Worker
Result Worker
從Porcess
接收結(jié)果數(shù)據(jù)坪稽。Pyspider有一個(gè)內(nèi)置的結(jié)果處理器將數(shù)據(jù)保存到resultdb
,根據(jù)您的需要重寫(xiě)它以處理結(jié)果。
(5)WebUI
WebUI是一個(gè)面向內(nèi)容的web前端鳞骤。它包含:
- 腳本編輯器窒百,調(diào)試器
- 項(xiàng)目管理器
- 任務(wù)監(jiān)控程序
- 結(jié)果查看器和導(dǎo)出
也許webui是pyspider最吸引人的地方。使用這個(gè)強(qiáng)大的UI豫尽,您可以像pyspider一樣一步一步地調(diào)試腳本篙梢。啟動(dòng)停止項(xiàng)目。找到哪個(gè)項(xiàng)目出錯(cuò)了美旧,什么請(qǐng)求失敗了渤滞,然后使用調(diào)試器再試一次贬墩。
(6)Data flow
pyspider中的數(shù)據(jù)流如上圖所示:
- 當(dāng)您按下WebUI上的Run按鈕時(shí),每個(gè)腳本都有一個(gè)名為on_start的回調(diào)妄呕。作為項(xiàng)目的入口陶舞,
on_start
產(chǎn)生的新任務(wù)將會(huì)提交給調(diào)度器。 - 調(diào)度程序使用一個(gè)數(shù)據(jù)URI將這個(gè)on_start任務(wù)分派為要獲取的普通任務(wù)绪励。
- Fetcher對(duì)它發(fā)出一個(gè)請(qǐng)求和一個(gè)響應(yīng)(對(duì)于數(shù)據(jù)URI肿孵,它是一個(gè)假的請(qǐng)求和響應(yīng),但與其他正常任務(wù)沒(méi)有區(qū)別)优炬,然后送給處理器颁井。
- 處理器調(diào)用on_start方法并生成一些要抓取的新URL。處理器向調(diào)度程序發(fā)送一條消息蠢护,說(shuō)明此任務(wù)已完成雅宾,并通過(guò)消息隊(duì)列將新任務(wù)發(fā)送給調(diào)度程序(在大多數(shù)情況下,這里沒(méi)有on_start的結(jié)果葵硕。如果有結(jié)果眉抬,處理器將它們發(fā)送到result_queue)。
- 調(diào)度程序接收新任務(wù)懈凹,在數(shù)據(jù)庫(kù)中查找蜀变,確定任務(wù)是新的還是需要重新抓取,如果是介评,將它們放入任務(wù)隊(duì)列库北,按順序分派任務(wù)。
- 這個(gè)過(guò)程重復(fù)(從步驟3開(kāi)始)们陆,直到WWW死后才停止;)寒瓦。調(diào)度程序?qū)z查定期任務(wù),以抓取最新數(shù)據(jù)坪仇。
3.關(guān)于任務(wù)
任務(wù)是調(diào)度的基本單元(就是進(jìn)行調(diào)度的過(guò)程就是對(duì)任務(wù)進(jìn)行調(diào)度)杂腰。
(1)基本原理
- 任務(wù)由它的
taskid
(默認(rèn):md5(url)
, 可以通過(guò)重寫(xiě)get_taskid(self, task)
方法來(lái)修改,在第一個(gè)腳本里重寫(xiě)函數(shù)方法)來(lái)區(qū)分椅文。 - 不同項(xiàng)目之間的任務(wù)是隔離的喂很。
- 一個(gè)任務(wù)有4個(gè)狀態(tài):
- active
- failed
- success
- bad-not used
- 只有處于
active
狀態(tài)的任務(wù)會(huì)被調(diào)度。 - 任務(wù)按優(yōu)先次序執(zhí)行皆刺。
(2)調(diào)度
a少辣、新任務(wù)
當(dāng)一個(gè)新的任務(wù)(從未見(jiàn)過(guò))出現(xiàn):
- 如果設(shè)置了exetime但沒(méi)有到達(dá),它將被放入一個(gè)基于時(shí)間的隊(duì)列中等待芹橡。
- 否則將被接受毒坛。
當(dāng)任務(wù)已經(jīng)在隊(duì)列中:
- 除非(
force_update
)強(qiáng)制更新,否則忽略
當(dāng)一個(gè)完成過(guò)的任務(wù)出現(xiàn)時(shí)(重復(fù)的任務(wù)):
- 如果
age
設(shè)置,且last_crawl_time + age < now(上一次的抓取時(shí)間+age設(shè)置的時(shí)間小于當(dāng)前時(shí)間)
它將會(huì)被接受煎殷,否則拋棄屯伞。 - 如果
itag
設(shè)置,且它不等于上一次的值豪直,它會(huì)被接受劣摇,否則拋棄(進(jìn)行itag設(shè)置使得所有的新任務(wù)進(jìn)行啟動(dòng)一遍)。
b弓乙、 重試
當(dāng)請(qǐng)求錯(cuò)誤或腳本錯(cuò)誤發(fā)生時(shí)末融,任務(wù)將在默認(rèn)情況下重試3次。
第一次重試將在30秒暇韧、1小時(shí)勾习、6小時(shí)、12小時(shí)后每次執(zhí)行懈玻,任何更多的重試將推遲24小時(shí)巧婶。retry_delay
是一個(gè)指定重試間隔的字典。這個(gè)字典中的元素是{retried:seconds}
, 如果未指定涂乌,則使用特殊鍵:''
空字符串指定的默認(rèn)推遲時(shí)間艺栈。
例如默認(rèn)的retry_delay
聲明如下:
class MyHandler(BaseHandler):
retry_delay = {
0: 30,
1: 1*60*60,
2: 6*60*60,
3: 12*60*60,
'': 24*60*60
}
4.關(guān)于項(xiàng)目
在大多數(shù)情況下,項(xiàng)目是為一個(gè)網(wǎng)站編寫(xiě)的一個(gè)腳本(一個(gè)網(wǎng)站一個(gè)項(xiàng)目)湾盒。
- 項(xiàng)目是獨(dú)立的湿右,但是可以用
from Projects import other_project
將另一個(gè)項(xiàng)目作為模塊導(dǎo)入。 - 一個(gè)項(xiàng)目有5個(gè)狀態(tài):
TODO
,STOP
,CHECKING
,DEBUG
和RUNNING
-
TODO
- 剛創(chuàng)建罚勾,正在編寫(xiě)腳本 - 如果您希望項(xiàng)目停止(= =)毅人,可以將其標(biāo)記為STOP。
-
CHECKING
- 當(dāng)正在運(yùn)行的項(xiàng)目被修改時(shí)尖殃,為了防止未完成的修改堰塌,項(xiàng)目狀態(tài)將被設(shè)置為自動(dòng)檢查。 -
DEBUG
/RUNNING
- 這兩種狀態(tài)對(duì)爬蟲(chóng)沒(méi)有區(qū)別分衫。但最好在第一次運(yùn)行時(shí)將其標(biāo)記為DEBUG
,然后在檢查后將其更改為RUNNING
般此。
-
- 爬行速率由
rate
和burst
并采用令牌桶算法
進(jìn)行控制蚪战。-
rate
- 一秒鐘內(nèi)有多少請(qǐng)求(即一秒鐘爬多少次) -
burst
(爆發(fā))- 考慮這種情況,RATE/BURST=0.1/3铐懊,這意味著蜘蛛每10秒抓取1個(gè)頁(yè)面邀桑。所有任務(wù)都已完成,Project將每分鐘檢查最后更新的項(xiàng)目科乎。假設(shè)找到3個(gè)新項(xiàng)目壁畸,pyspider將“爆發(fā)”并爬行3個(gè)任務(wù),而不等待3*10秒。但是捏萍,第四個(gè)任務(wù)需要等待10秒太抓。
-
- 若要?jiǎng)h除項(xiàng)目,請(qǐng)將“組”(group)設(shè)置為“刪除”(delete)令杈,將“狀態(tài)”設(shè)置為“停止”走敌,然后等待24小時(shí)。
回調(diào) on_finished
您可以在項(xiàng)目中重寫(xiě)on_finished方法逗噩,當(dāng)task_queue變?yōu)?時(shí)將觸發(fā)該方法掉丽。
第一種情況:當(dāng)您啟動(dòng)一個(gè)項(xiàng)目來(lái)抓取一個(gè)包含100個(gè)頁(yè)面的網(wǎng)站時(shí),當(dāng)100個(gè)頁(yè)面被成功抓取或重試失敗時(shí)异雁,on_finished
回調(diào)將被觸發(fā)捶障。
第二種情況:帶有auto_recrawl
任務(wù)的項(xiàng)目永遠(yuǎn)不會(huì)觸發(fā)on_finished
回調(diào),因?yàn)楫?dāng)其中有auto_recrawl
任務(wù)時(shí)纲刀,時(shí)間隊(duì)列永遠(yuǎn)不會(huì)變?yōu)?项炼。
第三種情況:帶有@every
修飾方法的項(xiàng)目將在每次新提交的任務(wù)完成時(shí)觸發(fā)on_finished
回調(diào)。
5. 腳本環(huán)境
(1) 變量
self.project_name
self.project
(當(dāng)前項(xiàng)目的詳細(xì)信息)self.response
self.task
官方文檔:http://docs.pyspider.org/en/latest/
(2)關(guān)于腳本
handler
的名稱并不重要柑蛇,但您需要至少一個(gè)繼承basehandler
的類芥挣。-
可以設(shè)置第三個(gè)參數(shù)來(lái)獲取任務(wù)對(duì)象:
def callback(self,response,task)
。
默認(rèn)情況下耻台,非200響應(yīng)不會(huì)提交回調(diào)空免。可以使用
@catch_status_code_error
來(lái)處理非200響應(yīng)盆耽。
(3)關(guān)于環(huán)境
logging
,print
以及異常會(huì)被捕獲蹋砚。你可以通過(guò)
from projects import some_project
將其他項(xiàng)目當(dāng)做模塊導(dǎo)入。
(4)Web view
- 以瀏覽器呈現(xiàn)的方式查看頁(yè)面(大約)
(5)HTML view
- 查看當(dāng)前回調(diào)的HTML(索引頁(yè)摄杂、細(xì)節(jié)頁(yè)等)
(6) Follows view
查看可從當(dāng)前回調(diào)進(jìn)行的回調(diào)(數(shù)量坝咐,即回調(diào)任務(wù)的數(shù)量和相應(yīng)的url)
索引頁(yè)面跟隨視圖將顯示可執(zhí)行的詳細(xì)頁(yè)面回調(diào)。
(7)Messages view
- 顯示
self.send_message
發(fā)送的消息
(8)Enable CSS Selector Helper
- 啟用Web視圖的CSS選擇器幫助程序析恢。它獲取您單擊的元素的CSS選擇器墨坚,然后將其添加到腳本中。
6.處理結(jié)果
從WebUI下載和查看數(shù)據(jù)很方便映挂,但可能不適用于計(jì)算機(jī)泽篮,所以要存儲(chǔ)到自己的數(shù)據(jù)庫(kù)中。
(1)使用resultdb
雖然resultdb僅用于結(jié)果預(yù)覽柑船,但不適用于大規(guī)模存儲(chǔ)結(jié)果數(shù)據(jù)
帽撑。
但是,如果您想從resultdb中獲取數(shù)據(jù)鞍时,那么有一些使用數(shù)據(jù)庫(kù)API的簡(jiǎn)單案例可以幫助您連接和選擇數(shù)據(jù)亏拉。
from pyspider.database import connect_database
resultdb = connect_database("<your resutldb connection url>")
for project in resultdb.projects:
for result in resultdb.select(project):
assert result['taskid']
assert result['url']
assert result['result']
result['result']
是由你編寫(xiě)的腳本中的RETURN
語(yǔ)句提交的對(duì)象扣蜻。
(2)使用ResultWorker
在生產(chǎn)環(huán)境中,你可能希望將pyspider連接到你的系統(tǒng)的處理管道及塘,而不是將結(jié)果存儲(chǔ)到resultdb中莽使。強(qiáng)烈建議重寫(xiě)ResultWorker。
from pyspider.result import ResultWorker
class MyResultWorker(ResultWorker):
def on_result(self, task, result):
assert task['taskid']
assert task['project']
assert task['url']
assert result
# your processing code goes here
result
是你腳本中return語(yǔ)句提交的對(duì)象磷蛹。
您可以將這個(gè)腳本(例如吮旅,my_result_worker.py)放在啟動(dòng)pyspider的文件夾中。
為result_worker
子命令添加參數(shù):
pyspider result_worker --result-cls=my_result_worker.MyResultWorker
(運(yùn)行命令的時(shí)候就這樣運(yùn)行)
或者,如果你使用配置文件
{
...
"result_worker": {
"result_cls": "my_result_worker.MyResultWorker"
}
...
}
為了兼容性味咳,將存儲(chǔ)在數(shù)據(jù)庫(kù)中的結(jié)果編碼為JSON(因?yàn)閞esult_worker存儲(chǔ)到數(shù)據(jù)庫(kù)的數(shù)據(jù)都是JSON類型的)庇勃。強(qiáng)烈建議您設(shè)計(jì)自己的數(shù)據(jù)庫(kù),并覆蓋上面描述的ResultWorker槽驶。
(3)關(guān)于結(jié)果的小技巧
想要在回調(diào)中返回多個(gè)結(jié)果?
resultdb會(huì)通過(guò)taskid(url)對(duì)結(jié)果進(jìn)行去重责嚷,后面的結(jié)果會(huì)覆蓋前面的(但是在腳本中每個(gè)任務(wù)只會(huì)返回一個(gè)結(jié)果,如果有多個(gè)結(jié)果的話只會(huì)返回最后一個(gè)數(shù)據(jù)結(jié)果)掂铐。
一個(gè)解決方案是使用send_message
API為每個(gè)結(jié)果生成一個(gè)偽taskid罕拂。
def detail_page(self, response):
for li in response.doc('li').items():
self.send_message(self.project_name, {
...寫(xiě)入要發(fā)的結(jié)果
# "url": response.url,
# "title": response.doc('title').text(),
}, url=response.url+"#"+li('a.product-sku').text())
def on_message(self, project, msg):#這個(gè)方法是返回結(jié)果,當(dāng)上一個(gè)方法每進(jìn)行一次send_message就會(huì)調(diào)用這個(gè)方法全陨,就會(huì)返回相應(yīng)的結(jié)果爆班。
return msg