多线程 多进程 协程 Queue(爬虫代码)

快速理解多进程与多线程以及协程的使用场合和特点

首先我们来了解下python中的进程,线程以及协程!

从计算机硬件角度:

计算机的核心是CPU,承担了所有的计算任务。
一个CPU,在一个时间切片里只能运行一个程序。

 

从操作系统的角度:

进程和线程,都是一种CPU的执行单元。

进程:表示一个程序的上下文执行活动(打开、执行、保存...)

线程:进程执行程序时候的最小调度单位(执行a,执行b...)

一个程序至少有一个进程,一个进程至少有一个线程。

 

并行 和 并发:


并行:多个CPU核心,不同的程序就分配给不同的CPU来运行。可以让多个程序同时执行。

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密集的操作性能是要高于进程的。

猜你喜欢

转载自www.cnblogs.com/16795079a/p/10952126.html
今日推荐