快速提升爬蟲性能的幾種方法
作者:孟慶健
來源:http://www.cnblogs.com/mengqingjian/p/8329651.html
一、背景知識
爬蟲的本質就是一個socket客戶端與服務端的通信過程,如果我們有多個url待爬取,只用一個線程且採用串列的方式執行,
那隻能等待爬取一個結束後才能繼續下一個,效率會非常低。需要強調的是:對於單線程下串列N個任務,並不完全等同於低效。
如果這N個任務都是純計算的任務,那麼該線程對cpu的利用率仍然會很高,之所以單線程下串列多個爬蟲任務低效, 是因為爬蟲任務是明顯的IO密集型程序。
關於IO模型詳見鏈接:http://www.cnblogs.com/linhaifeng/articles/7454717.html
那麼該如何提高爬取性能呢?且看下述概念。
二、同步、非同步、回調機制
*1、同步調用:即提交一個任務後就在原地等待任務結束,等到拿到任務的結果後再繼續下一行代碼,效率低下*
2、一個簡單的解決方案:多線程或多進程
在伺服器端使用多線程(或多進程)。
多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進程),這樣任何一個連接的阻塞都不會影響其他的連接。
該方案的問題是:
開啟多進程或都線程的方式,我們是無法無限制地開啟多進程或多線程的:在遇到要同時響應成百上千路的連接請求,則無論多線程還是多進程都會嚴重佔據系統資源,降低系統對外界響應效率,
而且線程與進程本身也更容易進入假死狀態。
3、改進方案:
線程池或進程池+非同步調用:提交一個任務後並不會等待任務結束,而是繼續下一行代碼**
很多程序員可能會考慮使用"線程池"或"連接池"。"線程池"旨在減少創建和銷毀線程的頻率,其維持一定合理數量的線程,並讓空閑的線程重新承擔新的執行任務。"連接池"維持連接的緩存池,盡量重用已有的連接、減少創建和關閉連接的頻率。這兩種技術都可以很好的降低系統開銷,都被廣泛應用很多大型系統,如websphere、tomcat和各種資料庫等。
改進後方案其實也存在著問題:
"線程池"和"連接池"技術也只是在一定程度上緩解了頻繁調用IO介面帶來的資源佔用。而且,所謂"池"始終有其上限,當請求大大超過上限時,"池"構成的系統對外界的響應並不比沒有池的時候效果好多少。所以使用"池"必須考慮其面臨的響應規模,並根據響應規模調整"池"的大小。
對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,"線程池"或"連接池"或許可以緩解部分壓力,但是不能解決所有問題。
總之,多線程模型可以方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,可以用非阻塞介面來嘗試解決這個問題。**
三 、高性能
上述無論哪種解決方案其實沒有解決一個性能相關的問題:IO阻塞,無論是多進程還是多線程,在遇到IO阻塞時都會被操作系統強行剝奪走CPU的執行許可權,程序的執行效率因此就降低了下來。
解決這一問題的關鍵在於,我們自己從應用程序級別檢測IO阻塞,然後切換到我們自己程序的其他任務執行,這樣把我們程序的IO降到最低,我們的程序處於就緒態就會增多,以此來迷惑操作系統,操作系統便以為我們的程序是IO比較少的程序,從而會儘可能多的分配CPU給我們,這樣也就達到了提升程序執行效率的目的**。
1、在python3.3之後新增了asyncio模塊,可以幫我們檢測IO(只能是網路IO),實現應用程序級別的切換
2、但asyncio模塊只能發tcp級別的請求,不能發http協議,因此,在我們需要發送http請求的時候,需要我們自定義http報頭。
3、自定義http報頭多少有點麻煩,於是有了aiohttp模塊,專門幫我們封裝http報頭,然後我們還需要用asyncio檢測IO實現切換。
asyncio+aiohttp
4、此外,還可以將requests.get函數傳給asyncio,就能夠被檢測了。
5、還有之前在協程時介紹的gevent模塊
6、封裝了gevent+requests模塊的grequests模塊
7、twisted:是一個網路框架,其中一個功能是發送非同步請求,檢測IO並自動切換。
8、tornado
發現上例在所有任務都完畢後也不能正常結束,為了解決該問題,讓我們來加上計數器。
題圖:pexels,CC0 授權。


TAG:編程派 |