亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

python爬蟲第11關 協程

系統 1545 0

協程是什么

我們已經做過不少爬蟲項目,不過我們爬取的數據都不算太大,如果我們想要爬取的是成千上萬條的數據,那么就會遇到一個問題:因為程序是一行一行依次執行的緣故,要等待很久,我們才能拿到想要的數據。

既然一個爬蟲爬取大量數據要爬很久,那我們能不能讓多個爬蟲一起爬???

這樣無疑能提高爬取的效率,就像一個人干不完的活兒,組個團隊一起干,活一下被干完了。

這是一個很好的思路——讓多個爬蟲幫我們干活。但具體怎么用Python實現這事呢?

我們可以先別急著想怎么實現這件事,后面我會跟你說。

現在,你先跟我想象一個情景:
python爬蟲第11關 協程_第1張圖片
相信你肯定會這么做:把三部電影都點擊下載??茨囊徊肯认螺d好了,就先看哪一部,讓還沒有下載完的電影持續保持下載狀態。

如果用計算機里的概念來解釋這件事的話:在一個任務未完成時,就可以執行其他多個任務,彼此不受影響(在看第一部下載好的電影時,其他電影繼續保持下載狀態,彼此之間不受影響),叫異步。

有異步的概念,那應該也有同步的概念吧?是的,同步就是一個任務結束才能啟動下一個(類比你看完一部電影,才能去看下一部電影)。
顯然,異步執行任務會比同步更加節省時間,因為它能減少不必要的等待。如果你需要對時間做優化,異步是一個很值得考慮的方案。
如果我們把同步與異步的概念遷移到網絡爬蟲的場景中,那我們之前學的爬蟲方式都是同步的。

爬蟲每發起一個請求,都要等服務器返回響應后,才會執行下一步。而很多時候,由于網絡不穩定,加上服務器自身也需要響應的時間,導致爬蟲會浪費大量時間在等待上。這也是爬取大量數據時,爬蟲的速度會比較慢的原因。
怎樣才能實現異步的爬蟲方式,提高爬蟲的效率呢?要回答這個問題的話,得了解一點點計算機的歷史小知識。

我們知道每臺計算機都靠著CPU(中央處理器)干活。在過去,單核CPU的計算機在處理多任務時,會出現一個問題:每個任務都要搶占CPU,執行完了一個任務才開啟下一個任務。CPU畢竟只有一個,這會讓計算機處理的效率很低。

python爬蟲第11關 協程_第2張圖片
為了解決這樣的問題,一種非搶占式的異步技術被創造了出來,這種方式叫多協程(在此,多是多個的意思)。

它的原理是:一個任務在執行過程中,如果遇到等待,就先去執行其他的任務,當等待結束,再回來繼續之前的那個任務。在計算機的世界,這種任務來回切換得非??焖?,看上去就像多個任務在被同時執行一樣。

這就好比當你要做一桌飯菜,你可以在等電飯煲蒸飯的時候去炒菜。而不是等飯做好,再去炒菜。你還是那個你,但工作時間就這樣被縮短了。多協程能夠縮短工作時間的原理,也是如此。

所以,要實現異步的爬蟲方式的話,需要用到多協程。在它的幫助下,我們能實現前面提到的“讓多個爬蟲替我們干活”。
那么,新的問題來了——怎么使用多協程?

多協程的用法

gevent庫
python爬蟲第11關 協程_第3張圖片
所以,接下來我會帶你了解gevent的用法,和實操一個多協程案例:爬取8個網站(包括百度、新浪、搜狐、騰訊、網易、愛奇藝、天貓、鳳凰)。

我們先用之前同步的爬蟲方式爬取這8個網站,然后等下再和gevent異步爬取做一個對比。

            
              import requests,time
#導入requests和time
start = time.time()
#記錄程序開始時間

url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']
#把8個網站封裝成列表

for url in url_list:
#遍歷url_list
    r = requests.get(url)
    #用requests.get()函數爬取網站
    print(url,r.status_code)
    #打印網址和抓取請求的狀態碼

end = time.time()
#記錄程序結束時間
print(end-start)
#end-start是結束時間減去開始時間,就是最終所花時間。
#最后,把時間打印出來。



            
          

python爬蟲第11關 協程_第4張圖片
程序運行后,你會看到同步的爬蟲方式,是依次爬取網站,并等待服務器響應(狀態碼為200表示正常響應)后,才爬取下一個網站。比如第一個先爬取了百度的網址,等服務器響應后,再去爬取新浪的網址,以此類推,直至全部爬取完畢。

為了讓你能更直觀地看到爬蟲完成任務所需的時間,我導入了time模塊,記錄了程序開始和結束的時間,最后打印出來的就是爬蟲爬取這8個網站所花費的時間。
如果我們用了多協程來爬取會有什么不同?

            
              from gevent import monkey
monkey.patch_all()
import gevent,time,requests

start = time.time()

url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']

def crawler(url):
    r = requests.get(url)
    print(url,time.time()-start,r.status_code)

tasks_list = []

for url in url_list:
    task = gevent.spawn(crawler,url)
    tasks_list.append(task)
gevent.joinall(tasks_list)
end = time.time()
print(end-start)



            
          

python爬蟲第11關 協程_第5張圖片
通過每個請求運行的時間,我們能知道:爬蟲用了異步的方式抓取了8個網站,因為每個請求完成的時間并不是按著順序來的。比如在我測試運行這個代碼的時候,最先爬取到的網站是搜狐,接著是鳳凰,并不是百度和新浪。

且每個請求完成時間之間的間隔都非常短,你可以看作這些請求幾乎是“同時”發起的。

通過對比同步和異步爬取最終所花的時間,用多協程異步的爬取方式,確實比同步的爬蟲方式速度更快。
其實,我們案例爬取的數據量還比較小,不能直接體現出更大的速度差異。如果爬的是大量的數據,運用多協程會有更顯著的速度優勢。

比如我做了一個測試:把爬取8個網站變成爬取80個網站,用同步的爬取方式大概需要花17.3秒,但用多協程異步爬取只需大概4.5秒,整個爬取效率提升了280%+。
python爬蟲第11關 協程_第6張圖片
現在,我們一行行來看剛剛用了gevent的代碼。

提醒:導入gevent庫前,得先安裝它。(安裝方法:window電腦:在終端輸入命令:pip install gevent,按下enter鍵;mac電腦:在終端輸入命令:pip3 install gevent,按下enter鍵)
再來看一遍代碼:

            
              from gevent import monkey
#從gevent庫里導入monkey模塊。
monkey.patch_all()
#monkey.patch_all()能把程序變成協作式運行,就是可以幫助程序實現異步。
import gevent,time,requests
#導入gevent、time、requests。

start = time.time()
#記錄程序開始時間。

url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']
#把8個網站封裝成列表。

def crawler(url):
#定義一個crawler()函數。
    r = requests.get(url)
    #用requests.get()函數爬取網站。
    print(url,time.time()-start,r.status_code)
    #打印網址、請求運行時間、狀態碼。

tasks_list = [ ]
#創建空的任務列表。

for url in url_list:
#遍歷url_list。
    task = gevent.spawn(crawler,url)
    #用gevent.spawn()函數創建任務。
    tasks_list.append(task)
    #往任務列表添加任務。
gevent.joinall(tasks_list)
#執行任務列表里的所有任務,就是讓爬蟲開始爬取網站。
end = time.time()
#記錄程序結束時間。
print(end-start)
#打印程序最終所需時間。



            
          

第1、3行代碼:從gevent庫里導入了monkey模塊,這個模塊能將程序轉換成可異步的程序。monkey.patch_all(),它的作用其實就像你的電腦有時會彈出“是否要用補丁修補漏洞或更新”一樣。它能給程序打上補丁,讓程序變成是異步模式,而不是同步模式。它也叫“猴子補丁”。
我們要在導入其他庫和模塊前,先把monkey模塊導入進來,并運行monkey.patch_all()。這樣,才能先給程序打上補丁。你也可以理解成這是一個規范的寫法。

第5行代碼:我們導入了gevent庫來幫我們實現多協程,導入了time模塊來幫我們記錄爬取所需時間,導入了requests模塊幫我們實現爬取8個網站。
python爬蟲第11關 協程_第7張圖片
第21、23、25行代碼:我們定義了一個crawler函數,只要調用這個函數,它就會執行【用requests.get()爬取網站】和【打印網址、請求運行時間、狀態碼】這兩個任務。
python爬蟲第11關 協程_第8張圖片
第33行代碼:因為gevent只能處理gevent的任務對象,不能直接調用普通函數,所以需要借助gevent.spawn()來創建任務對象。
這里需要注意一點:gevent.spawn()的參數需為要調用的函數名及該函數的參數。比如,gevent.spawn(crawler,url)就是創建一個執行crawler函數的任務,參數為crawler函數名和它自身的參數url。
python爬蟲第11關 協程_第9張圖片
第35行代碼:用append函數把任務添加到tasks_list的任務列表里。
第37行代碼:調用gevent庫里的joinall方法,能啟動執行所有的任務。gevent.joinall(tasks_list)就是執行tasks_list這個任務列表里的所有任務,開始爬取。
python爬蟲第11關 協程_第10張圖片
總結一下用gevent實現多協程爬取的重點:
python爬蟲第11關 協程_第11張圖片
到這里,用gevent實操抓取8個網站我們已經完成,gevent的基礎語法我們也大致了解。

那如果我們要爬的不是8個網站,而是1000個網站,我們可以怎么做?

用我們剛剛學的gevent語法,我們可以用gevent.spawn()創建1000個爬取任務,再用gevent.joinall()執行這1000個任務。

但這種方法會有問題:執行1000個任務,就是一下子發起1000次請求,這樣子的惡意請求,會拖垮網站的服務器。
python爬蟲第11關 協程_第12張圖片
既然這種直接創建1000個任務的方式不可取,那我們能不能只創建成5個任務,但每個任務爬取200個網站?

假設我們有1000個任務,那創建5個任務,每個任務爬取200個網站的代碼可以寫成如下的樣子(此代碼僅做展示,并不可運行):

            
              from gevent import monkey
monkey.patch_all()
import gevent,time,requests

start = time.time()
url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/'
……
#假設有1000個網址
]

def crawler(url_list):
#定義一個crawler()函數。
    for url in url_list:
        r = requests.get(url)
        print(url,time.time()-start,r.status_code)

tasks_list = [ ]
#創建空的任務列表。
for i in range(5):
    task = gevent.spawn(crawler,url_list[i*200:(i+1)*200])
    #用gevent.spawn()函數創建5個任務。
    tasks_list.append(task)
    #往任務列表添加任務。

gevent.joinall(tasks_list)
end = time.time()
print(end-start)



            
          

遺憾地告訴你,這么做也還是會有問題的。就算我們用gevent.spawn()創建了5個分別執行爬取200個網站的任務,這5個任務之間是異步執行的,但是每個任務(爬取200個網站)內部是同步的。
這意味著:如果有一個任務在執行的過程中,它要爬取的一個網站一直在等待響應,哪怕其他任務都完成了200個網站的爬取,它也還是不能完成200個網站的爬取。
python爬蟲第11關 協程_第13張圖片
這個方法也不行,那還有什么方法呢?

這時我們可以從實際生活的案例中得到啟發。想想銀行是怎么在一天內辦理1000個客戶的業務的。

銀行會開設辦理業務的多個窗口,讓客戶取號排隊,由銀行的叫號系統分配客戶到不同的窗口去辦理業務。

在gevent庫中,也有一個模塊可以實現這種功能——queue模塊。

queue模塊
當我們用多協程來爬蟲,需要創建大量任務時,我們可以借助queue模塊。

queue翻譯成中文是隊列的意思。我們可以用queue模塊來存儲任務,讓任務都變成一條整齊的隊列,就像銀行窗口的排號做法。因為queue其實是一種有序的數據結構,可以用來存取數據。

這樣,協程就可以從隊列里把任務提取出來執行,直到隊列空了,任務也就處理完了。就像銀行窗口的工作人員會根據排號系統里的排號,處理客人的業務,如果已經沒有新的排號,就意味著客戶的業務都已辦理完畢。
python爬蟲第11關 協程_第14張圖片
接下來,我們來實操看看,可以怎么用queue模塊和協程配合,依舊以抓取8個網站為例。

            
              from gevent import monkey
monkey.patch_all()
import gevent,time,requests
from gevent.queue import Queue

start = time.time()

url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']

work = Queue()
for url in url_list:
    work.put_nowait(url)

def crawler():
    while not work.empty():
        url = work.get_nowait()
        r = requests.get(url)
        print(url,work.qsize(),r.status_code)

tasks_list  = [ ]

for x in range(2):
    task = gevent.spawn(crawler)
    tasks_list.append(task)
gevent.joinall(tasks_list)

end = time.time()
print(end-start)



            
          

python爬蟲第11關 協程_第15張圖片
網址后面的數字指的是隊列里還剩的任務數,比如第一個網址后面的數字6,就是此時隊列里還剩6個抓取其他網址的任務。

現在,我們把剛剛運行的代碼拆成4部分來講解,第1部分是導入模塊。

            
              from gevent import monkey
#從gevent庫里導入monkey模塊。
monkey.patch_all()
#monkey.patch_all()能把程序變成協作式運行,就是可以幫助程序實現異步。
import gevent,time,requests
#導入gevent、time、requests
from gevent.queue import Queue
#從gevent庫里導入queue模塊


            
          

因為gevent庫里就帶有queue,所以我們用【from gevent.queue import Queue】就能把queue模塊導入。其他模塊和代碼我們在講解gevent時已經講解過了,相信你能懂。

第2部分,是如何創建隊列,以及怎么把任務存儲進隊列里。

            
              start = time.time()
#記錄程序開始時間

url_list = ['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/']

work = Queue()
#創建隊列對象,并賦值給work。
for url in url_list:
#遍歷url_list
    work.put_nowait(url)
    #用put_nowait()函數可以把網址都放進隊列里。



            
          

用Queue()能創建queue對象,相當于創建了一個不限任何存儲數量的空隊列。如果我們往Queue()中傳入參數,比如Queue(10),則表示這個隊列只能存儲10個任務。

創建了queue對象后,我們就能調用這個對象的put_nowait方法,把我們的每個網址都存儲進我們剛剛建立好的空隊列里。

work.put_nowait(url)這行代碼就是把遍歷的8個網站,都存儲進隊列里。

第3部分,是定義爬取函數,和如何從隊列里提取出剛剛存儲進去的網址。

            
              def crawler():
    while not work.empty():
    #當隊列不是空的時候,就執行下面的程序。
        url = work.get_nowait()
        #用get_nowait()函數可以把隊列里的網址都取出。
        r = requests.get(url)
        #用requests.get()函數抓取網址。
        print(url,work.qsize(),r.status_code)
        #打印網址、隊列長度、抓取請求的狀態碼。



            
          

這里定義的crawler函數,多了三個你可能看不懂的代碼:1.while not work.empty():;2.url = work.get_nowait();3.work.qsize()。

這三個代碼涉及到queue對象的三個方法:empty方法,是用來判斷隊列是不是空了的;get_nowait方法,是用來從隊列里提取數據的;qsize方法,是用來判斷隊列里還剩多少數量的。

當然,queue對象的方法還不止這幾種,比如有判斷隊列是否為空的empty方法,對應也有判斷隊列是否為滿的full方法。

你是不是覺得queue對象這么多方法,一下子記不?。科鋵?,這些不需要你死記硬背的,附上一張queue對象的方法表,你只需要在用到的時候,查查表就好。
python爬蟲第11關 協程_第16張圖片
代碼的前3部分,我們講解完了。如果你能明白隊列怎么創建、數據怎么存儲進隊列,以及怎么從隊列里提取出的數據,就說明queue模塊的重點內容你都掌握了。

python爬蟲第11關 協程_第17張圖片
接在第3部分代碼的后面,就是讓爬蟲用多協程執行任務,爬取隊列里的8個網站的代碼(重點看有注釋的代碼)。

            
              def crawler():
    while not work.empty():
        url = work.get_nowait()
        r = requests.get(url)
        print(url,work.qsize(),r.status_code)

tasks_list  = [ ]
#創建空的任務列表
for x in range(2):
#相當于創建了2個爬蟲
    task = gevent.spawn(crawler)
    #用gevent.spawn()函數創建執行crawler()函數的任務。
    tasks_list.append(task)
    #往任務列表添加任務。
gevent.joinall(tasks_list)
#用gevent.joinall方法,執行任務列表里的所有任務,就是讓爬蟲開始爬取網站。
end = time.time()
print(end-start)



            
          

python爬蟲第11關 協程_第18張圖片
我們創建了兩只可以異步爬取的爬蟲。它們會從隊列里取走網址,執行爬取任務。一旦一個網址被一只爬蟲取走,另一只爬蟲就取不到了,另一只爬蟲就會取走下一個網址。直至所有網址都被取走,隊列為空時,爬蟲就停止工作。

用協程技術和隊列爬取8個網站的完整代碼如下:

            
              from gevent import monkey#gevent從庫里導入monkey模塊
monkey.patch_all()#能把程序變成協作式運行,就是可以幫助程序實現異步
import gevent,time,requests
from gevent.queue import Queue#gevent從庫里導入queue模塊

start=time.time()

url_list=['https://www.baidu.com/',
'https://www.sina.com.cn/',
'http://www.sohu.com/',
'https://www.qq.com/',
'https://www.163.com/',
'http://www.iqiyi.com/',
'https://www.tmall.com/',
'http://www.ifeng.com/'
]

work=Queue()#創建隊列對象,并賦值給work

for url in url_list:
    work.put_nowait(url)#用put_nowait()函數可以把網址都放進隊列里。

def crawler():
    while not work.empty():#當隊列不是空的時候,就執行下面的程序
        url=work.get_nowait()#get_nowait()函數可以把隊列里的網址都取出。
        res=requests.get(url)#用requests.get()函數抓取網址。
        print(url,work.qsize(),res.status_code)#打印網址、隊列長度、抓取請求的狀態碼

tasks_list=[]#創建空的任務列表

for x in range(2):#相當于創建了兩個爬蟲
    task=gevent.spawn(crawler)#用gevent.spawn()函數創建執行crawler()函數的任務
    tasks_list.append(task)

gevent.joinall(tasks_list)#用gevent.joinall方法,執行任務列表里的所有任務,就是讓爬蟲開始爬取網站。

end=time.time()
print(end-start)



            
          

動手總會有收獲的。恭喜你,這一關的核心知識,實現多協程的gevent庫和Queue模塊,你都學完了!

拓展復習

不過,我還想和你拓展一點新的知識。

同樣是要做飯菜,我們已經知道比先做飯再做菜更好的方式是,等待做飯的過程中去做菜。但其實還有更快的方案:讓一個人負責做飯,一個人負責做菜。

繼續說我們的計算機歷史小知識:在后來,我們的CPU從單核終于進化到了多核,每個核都能夠獨立運作。計算機開始能夠真正意義上同時執行多個任務(術語叫并行執行),而不是在多個任務之間來回切換(術語叫并發執行)。

比如你現在打開瀏覽器看著爬蟲課程的同時,可以打開音樂播放器聽歌,還可以打開Excel。對于多核CPU而言,這些任務就都是同時運行的。

時至今日,我們電腦一般都會是多核CPU。多協程,其實只占用了CPU的一個核運行,沒有充分利用到其他核。利用CPU的多個核同時執行任務的技術,我們把它叫做“多進程”。

所以,真正大型的爬蟲程序不會單單只靠多協程來提升爬取速度的。比如,百度搜索引擎,可以說是超大型的爬蟲程序,它除了靠多協程,一定還會靠多進程,甚至是分布式爬蟲。

多進程爬蟲和分布式爬蟲不屬于我們這個課程的知識范疇,我這里不會多講?;蛟S接下來我們會開設爬蟲進階課程,到時再和大家分享。

最后,是這一關的復習。
同步與異步——
python爬蟲第11關 協程_第19張圖片
多協程,是一種非搶占式的異步方式。使用多協程的話,就能讓多個爬取任務用異步的方式交替執行。
python爬蟲第11關 協程_第20張圖片
python爬蟲第11關 協程_第21張圖片
python爬蟲第11關 協程_第22張圖片
python爬蟲第11關 協程_第23張圖片
python爬蟲第11關 協程_第24張圖片


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 久久精品久久精品国产大片 | 国产精品免费一级在线观看 | 亚洲精品区 | 手机看片国产免费久久网 | 九九免费视频 | 香蕉在线精品一区二区 | 黄色在线观看免费 | 色综合久久久久综合99 | 国产三级久久 | 久久这里只精品热免费99 | 国产精品福利影院 | 精品欧美一区二区三区在线观看 | 可以免费观看的一级片 | 欧美日韩国产成人综合在线 | 欧美一区二区三 | aaa色| 久久久久久久久久久96av | 日日碰日日摸日日澡视频播放 | 成人观看网站a | 日韩最新视频一区二区三 | 成人短视频在线观看 | 日韩成| 91精品国产综合久久欧美 | 九色网址| 四虎精品影院在线观看视频 | 免费一级大片儿 | 99热这里只有精品6免费 | 国产精品国产自线在线观看 | 国产一级二级在线观看 | 成人激情小视频 | 全部精品孕妇色视频在线 | 成人国产精品免费网站 | 亚洲精品中文字幕区 | 日本综合色 | 在线日韩欧美 | 亚洲美女性生活视频 | 99久久精品费精品国产 | 欧美国产高清 | 久久97精品久久久久久清纯 | 成人在线观看不卡 | 成人私人影院www片免费高清 |