要點:
- 手動線程池
- concurrent.futures線程池
- concurrent.futures進程池
- gevent協程
1、實戰爬取維基百科例子
平時咱們有不少任務,尤爲是比較耗時的大量任務要處理,必定會用到併發處理。畢竟串行太慢了,下面咱們去爬一個維基百科的網站:python
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
咱們來爬取紅框裏面的導航文本部分,這是一個很是簡單的爬蟲(關於爬蟲的文章前面寫的太多太多了,你們能夠翻歷史文章)
1).鏈接網頁
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
2).爬取網頁
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
- 函數設計的時候咱們但願入參是一個元組(url,words),方便後面作併發處理
- 網頁很是簡單,直接用requests取爬取,獲取text
- 用pyquery來解析網頁,獲取對國家的描述
- 數據結構用字典對來存儲
2、PK前作點準備工做
1).若是咱們如今要爬取100個國家的信息,有幾種辦法呢:
- 最慢的串行爬取
- 本身手動構建一個線程池,把要爬取的100國家都扔到共享隊列裏面,讓多個線程共享爬取
- 利用concurrent.futures標準庫裏的線程池來爬去
- 用多進程來爬取,雖然網頁請求是CPU密集型的,用進程有點浪費,可是咱們做爲對比,是能夠試一下的
- 用協程也叫微線程,是一種綠色線程,用來作高併發很爽
2).爲了準確的計算每一種方法的耗時,咱們寫一個函數專門來計算時間:
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
下面咱們用上面的5種方法逐一運行,爲了簡單期間咱們統一爬取5個國家,每種方法上面用裝飾器@cost_time來計算一下,看看到底哪一種方便比較簡單,速度又最快~~
3、慢慢的串行處理
先來段最通俗的one by one的串行處理
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
>>>
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
爬取5個國家的簡介花了17秒,串行由於在等待服務器的相應的時候傻等,因此浪費了不少時間
4、手動建多線程共享隊列
利用queue有鎖的功能,手動把數據塞進隊列,而後多個線程共享爬取
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
>>>
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
多線程確實很是快,只須要5秒左右就搞定了,可是用這種方法代碼太多,有沒有更優美的方法呢
5、用標準庫裏面的線程池
與其動手造輪子,不如用無所不能的庫,Python裏面的庫真的太多太多了!這也是Python爲啥這麼火爆的緣由之一.
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
>>>
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
發現用系統的線程池跟手動的幾乎差很少,可是你們發現沒有用輪子來處理,代碼量很是小,並且很優美!(這是Python之美,能用輪子儘可能用輪子,簡潔高效).
有同窗會問,有沒有什麼狀況是必定要手動構建線程池,而不能用 <ThreadPoolExecutor >,確實有這樣的狀況,你們思考一下,不明白的留言給我,偷偷告訴你.
6、用標準庫的裏進程池
既然上面有線程池,必定有進程池吧。是的,咱們下面來看看殺雞用牛刀的多進程處理,須要幾秒呢:
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
>>>
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
差很少也是5-6秒左右,多進程仍是比較快的!可是咱們這裏是5個國家,若是500個你不可能開100個進程來處理呢,若是碰到很是巨大的併發量,又要節省系統資源,又要速度很快,怎麼辦呢,咱們看最後一招
7、絕招,用協程來併發
用一下gevent這個庫,功能強大使用簡單,對協程的封裝比較好。每當一個協程阻塞時,程序將自動調度,gevent幫咱們處理了全部的底層細節
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
gevent.spawn來建立一個一個的協程對象,而後joinall會等待全部的協程運行完成,最後咱們就獲取到幾個國家的簡介數據。
>>>
![在這裏輸入圖片標題 輸入圖片說明](http://static.javashuo.com/static/loading.gif)
哇協程果真很牛逼,只須要2.8秒左右,很是舒爽的感受!可是這裏由於requests庫有個缺點,訪問的時候是上一個訪問結束,才能進行下一次訪問!因此須要用gevent的猴子補丁.另外gevent雖然很好,可是它是大規模併發,若是發起10000個網絡請求,估計很快會被封IP!
好咱們來總結一下:
- 同步處理:17秒
- 異步手動建線程池:5.5秒
- 異步標準庫線程池:5.2秒
- 異步標準庫進程池:5.9秒
- 併發協程:2.8秒
很明顯用Gevent最快,尤爲是在大規模的幾十萬級別的併發處理效果很是明顯.
線程池也是一個不錯的選擇,並且比較靈活,若是須要多個併發任務之間有交互的話,仍是須要用線程池.
那進程池呢,咱們上面考慮的都是IO 密集型task,若是咱們碰到了CPO型的那就必需要多核多CPU運行才能加速。畢竟Python有一個煩人的GIL,
好今天的文章就寫到這裏,歡迎留言討論服務器
文章摘自:菜鳥學python微信微信