python爬蟲:基于gevent異步爬蟲的原理及實(shí)現(xiàn)

這張真的很好看啊

??當(dāng)你寫爬蟲寫了一段時(shí)間嗜愈,你開(kāi)始覺(jué)得這個(gè)爬蟲怎么那么慢旧蛾,明明代碼優(yōu)美沒(méi)有bug。所以你不會(huì)去想方設(shè)法降低你爬蟲的時(shí)間復(fù)雜度或者空間復(fù)雜度蠕嫁,你清楚的知道機(jī)器的大部分時(shí)間花在了網(wǎng)絡(luò)IO上锨天。想提速怎么辦?
??加錢買帶寬買機(jī)器疤甓尽病袄!好的本文結(jié)束,大家散了散了赘阀。
??哎哎哎益缠,你們刀放下我好好說(shuō)話。
??看標(biāo)題猜到基公,本文爬蟲提速方式是用異步機(jī)制幅慌。先看看這個(gè)與你的同步爬蟲有什么差別?你需要先了解兩(四)個(gè)概念:

  • 同步和異步:關(guān)注的是消息通信機(jī)制 (synchronous communication/ asynchronous communication)
    • 同步轰豆,就是在發(fā)出一個(gè)調(diào)用時(shí)胰伍,在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回秒咨。調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果喇辽。
    • 異步掌挚,調(diào)用在發(fā)出之后雨席,這個(gè)調(diào)用就直接返回了,所以沒(méi)有返回結(jié)果吠式。在調(diào)用發(fā)出后陡厘,被調(diào)用者通過(guò)狀態(tài)、通知來(lái)通知調(diào)用者特占,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用糙置。
  • 阻塞和非阻塞:關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài)是目。
    • 阻塞調(diào)用:指調(diào)用結(jié)果返回之前谤饭,當(dāng)前線程會(huì)被掛起。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回。
    • 非阻塞調(diào)用:指在不能得到結(jié)果時(shí)揉抵,該調(diào)用不會(huì)阻塞當(dāng)前線程亡容。

??你一突然一拍腦袋,完蛋怎么跟線程有關(guān)系冤今,不是說(shuō)python有GIL闺兢,多線程都是假的。
??對(duì)啊對(duì)啊戏罢,快來(lái)學(xué)golang吧屋谭。哎哎哎?怎么又是你龟糕,把刀放下好好說(shuō)話桐磁。
??python因?yàn)镚IL并不能做到并行,但可以做到并發(fā)翩蘸。對(duì)于計(jì)算密集型應(yīng)用所意,python的多線程確實(shí)沒(méi)啥用。但對(duì)于向網(wǎng)頁(yè)提交多個(gè)request這種IO密集型應(yīng)用催首,并發(fā)就很有用了扶踊。嗯...說(shuō)你的爬蟲不是cpu密集型,是IO密集型你沒(méi)什么意見(jiàn)吧郎任。
??簡(jiǎn)單說(shuō)三個(gè)大家應(yīng)該多多少少了解的概念(為不影響閱讀秧耗,詳細(xì)概念我會(huì)放在本文最后附錄部分)。

  • 進(jìn)程:擁有自己獨(dú)立的堆和棧舶治,既不共享堆分井,亦不共享?xiàng)#?strong>進(jìn)程由操作系統(tǒng)調(diào)度
  • 線程:擁有自己獨(dú)立的棧和共享的堆,共享堆霉猛,不共享?xiàng)#?strong>線程亦由操作系統(tǒng)調(diào)度(標(biāo)準(zhǔn)線程是的)
  • 協(xié)程:和線程一樣共享堆章姓,不共享?xiàng)#瑓f(xié)程由程序員在協(xié)程的代碼里顯式調(diào)度

??別急辙谜,馬上引出gevent山孔,基礎(chǔ)知識(shí)還是要講講的。之前說(shuō)python的多線程其實(shí)是串行坛悉,但是的確可以提高IO密集型應(yīng)用的速度伐厌,為什么這里不用多線程而要基于gevent(協(xié)程)?

  • 傳統(tǒng)的生產(chǎn)者-消費(fèi)者模型是一個(gè)線程寫消息裸影,一個(gè)線程取消息挣轨,通過(guò)鎖機(jī)制控制隊(duì)列和等待,但容易死鎖轩猩。
  • 如果改用協(xié)程卷扮,生產(chǎn)者生產(chǎn)消息后荡澎,直接通過(guò)yield跳轉(zhuǎn)到消費(fèi)者開(kāi)始執(zhí)行,待消費(fèi)者執(zhí)行完畢后晤锹,切換回生產(chǎn)者繼續(xù)生產(chǎn)衔瓮,效率極高
    ??來(lái)來(lái)來(lái)抖甘,請(qǐng)gevent登場(chǎng):

Gevent安裝:

??直接輸入pip install gevent

Gevent核心部分:

??gevent中的主要模式, 它是以C擴(kuò)展模塊形式接入Python的輕量級(jí)協(xié)程热鞍。 全部運(yùn)行在主程序操作系統(tǒng)進(jìn)程的內(nèi)部,但它們被程序員協(xié)作式地調(diào)度

  • Greenlets:請(qǐng)注意基于Greenlets衔彻,先有Greenlets后有Gevent薇宠。greenlet你稍微了解這些要點(diǎn):
    • 每一個(gè)greenlet.greenlet實(shí)例都有一個(gè)parent(可指定,默認(rèn)為創(chuàng)生新的greenlet.greenlet所在環(huán)境)艰额,當(dāng)greenlet.greenlet實(shí)例執(zhí)行完邏輯正常結(jié)束澄港、或者拋出異常結(jié)束時(shí),執(zhí)行邏輯切回到其parent
    • 可以繼承g(shù)reenlet.greenlet柄沮,子類需要實(shí)現(xiàn)run方法回梧,當(dāng)調(diào)用greenlet.switch方法時(shí)會(huì)調(diào)用到這個(gè)run方法
  • 確定性:greenlet具有確定性。在相同配置相同輸入的情況下祖搓,它們總是會(huì)產(chǎn)生相同的輸出狱意。你爬蟲就不要想了,網(wǎng)絡(luò)響應(yīng)時(shí)間每次都不一樣拯欧,但這個(gè)特性你需要了解详囤。
  • 程序停止:當(dāng)主程序(main program)收到一個(gè)SIGQUIT信號(hào)時(shí),調(diào)用gevent.shutdown可以退出程序镐作。
  • 超時(shí):通過(guò)超時(shí)可以對(duì)代碼塊兒或一個(gè)Greenlet的運(yùn)行時(shí)間進(jìn)行約束藏姐。
  • 猴子補(bǔ)丁:先了解gevent.monkey.patch_all()

??先看代碼吧,結(jié)合代碼說(shuō):

import gevent
import greenlet
def callback(event, args):
    print event, args[0], '===:>>>>', args[1]

# 想象成你的爬蟲1
def foo():
    print('Running in foo')
    # 這個(gè)時(shí)候做了網(wǎng)絡(luò)IO
    gevent.sleep(0)
    print('Explicit context switch to foo again')

# 想象成你的爬蟲2
def bar():
    print('Explicit context to bar')
    # 這個(gè)時(shí)候做了網(wǎng)絡(luò)IO
    gevent.sleep(0)
    print('Implicit context switch back to bar')

print 'main greenlet info: ', greenlet.greenlet.getcurrent()
print 'hub info', gevent.get_hub()
oldtrace = greenlet.settrace(callback)
        
gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])
greenlet.settrace(oldtrace)

??你可以直接代碼拷過(guò)去運(yùn)行一下该贾,你可以看到gevent的調(diào)度方式羔杨。我將其轉(zhuǎn)換成圖片方便大家閱讀理解。你會(huì)發(fā)現(xiàn)多了個(gè)hub杨蛋,每次從hub切換到一個(gè)greenlet后兜材,都會(huì)回到hub,然而這就是gevent的關(guān)鍵六荒。

Gevent中調(diào)度方式

??采用這種模式個(gè)人理解是:

  • hub是事件驅(qū)動(dòng)的核心护姆,每次切換到hub后將繼續(xù)循環(huán)事件矾端。如果在一個(gè)greenlet中不出來(lái)掏击,那么其它greenlet將得不到調(diào)用
  • 維持兩者關(guān)系肯定比維持多個(gè)關(guān)系簡(jiǎn)單秩铆。所以每次關(guān)心的就是hub以及當(dāng)前greenlet砚亭,不需要全局考慮各個(gè)greenlet之間關(guān)系灯变。

涉及數(shù)據(jù)結(jié)構(gòu):

??嗯...有興趣深入了解的看官方文檔吧?這里主要講爬蟲捅膘,爬蟲用的到的地方給了解釋添祸。

  • 事件
  • 隊(duì)列
  • 組和池:寫爬蟲的話最少需要掌握池。
    • 池(pool)是一個(gè)為處理數(shù)量變化并且需要限制并發(fā)的greenlet而設(shè)計(jì)的結(jié)構(gòu)寻仗。
  • 鎖和信號(hào)量
  • 線程局部變量
  • 子進(jìn)程
  • Actors

??實(shí)際應(yīng)用到你的爬蟲中:
??實(shí)在抱歉啊刃泌,我盡可能的少說(shuō)概念了,可是直接上代碼就跟網(wǎng)上其他我看的教程一樣云里霧里署尤,我覺(jué)得這樣不是很好耙替,好了快看代碼吧。

import gevent
from gevent import Greenlet
from gevent import monkey
import gevent.pool
# 在進(jìn)行IO操作時(shí)曹体,默認(rèn)切換協(xié)程
monkey.patch_all()

# 假設(shè)我在這里調(diào)用了你的爬蟲類接口
def run_Spider(url):
    # do anything what u want
    pass
    
if __name__ == '__main__':
    # 假如你的url寫在文件中 用第一個(gè)參數(shù)傳進(jìn)來(lái)
    import sys
    # 限制并發(fā)數(shù)20
    pool = gevent.pool.Pool(20)
    # 這里也可以用pool.map,我這么寫比較無(wú)腦
    threads = []
    with open(sys.argv[1], "r") as f:
        for line in f:
            threads.append(pool.spawn(run_Spider,line.strip()))
    gevent.joinall(threads)
    print "finish"

??這樣就實(shí)現(xiàn)一個(gè)基本異步爬蟲俗扇,更加復(fù)雜的異步也逃不過(guò)這些基礎(chǔ)的東西。如果說(shuō)的不到位箕别,大家指正啊沒(méi)事铜幽,評(píng)論私信都行,不想寫那么多概念的串稀,可是好像不寫不行除抛,會(huì)更加云里霧里。


附錄:

進(jìn)程

  • 不共享任何狀態(tài)
  • 調(diào)度由操作系統(tǒng)完成
  • 有獨(dú)立的內(nèi)存空間(上下文切換的時(shí)候需要保存棧母截、cpu寄存器镶殷、虛擬內(nèi)存、以及打開(kāi)的相關(guān)句柄等信息微酬,開(kāi)銷大)
  • 通訊主要通過(guò)信號(hào)傳遞的方式來(lái)實(shí)現(xiàn)(實(shí)現(xiàn)方式有多種绘趋,信號(hào)量、管道颗管、事件等陷遮,通訊都需要過(guò)內(nèi)核,效率低)

線程

  • 共享變量(解決了通訊麻煩的問(wèn)題垦江,但是對(duì)于變量的訪問(wèn)需要加鎖)
  • 調(diào)度由操作系統(tǒng)完成
  • 一個(gè)進(jìn)程可以有多個(gè)線程帽馋,每個(gè)線程會(huì)共享父進(jìn)程的資源(創(chuàng)建線程開(kāi)銷占用比進(jìn)程小很多,可創(chuàng)建的數(shù)量也會(huì)很多)
  • 通訊除了可使用進(jìn)程間通訊的方式比吭,還可以通過(guò)共享內(nèi)存的方式進(jìn)行通信(通過(guò)共享內(nèi)存通信比通過(guò)內(nèi)核要快很多)
  • 線程的使用會(huì)給系統(tǒng)帶來(lái)上下文切換的額外負(fù)擔(dān)绽族。

協(xié)程

  • 調(diào)度完全由用戶控制
  • 一個(gè)線程(進(jìn)程)可以有多個(gè)協(xié)程
  • 每個(gè)線程(進(jìn)程)循環(huán)按照指定的任務(wù)清單順序完成不同的任務(wù)(當(dāng)任務(wù)被堵塞時(shí),執(zhí)行下一個(gè)任務(wù)衩藤;當(dāng)恢復(fù)時(shí)吧慢,再回來(lái)執(zhí)行這個(gè)任務(wù);任務(wù)間切換只需要保存任務(wù)的上下文赏表,沒(méi)有內(nèi)核的開(kāi)銷检诗,可以不加鎖的訪問(wèn)全局變量)
  • 協(xié)程需要保證是非堵塞的且沒(méi)有相互依賴
  • 協(xié)程基本上不能同步通訊匈仗,多采用異步的消息通訊,效率比較高
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逢慌,一起剝皮案震驚了整個(gè)濱河市悠轩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攻泼,老刑警劉巖火架,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異忙菠,居然都是意外死亡距潘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門只搁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)音比,“玉大人,你說(shuō)我怎么就攤上這事氢惋《呆妫” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵焰望,是天一觀的道長(zhǎng)骚亿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)熊赖,這世上最難降的妖魔是什么来屠? 我笑而不...
    開(kāi)封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮震鹉,結(jié)果婚禮上俱笛,老公的妹妹穿的比我還像新娘。我一直安慰自己传趾,他們只是感情好迎膜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著浆兰,像睡著了一般磕仅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上簸呈,一...
    開(kāi)封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天榕订,我揣著相機(jī)與錄音,去河邊找鬼蜕便。 笑死劫恒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玩裙。 我是一名探鬼主播兼贸,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吃溅!你這毒婦竟也來(lái)了溶诞?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤决侈,失蹤者是張志新(化名)和其女友劉穎螺垢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赖歌,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枉圃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了庐冯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孽亲。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖展父,靈堂內(nèi)的尸體忽然破棺而出返劲,到底是詐尸還是另有隱情,我是刑警寧澤栖茉,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布篮绿,位于F島的核電站,受9級(jí)特大地震影響吕漂,放射性物質(zhì)發(fā)生泄漏亲配。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一惶凝、第九天 我趴在偏房一處隱蔽的房頂上張望吼虎。 院中可真熱鬧,春花似錦苍鲜、人聲如沸鲸睛。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)官辈。三九已至,卻和暖如春遍坟,著一層夾襖步出監(jiān)牢的瞬間拳亿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工愿伴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肺魁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓隔节,卻偏偏與公主長(zhǎng)得像鹅经,于是被迫代替她去往敵國(guó)和親寂呛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 目錄 一、開(kāi)啟線程的兩種方式 在python中開(kāi)啟線程要導(dǎo)入threading偶洋,它與開(kāi)啟進(jìn)程所需要導(dǎo)入的模塊mul...
    CaiGuangyin閱讀 2,402評(píng)論 1 16
  • 前言 很多朋友對(duì)異步編程都處于“聽(tīng)說(shuō)很強(qiáng)大”的認(rèn)知狀態(tài)熟吏。鮮有在生產(chǎn)項(xiàng)目中使用它。而使用它的同學(xué)玄窝,則大多數(shù)都停留在知...
    星星在線閱讀 2,856評(píng)論 2 39
  • 前述 進(jìn)程 線程 協(xié)程 異步 并發(fā)編程(不是并行)目前有四種方式:多進(jìn)程分俯、多線程、協(xié)程和異步哆料。 多進(jìn)程編程在pyt...
    softlns閱讀 6,332評(píng)論 2 24
  • 年輕的女孩子想嫁給愛(ài)情缸剪,年長(zhǎng)點(diǎn)的就會(huì)想著嫁給金錢,成熟的女人知道东亦,應(yīng)當(dāng)嫁給人品與涵養(yǎng)杏节,深沉的女子明白,最該嫁給是溫...
    炙熱玫瑰閱讀 136評(píng)論 1 1
  • hi 豆苗: 我是小樹(shù)的隊(duì)友典阵,錢叔叔(怎么有點(diǎn)怪怪的自我介紹)我們有個(gè)超級(jí)酷的戰(zhàn)隊(duì)奋渔,叫做鐵公雞! 鐵公雞的圖形是這...
    黑土錢閱讀 347評(píng)論 4 7