開坑個新系列,主要面向新手,老司機能夠忽略。python
這個系列內的文章將會讓你知道如何作到讓你寫的爬蟲在運行的時候速度能像火箭同樣快!服務器
不少初學爬蟲的朋友對於這方面的知識彷佛是空白的,甚至還有一些在爬蟲崗位上工做了一兩年的人也搞不清楚在不使用爬蟲框架的狀況下,如何寫出一個速度足夠快的爬蟲,而網上的文章大可能是基於多進程/Gevent來寫的,代碼看起來就極其複雜,甚至有些人抄來抄去連多進程和多線程沒搞清楚,若是是一個想學習這方面知識的人看到了這樣的文章,多半會一臉懵逼。cookie
綜上所述,爲了讓關注我公衆號的新手朋友們能快速掌握這些技巧,這個系列就這樣誕生了~網絡
話很少說,咱們正式開始。在提高爬蟲的速度這方面,最基礎、最有效、最直接的操做是什麼呢?沒錯,就是併發請求,若是你的爬蟲整個邏輯是順序執行的,請求的時候永遠不會併發,那麼你就會遇到像他這樣的狀況:《小白寫了個壁紙的爬蟲,能跑起來,可是感受很慢,不知道怎麼回事,請大佬指點》。session
上面這是我昨天刷V2的時候看到的一個帖子,樓主的代碼內容簡單歸納一下就徹底是順序執行的,每下載一個圖片都須要等待當前這個圖片下載完了才能繼續下載下一個,這樣子作固然會很是慢了!這篇文章就拿他的代碼做爲樣例,在原來的基礎上進行一些調整,從而讓他寫的這個爬蟲的運行速度能從龜爬變成像坐火箭同樣快!多線程
首先,咱們須要知道什麼是併發,這裏的併發指的是「並行發送請求」,意思就是一次性發出多個請求,從而達到節省時間的效果!那麼併發和不併發的區別在哪呢?簡單來講就是這樣子的:併發
把爬蟲比喻成工人,在不併發的狀況下,一個工人一次只能作一件事情,因此必需要下載完一個圖片才能繼續下載下一個。框架
而在併發的狀況下,就有不少個工人一塊兒在幹活,每一個工人都被分配了一件事情作,因此能夠同時下載多個圖片,速度天然就快了不少。異步
固然,上面說的這個例子只是從一個宏觀的角度上來看併發,實際在作的時候要讓你的爬蟲能併發請求的方式是分爲多線程、多進程、協程三種的,**並非每一種方式在運行時的效果都像上面說的這樣,這裏先不作深刻探討,由於這不是本文的重點。**咱們如今只須要知道,只要能讓爬蟲併發請求,就能同時下載多個圖片,讓速度快得飛起,這樣就夠了。async
那麼咱們要用上面說的三種方式裏的哪種來實現併發請求呢?這還用問嗎?固然是選擇代碼最簡單、改動最小,而且最容易看懂的協程啊!在Python3.4以後Python就引入了一個叫作asyncio的庫,原生支持了異步IO,而在3.5以後Python又支持了async
和await
這兩個語法,使得寫異步代碼能夠像寫同步代碼同樣簡單易讀。
剛剛又提到了兩個詞,同步和異步,這兩個詞的含義其實就跟上面的併發差很少,同步代碼就是順序執行的,而異步則不是,這裏一樣不作深刻探討,先知道有這麼個東西就好了。
看到這裏確定會有人開始有疑問了,雖然前面說咱們要用協程來實現併發請求,可是後面說的倒是什麼Python支持原生異步,那麼這個異步跟協程的關係又是什麼呢?
其實很簡單,協程可讓你寫異步代碼的時候能像寫同步代碼同樣簡單,在Python3中寫協程代碼的核心語法就是async
和await
這兩個,舉個簡單的例子吧:
def func():
print(1)
time.sleep(10)
print(2)
複製代碼
這是一段普通的函數,它屬於同步代碼,裏面的time.sleep
是普通函數,也屬於同步代碼。
async def func(): # 調用協程函數的那個函數也須要是一個協程函數
print(1)
await asyncio.sleep(10) # 調用協程函數的時候要在前面加await
print(2)
複製代碼
而這是一個協程函數,它屬於異步代碼,裏面的asyncio.sleep
是協程函數,也屬於異步代碼。
它們的區別顯而易見,用協程來寫異步代碼,除了須要換成異步的庫之外,就只是多了個async
、await
而已,是否是很是簡單?
那麼咱們在瞭解了怎麼寫協程代碼以後,就能開始優化那段慢成龜速的代碼了嗎?答案是否認的,那段代碼中使用了requests庫進行網絡請求,而requests是一個同步庫,不能在異步環境下使用;一樣,文件操做用的open
和file.write
也是同步的,也不能在異步環境下使用。
因此在開始以前咱們還須要瞭解兩個庫,分別是aiohttp和aiofiles,aiohttp是一個異步網絡請求庫,而aiofiles是一個異步文件操做庫。(aiofiles是基於線程池實現的,並非真正的原生異步,但問題不大,不影響使用)
切記,異步代碼不能與同步代碼混用,不然若是同步代碼耗時過長,異步代碼就會被阻塞,失去異步的效果。而網絡請求和文件操做是整個流程中最耗時的部分,因此咱們必須使用異步的庫來進行操做!不然就白搞了!
好了,先來看看aiohttp的用法吧,官方文檔上的示例大體以下:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
result = await resp.text()
複製代碼
是否是以爲很麻煩,不像requests庫那麼方便?還以爲兩層async with
很醜?有沒有辦法讓它像requests庫同樣方便呢?
答案是有的,有一個叫做aiohttp-requests的庫,它能讓上面的這段代碼變成這樣:
resp = await requests.get(url)
result = await resp.text()
複製代碼
清爽多了對吧?咱們等下就用它了!記得裝這個庫的前提是要先裝aiohttp哦!
而後咱們來看看aiofiles的用法,官方文檔上的示例以下:
async with aiofiles.open('filename', mode='r') as f:
contents = await f.read()
print(contents)
複製代碼
嗯,這個用起來就和用同步代碼操做文件差很少了,沒啥可挑剔的,直接用就完事了。
提示:aiohttp-requests默認是建立並使用了session的,對於一些須要不保留Cookie進行請求的場景須要本身實例化一個Requests
類,並指定cookie_jar爲aiohttp.DummyCookieJar
。
瞭解完了要用的庫以後咱們就能夠開始對貼子中的代碼進行魔改了,若是你用的不是Python3.5以上版本的話須要先準備一下環境。除了版本號大於等於3.5的Python之外,你還須要安裝如下幾個庫:
執行一下pip install aiohttp aiohttp-requests aiofiles pillow
一次性裝完,若是存在多個不一樣版本的Python環境記得區分好。
而後咱們打開編輯器,開始改代碼,首先調整一下導包的部分,將裏面的requests替換成aiohttp-requests,像這樣:
而後搜索一下requests
,看看哪些地方用到了它。
接着把全部搜到的部分都給改爲異步請求的。
同時不要忘了將全部調用過requests.get
的函數都變成協程函數。
而後咱們把文件操做的部分也換成異步的,使用aiofiles.open
代替open
。
最主要的部分都換好了,接着咱們將原先在if __name__ == '__main__':
下的代碼移到一個新寫的協程函數run
中,而且將調用前面協程函數的部分都加上await
。
再導入一下asyncio庫,而後在if __name__ == '__main__':
下寫出這樣的代碼:
上面這個是Python3.7以後才能用的寫法,低於Python3.7要這樣寫:
如今咱們就能夠運行一下看看修改後的代碼能不能跑通了。
這裏報了個錯,從錯誤堆棧中能夠看出問題是出在response = await requests.get(url=url, headers=headers)
這裏的,緣由是self.session._request
方法沒有key爲url
的參數。這個問題很好解決,只須要將url=url
變成url
就行了(原本也就不必這麼指定參數寫)。將代碼中全部用到requests.get
而且存在url=url
這種寫法的都作一下調整:
調整完以後再運行一次就正常了,效果和原先的代碼相同。
注意!僅僅是這樣並不會讓速度發生很大的變化!咱們最後還須要將這一堆代碼中最耗時且是順序執行、沒有併發請求的部分單獨放到一個協程函數中,而且用asyncio.gather
來併發調用(因爲本來的邏輯較爲混亂,這裏除了併發請求之外還進行了一些其餘的微調,主要是計數和文件路徑的部分,可有可無)。
運行一下看看效果,剛運行起來一瞬間就刷了一排的下載完成,跟修改以前比起來簡直是天差地別。
這就是併發請求的威力!咱們僅僅是對他本來的代碼進行了一些微調,把最耗時的下載圖片部分簡單粗暴地使用asyncio.gather
併發執行了一下,速度就從龜爬變成了像坐火箭同樣快!(其實代碼中還有不少能夠優化的點,這裏就不一一拿出來說了)
最後給你們提個醒:
雖然併發請求很是牛逼,可讓你的爬蟲變得飛快,但它也不是不存在任何問題的!
若是你的併發請求數量過大(又稱併發數太高),你的爬蟲就至關因而在對他人的服務器進行Dos攻擊(拒絕服務攻擊)了!
舉個例子,你在爬一個小網站的時候爲了本身爬的速度更快,對併發請求的數量毫無限制,使得你的爬蟲一次性發出了幾百、上千個請求,但通常的小網站根本扛不住這麼高的併發!幾乎會在一瞬間就被你的爬蟲給打爆掉!試想一下,若是你是站長,看到這樣的情形你會怎麼想?
若是你不能理解這個例子所產生的效果是什麼樣的,能夠本身搭建一個Web服務,只放一個簡單的頁面,而後開個幾百併發去請求這個頁面,這樣你就能切身地體會到別人是什麼感覺了。
因此記住,必定要合理控制併發請求的數量,不要對對方網站形成過大的壓力!你給別人留活路,別人纔會給你留活路!
最後再留個小做業吧,如何對這個修改後的代碼增長一道併發數的限制?在留言區給出你的答案。(提示:可經過搜索引擎查找【aiohttp併發鏈接數限制】和【python 列表切割】相關的內容)
這個時代各類東西變化太快,而網絡上的垃圾信息又不少,你須要有一個良好的知識獲取渠道,不少時候早就是一種優點,還不趕忙關注個人公衆號並置頂/星標一波~
發送消息「爬蟲速度提高之併發請求」到個人公衆號【小周碼字】便可得到本文代碼下載地址~