多線程 多進程 協程 Queue(爬蟲代碼)

 

快速理解多進程與多線程以及協程的使用場合和特色

首先咱們來了解下python中的進程,線程以及協程!html

從計算機硬件角度:node

計算機的核心是CPU,承擔了全部的計算任務。
一個CPU,在一個時間切片裏只能運行一個程序。python

 

從操做系統的角度:數據庫

進程和線程,都是一種CPU的執行單元。安全

進程:表示一個程序的上下文執行活動(打開、執行、保存...)服務器

線程:進程執行程序時候的最小調度單位(執行a,執行b...)網絡

一個程序至少有一個進程,一個進程至少有一個線程。多線程

 

並行 和 併發:併發


並行:多個CPU核心,不一樣的程序就分配給不一樣的CPU來運行。可讓多個程序同時執行。app

cpu1 -------------
cpu2 -------------
cpu3 -------------
cpu4 -------------

併發:單個CPU核心,在一個時間切片裏一次只能運行一個程序,若是須要運行多個程序,則串行執行。

cpu1  ----  ----

cpu1    ----  ----

 


多進程/多線程:
表示能夠同時執行多個任務,進程和線程的調度是由操做系統自動完成。


進程:每一個進程都有本身獨立的內存空間,不一樣進程之間的內存空間不共享。
進程之間的通訊有操做系統傳遞,致使通信效率低,切換開銷大。

線程:一個進程能夠有多個線程,全部線程共享進程的內存空間,通信效率高,切換開銷小。

共享意味着競爭,致使數據不安全,爲了保護內存空間的數據安全,引入"互斥鎖"。

一個線程在訪問內存空間的時候,其餘線程不容許訪問,必須等待以前的線程訪問結束,才能使用這個內存空間。

互斥鎖:一種安全有序的讓多個線程訪問內存空間的機制。

 

Python的多線程:

GIL 全局解釋器鎖:線程的執行權限,在Python的進程裏只有一個GIL。

一個線程須要執行任務,必須獲取GIL。

好處:直接杜絕了多個線程訪問內存空間的安全問題。
壞處:Python的多線程不是真正多線程,不能充分利用多核CPU的資源。

可是,在I/O阻塞的時候,解釋器會釋放GIL。


因此:

多進程:密集CPU任務,須要充分使用多核CPU資源(服務器,大量的並行計算)的時候,用多進程。 multiprocessing
缺陷:多個進程之間通訊成本高,切換開銷大。


多線程:密集I/O任務(網絡I/O,磁盤I/O,數據庫I/O)使用多線程合適。
threading.Thread、multiprocessing.dummy
缺陷:同一個時間切片只能運行一個線程,不能作到高並行,可是能夠作到高併發。


協程:又稱微線程,在單線程上執行多個任務,用函數切換,開銷極小。不經過操做系統調度,沒有進程、線程的切換開銷。genvent,monkey.patchall

多線程請求返回是無序的,那個線程有數據返回就處理那個線程,而協程返回的數據是有序的。

缺陷:單線程執行,處理密集CPU和本地磁盤IO的時候,性能較低。處理網絡I/O性能仍是比較高.

 

 

下面以這個網站爲例,採用三種方式爬取。爬取前250名的電影。。

https://movie.douban.com/top250?start=0

 經過分析網頁發現第2頁的url start=25,第3頁的url start=50,第3頁的start=75。所以能夠得出這個網站每一頁的數局是經過遞增start這個參數獲取的。

通常不看第一頁的數據,第一頁的沒有參考價值。

 

此次咱們主要爬取,電影名字跟評分。只是使用不一樣方式去對比下不一樣點,因此數據方面就不過多提取或者保存。只是簡單的將其爬取下打印出來看看。

第一:採用多進程 , multiprocessing 模塊。 固然這個耗時更網絡好壞有關。在所有要請求都正常的狀況下耗時15s多。

 

  Process多進程實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python2
# -*- coding=utf-8 -*-
 
from  multiprocessing  import  Process, Queue
 
import  time
from  lxml  import  etree
import  requests
 
 
class  DouBanSpider(Process):
     def  __init__( self , url, q):
         # 重寫寫父類的__init__方法
         super (DouBanSpider,  self ).__init__()
         self .url  =  url
         self .q  =  q
         self .headers  =  {
             'Host' 'movie.douban.com' ,
             'Referer' 'https://movie.douban.com/top250?start=225&filter=' ,
             'User-Agent' 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36' ,
         }
 
     def  run( self ):
         self .parse_page()
 
     def  send_request( self ,url):
         '''
         用來發送請求的方法
         :return: 返回網頁源碼
         '''
         # 請求出錯時,重複請求3次,
         =  0
         while  i < =  3 :
             try :
                 print  u "[INFO]請求url:" + url
                 return  requests.get(url = url,headers = self .headers).content
             except  Exception as e:
                 print  u '[INFO] %s%s' %  (e,url)
                 + =  1
 
     def  parse_page( self ):
         '''
         解析網站源碼,並採用xpath提取 電影名稱和平分放到隊列中
         :return:
         '''
         response  =  self .send_request( self .url)
         html  =  etree.HTML(response)
         # 獲取到一頁的電影數據
         node_list  =  html.xpath( "//div[@class='info']" )
         for  move  in  node_list:
             # 電影名稱
             title  =  move.xpath( './/a/span/text()' )[ 0 ]
             # 評分
             score  =  move.xpath( './/div[@class="bd"]//span[@class="rating_num"]/text()' )[ 0 ]
            
             # 將每一部電影的名稱跟評分加入到隊列
             self .q.put(score  +  "\t"  +  title)
 
 
def  main():
     # 建立一個隊列用來保存進程獲取到的數據
     =  Queue()
     base_url  =  'https://movie.douban.com/top250?start='
     # 構造全部url
     url_list  =  [base_url + str (num)  for  num  in  range ( 0 , 225 + 1 , 25 )]
 
     # 保存進程
     Process_list  =  []
     # 建立並啓動進程
     for  url  in  url_list:
         =  DouBanSpider(url,q)
         p.start()
         Process_list.append(p)
     
     # 讓主進程等待子進程執行完成
     for  in  Process_list:
         i.join()
 
     while  not  q.empty():
         print  q.get()
 
if  __name__ = = "__main__" :
     
     start  =  time.time()
     main()
     print  '[info]耗時:%s' % (time.time() - start)

  

 

 

 

  採用多線程時,耗時10.4s

 

  thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python2
# -*- coding=utf-8 -*-
 
from  threading  import  Thread
from  Queue  import  Queue
import  time
from  lxml  import  etree
import  requests
 
 
class  DouBanSpider(Thread):
     def  __init__( self , url, q):
         # 重寫寫父類的__init__方法
         super (DouBanSpider,  self ).__init__()
         self .url  =  url
         self .q  =  q
         self .headers  =  {
             'Cookie' 'll="118282"; bid=ctyiEarSLfw; ps=y; __yadk_uid=0Sr85yZ9d4bEeLKhv4w3695OFOPoedzC; dbcl2="155150959:OEu4dds1G1o"; as="https://sec.douban.com/b?r=https%3A%2F%2Fbook.douban.com%2F"; ck=fTrQ; _pk_id.100001.4cf6=c86baf05e448fb8d.1506160776.3.1507290432.1507283501.; _pk_ses.100001.4cf6=*; __utma=30149280.1633528206.1506160772.1507283346.1507290433.3; __utmb=30149280.0.10.1507290433; __utmc=30149280; __utmz=30149280.1506160772.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=223695111.1475767059.1506160772.1507283346.1507290433.3; __utmb=223695111.0.10.1507290433; __utmc=223695111; __utmz=223695111.1506160772.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); push_noty_num=0; push_doumail_num=0' ,
             'Host' 'movie.douban.com' ,
             'Referer' 'https://movie.douban.com/top250?start=225&filter=' ,
             'User-Agent' 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36' ,
         }
 
     def  run( self ):
         self .parse_page()
 
     def  send_request( self ,url):
         '''
         用來發送請求的方法
         :return: 返回網頁源碼
         '''
         # 請求出錯時,重複請求3次,
         =  0
         while  i < =  3 :
             try :
                 print  u "[INFO]請求url:" + url
                 html  =  requests.get(url = url,headers = self .headers).content
             except  Exception as e:
                 print  u '[INFO] %s%s' %  (e,url)
                 + =  1
             else :
                 return  html
 
     def  parse_page( self ):
         '''
         解析網站源碼,並採用xpath提取 電影名稱和平分放到隊列中
         :return:
         '''
         response  =  self .send_request( self .url)
         html  =  etree.HTML(response)
         # 獲取到一頁的電影數據
         node_list  =  html.xpath( "//div[@class='info']" )
         for  move  in  node_list:
             # 電影名稱
             title  =  move.xpath( './/a/span/text()' )[ 0 ]
             # 評分
             score  =  move.xpath( './/div[@class="bd"]//span[@class="rating_num"]/text()' )[ 0 ]
 
             # 將每一部電影的名稱跟評分加入到隊列
             self .q.put(score  +  "\t"  +  title)
 
 
def  main():
     # 建立一個隊列用來保存進程獲取到的數據
     =  Queue()
     base_url  =  'https://movie.douban.com/top250?start='
     # 構造全部url
     url_list  =  [base_url + str (num)  for  num  in  range ( 0 , 225 + 1 , 25 )]
 
     # 保存線程
     Thread_list  =  []
     # 建立並啓動線程
     for  url  in  url_list:
         =  DouBanSpider(url,q)
         p.start()
         Thread_list.append(p)
 
     # 讓主線程等待子線程執行完成
     for  in  Thread_list:
         i.join()
 
     while  not  q.empty():
         print  q.get()
 
if  __name__ = = "__main__" :
 
     start  =  time.time()
     main()
     print  '[info]耗時:%s' % (time.time() - start)

  

 

 

 

採用協程爬取,耗時15S,

  gevent

 

 

 

 

用了多進程,多線程,協程,實現的代碼都同樣,沒有測試出明顯的那個好!都不分上下,可能跟網絡,或者服務器配置有關。

但理論上來講線程,協程在I/O密集的操做性能是要高於進程的。

相關文章
相關標籤/搜索