協程是什麼
若是咱們想要爬取的是成千上萬條的數據,那麼就會遇到一個問題:
由於程序是一行一行依次執行的緣故,要等待好久,咱們才能拿到想要的數據
既然一個爬蟲爬取大量數據要爬好久,那咱們能不能讓多個爬蟲一塊兒爬取?
一我的幹不完的活兒,組個團隊一塊兒幹,活一下被幹完了
把三部電影都點擊下載。哪一部先下載好了,就先看哪一部,尚未下載完的電影持續保持下載狀態
在一個任務未完成時,就能夠執行其餘多個任務,彼此不受影響(在看第一部下載好的電影時,其餘電影繼續保持下載狀態,彼此之間不受影響),叫異步。
有異步的概念,也有同步的概念, 同步就是一個任務結束才能啓動下一個(類比你看完一部電影,才能去看下一部電影)。
顯然,異步執行任務會比同步更加節省時間,由於它能減小沒必要要的等待。若是你須要對時間作優化,異步是一個很值得考慮的方案。
若是咱們把同步與異步的概念遷移到網絡爬蟲的場景中,那咱們以前學的爬蟲方式都是同步的
爬蟲每發起一個請求,都要等服務器返回響應後,纔會執行下一步。而不少時候,因爲網絡不穩定,加上服務器自身也須要響應的時間,致使爬蟲會浪費大量時間在等待上。這也是爬取大量數據時,爬蟲的速度會比較慢的緣由
咱們能夠採起異步的爬蟲方式,讓多個爬蟲在執行任務時保持相對獨立,彼此不受干擾,這樣就能夠免去等待時間, 顯然這樣爬蟲的效率和速度都會提升。
計算機小知識
每臺計算機都靠着CPU(中央處理器)幹活。在過去,單核CPU的計算機在處理多任務時,會出現一個問題:每一個任務都要搶佔CPU,執行完了一個任務纔開啓下一個任務。CPU畢竟只有一個,這會讓計算機處理的效率很低
爲了解決這樣的問題,一種非搶佔式的異步技術被創造了出來,這種方式叫多協程
原理:一個任務在執行過程當中,若是遇到等待,就先去執行其餘的任務,當等待結束,再回來繼續以前的那個任務。
在計算機的世界,這種任務來回切換得很是快速,看上去就像多個任務在被同時執行同樣。
能夠在等電飯煲蒸飯的時候去炒菜。而不是等飯作好,再去炒菜。你仍是那個你,但工做時間就這樣被縮短了
多協程的用法 gevent庫
效果對比: 爬取8個網站(包括百度、新浪、搜狐、騰訊、網易、愛奇藝、天貓、鳳凰)
同步的爬蟲方式,是依次爬取網站,並等待服務器響應(狀態碼爲200表示正常響應)後,才爬取下一個網站。好比第一個先爬取了百度的網址,等服務器響應後,再去爬取新浪的網址,以此類推,直至所有爬取完畢。
# 導入requests和time
import requests
import time
# 記錄程序開始時間
start = time.time()
# 把8個網站封裝成列表
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/'
]
# 遍歷url_list
for url in url_list:
# 用requests.get()函數爬取網站
r = requests.get(url)
# 打印網址和抓取請求的狀態碼
print(url, r.status_code)
# 記錄程序結束時間
end = time.time()
# end-start是結束時間減去開始時間,就是最終所花時間。
# 最後,把時間打印出來。
print(end-start)
複製代碼
試試異步...
# 建議只要使用gevent記得把gevent相關的import語句放在全部其餘import語句的前邊
# 在最開頭的地方gevent.monkey.patch_all();
# 把標準庫中的thread/socket等給替換掉.
# 這樣咱們在後面使用socket的時候能夠跟日常同樣使用,無需修改任何代碼,可是它變成非阻塞的了
import gevent
from gevent import monkey
monkey.patch_all()
import requests
import 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/'
]
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)
複製代碼
咱們案例爬取的數據量還比較小,不能直接體現出更大的速度差別。若是爬的是大量的數據,運用多協程會有更顯著的速度優點
把爬取
8
個網站變成爬取80
個網站,用同步的爬取方式大概須要花17.3
秒,但用多協程異步爬取只需大概4.5秒,整個爬取效率提高了**280%+
**。
安裝 gevent ==>
pip install gevent
以前的代碼, 加上註釋...
# 導入gevent、time、requests。
import requests
import time
import gevent
# 從gevent庫裏導入monkey模塊。
from gevent import monkey
# monkey.patch_all()能把程序變成協做式運行,就是能夠幫助程序實現異步。
monkey.patch_all()
# 記錄程序開始時間。
start = time.time()
# 把8個網站封裝成列表。
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/'
]
# 定義一個crawler()函數。
def crawler(url):
# 用requests.get()函數爬取網站。
r = requests.get(url)
# 打印網址、請求運行時間、狀態碼。
print(url, time.time()-start, r.status_code)
# 建立空的任務列表。
tasks_list = []
# 遍歷url_list。
for url in url_list:
# 用gevent.spawn()函數建立任務。
task = gevent.spawn(crawler, url)
# 往任務列表添加任務。
tasks_list.append(task)
# 執行任務列表裏的全部任務,就是讓爬蟲開始爬取網站。
gevent.joinall(tasks_list)
# 記錄程序結束時間。
end = time.time()
# 打印程序最終所需時間。
print(end-start)
複製代碼
gevent.spawn()的參數需爲要調用的函數名及該函數的參數。好比,gevent.spawn(crawler,url)就是建立一個執行crawler函數的任務,參數爲crawler函數名和它自身的參數url。
調用gevent庫裏的joinall方法,能啓動執行全部的任務。gevent.joinall(tasks_list)就是執行tasks_list這個任務列表裏的全部任務,開始爬取。
那若是咱們要爬的不是8個網站,而是1000個網站,咱們能夠怎麼作?
咱們能夠用gevent.spawn()建立1000個爬取任務,再用gevent.joinall()執行這1000個任務。
執行1000個任務,就是一會兒發起1000次請求,這樣子的惡意請求,會拖垮網站的服務器
既然這種建立1000個任務的方式不可取,咱們能不能只建立成5個任務,每一個任務爬取200個網站?
這5個任務之間是異步執行的,可是每一個任務(爬取200個網站)內部是同步的
隊列(銀行叫號) queue模塊
當咱們用多協程來爬蟲,須要建立大量任務時,咱們能夠藉助queue模塊。
queue翻譯成中文是隊列的意思。咱們能夠用queue模塊來存儲任務,讓任務都變成一條整齊的隊列,就像銀行窗口的排號作法。由於queue實際上是一種有序的數據結構,能夠用來存取數據。
這樣,協程就能夠從隊列裏把任務提取出來執行,直到隊列空了,任務也就處理完了。就像銀行窗口的工做人員會根據排號系統裏的排號,處理客人的業務,若是已經沒有新的排號,就意味着客戶的業務都已辦理完畢。
# 從gevent庫裏導入monkey模塊。
import requests
import time
import gevent
from gevent.queue import Queue
from gevent import monkey
# monkey.patch_all()能把程序變成協做式運行,就是能夠幫助程序實現異步。
monkey.patch_all()
# 導入gevent、time、requests
# 從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。
work = Queue()
# 遍歷url_list
for url in url_list:
# 用put_nowait()函數能夠把網址都放進隊列裏。
work.put_nowait(url)
def crawler():
# 當隊列不是空的時候,就執行下面的程序。
while not work.empty():
# 用get_nowait()函數能夠把隊列裏的網址都取出。
url = work.get_nowait()
# 用requests.get()函數抓取網址。
r = requests.get(url)
# 打印網址、隊列長度、抓取請求的狀態碼。
print(url, work.qsize(), r.status_code)
#建立空的任務列表
tasks_list = []
#至關於建立了8個爬蟲
for x in range(8):
#用gevent.spawn()函數建立執行crawler()函數的任務。
task = gevent.spawn(crawler)
#往任務列表添加任務。
tasks_list.append(task)
#用gevent.joinall方法,執行任務列表裏的全部任務,就是讓爬蟲開始爬取網站。
gevent.joinall(tasks_list)
end = time.time()
print(end-start)
複製代碼
並行和併發
後來,咱們的CPU從單核終於進化到了多核,每一個核都可以獨立運做。計算機開始可以真正意義上同時執行多個任務(術語叫並行執行),而不是在多個任務之間來回切換(術語叫併發執行)
咱們電腦通常都會是多核CPU。多協程,其實只佔用了CPU的一個核運行,沒有充分利用到其餘核。利用CPU的多個核同時執行任務的技術,咱們把它叫作「多進程」。
真正大型的爬蟲程序不會單單隻靠多協程來提高爬取速度的。好比,百度搜索引擎,能夠說是超大型的爬蟲程序,它除了靠多協程,必定還會靠多進程,甚至是分佈式爬蟲。
雖然爬蟲是異步加多線程的,可是咱們只能在一臺主機上運行,因此爬取效率仍是有限的,分佈式爬蟲則是將多臺主機組合起來,共同完成一個爬取任務,這將大大提升爬取的效率
總結
同步和異步
多協程,是一種非搶佔式的異步方式。使用多協程,就能讓多個爬取任務用異步的方式交替執行。
貓哥教你寫爬蟲 000--開篇.md
貓哥教你寫爬蟲 001--print()函數和變量.md
貓哥教你寫爬蟲 002--做業-打印皮卡丘.md
貓哥教你寫爬蟲 003--數據類型轉換.md
貓哥教你寫爬蟲 004--數據類型轉換-小練習.md
貓哥教你寫爬蟲 005--數據類型轉換-小做業.md
貓哥教你寫爬蟲 006--條件判斷和條件嵌套.md
貓哥教你寫爬蟲 007--條件判斷和條件嵌套-小做業.md
貓哥教你寫爬蟲 008--input()函數.md
貓哥教你寫爬蟲 009--input()函數-人工智能小愛同窗.md
貓哥教你寫爬蟲 010--列表,字典,循環.md
貓哥教你寫爬蟲 011--列表,字典,循環-小做業.md
貓哥教你寫爬蟲 012--布爾值和四種語句.md
貓哥教你寫爬蟲 013--布爾值和四種語句-小做業.md
貓哥教你寫爬蟲 014--pk小遊戲.md
貓哥教你寫爬蟲 015--pk小遊戲(全新改版).md
貓哥教你寫爬蟲 016--函數.md
貓哥教你寫爬蟲 017--函數-小做業.md
貓哥教你寫爬蟲 018--debug.md
貓哥教你寫爬蟲 019--debug-做業.md
貓哥教你寫爬蟲 020--類與對象(上).md
貓哥教你寫爬蟲 021--類與對象(上)-做業.md
貓哥教你寫爬蟲 022--類與對象(下).md
貓哥教你寫爬蟲 023--類與對象(下)-做業.md
貓哥教你寫爬蟲 024--編碼&&解碼.md
貓哥教你寫爬蟲 025--編碼&&解碼-小做業.md
貓哥教你寫爬蟲 026--模塊.md
貓哥教你寫爬蟲 027--模塊介紹.md
貓哥教你寫爬蟲 028--模塊介紹-小做業-廣告牌.md
貓哥教你寫爬蟲 029--爬蟲初探-requests.md
貓哥教你寫爬蟲 030--爬蟲初探-requests-做業.md
貓哥教你寫爬蟲 031--爬蟲基礎-html.md
貓哥教你寫爬蟲 032--爬蟲初體驗-BeautifulSoup.md
貓哥教你寫爬蟲 033--爬蟲初體驗-BeautifulSoup-做業.md
貓哥教你寫爬蟲 034--爬蟲-BeautifulSoup實踐.md
貓哥教你寫爬蟲 035--爬蟲-BeautifulSoup實踐-做業-電影top250.md
貓哥教你寫爬蟲 036--爬蟲-BeautifulSoup實踐-做業-電影top250-做業解析.md
貓哥教你寫爬蟲 037--爬蟲-寶寶要聽歌.md
貓哥教你寫爬蟲 038--帶參數請求.md
貓哥教你寫爬蟲 039--存儲數據.md
貓哥教你寫爬蟲 040--存儲數據-做業.md
貓哥教你寫爬蟲 041--模擬登陸-cookie.md
貓哥教你寫爬蟲 042--session的用法.md
貓哥教你寫爬蟲 043--模擬瀏覽器.md
貓哥教你寫爬蟲 044--模擬瀏覽器-做業.md
貓哥教你寫爬蟲 045--協程.md
貓哥教你寫爬蟲 046--協程-實踐-吃什麼不會胖.md
貓哥教你寫爬蟲 047--scrapy框架.md
貓哥教你寫爬蟲 048--爬蟲和反爬蟲.md
貓哥教你寫爬蟲 049--完結撒花.mdhtml