一、需求

  最近准备爬取某电商网站的数据,先不考虑代理、分布式,先说效率问题(当然你要是请求的太快就会被封掉,亲测,400个请求过去,服务器直接拒绝连接,心碎),步入正题。一般情况下小白的我们第一个想到的是for循环,这个可是单线程啊。那我们考虑for循环直接开他个5个线程,问题来了,如果有一个url请求还没有回来,后面的就干等,这么用多线程等于没用,到处贴创可贴。

二、性能考虑

  确定要用多线程或者多进程了,那我们到底是用多线程还是多进程,有些人对多进程和多线程有一定的偏见,就因为python的GIL锁,下面我们说一下这两个东西的差别。

三、多线程:

  一般情况下我们启动一个.py文件,就等于启动了一个进程,一个进程里面默认有一个线程工作,我们使用的多线程的意思就是在一个进程里面启用多个线程。但问题来了,为什么要使用多线程呢?我知道启动一个进程的时候需要创建一些内存空间,就相当于一间房子,我们要在这个房子里面干活,你可以想一个人就等于一个线程,你房子里面有10个人的空间跟有20个人的空间,正常情况下是不一样的,因为我们知道线程和线程之间默认是可以通信的(进程之间默认是不可以通信的,不过可以用技术实现,比如说管道)。可以多线程为了保证计算数据的正确性,所以出现了GIL锁,保证同一时间只能有一个线程在计算。GIL锁你可以基本理解为,比如在这个房间里要算一笔账,在同一时间内只能有一个人在算这笔账,想一个问题,如果这笔账5个人就能算清楚,我需要10平米的房间就行,那为什么要请10个人,花20平米呢?所以并不是开的线程越多越好。但是,但是,但是,注意大家不用动脑筋(CPU计算)算这笔账的时候可以去干别的事(比如说5个人分工,各算一部分),比如说各自把自己算完后的结果记录在账本上以便后面对账,这个的话每个人都有自己的账本,所以多线程适合IO操作,记住了就算是适合IO操作,也不代表说人越多越好,所以这个量还是得根据实际情况而定。

  示例:

 import requests
from concurrent.futures import ThreadPoolExecutor urls_list = [
'https://www.baidu.com',
'http://www.gaosiedu.com',
'https://www.jd.com',
'https://www.taobao.com',
'https://news.baidu.com',
]
pool = ThreadPoolExecutor(3) def request(url):
response = requests.get(url)
return response def read_data(future,*args,**kwargs):
response = future.result()
response.encoding = 'utf-8'
print(response.status_code,response.url) def main():
for url in urls_list:
done = pool.submit(request,url)
done.add_done_callback(read_data) if __name__ == '__main__':
main()
pool.shutdown(wait=True)

线程池

四、多进程:

  上面我们介绍了多线程(线程池),现在我们聊聊进程池,我们知道一个进程占用一个CPU,现在的配置CPU一般都是4核,我们启动两个进程就是分别在两个CPU里面(两个内核)各运行一个进程,我知道进程里面才有线程,默认是一个。但是有个缺点,按照上面的说法,开两个进程占用的内存空间是开一个进程占用内存空间的2倍。CPU就占用了2个核,电脑还得干别的事儿对吧,不能冒冒失失瞎用。开的太多是不是其他程序就得等着,我们思考一下,占用这么多的内存空间,利用了多个CPU的优点为了什么?CPU是用来做什么的?没错就是用来计算的,所以在CPU密集运算的情况下建议用多进程。注意,具体要开几个进程,根据机器的实际配置和实际生产情况而定。

 import requests
from concurrent.futures import ProcessPoolExecutor urls_list = [
'https://www.baidu.com',
'http://www.gaosiedu.com',
'https://www.jd.com',
'https://www.taobao.com',
'https://news.baidu.com',
]
pool = ProcessPoolExecutor(3) def request(url):
response = requests.get(url)
return response def read_data(future,*args,**kwargs):
response = future.result()
response.encoding = 'utf-8'
print(response.status_code,response.url) def main():
for url in urls_list:
done = pool.submit(request,url)
done.add_done_callback(read_data) if __name__ == '__main__':
main()
pool.shutdown(wait=True)

进程池

总结:

  1、多线程适合IO密集型程序

   2、多进程适合CPU密集运算型程序

五、协程:

  协程:又称微线程纤程。英文名Coroutine。那协程到底是个什么东西,通俗的讲就是比线程还要小的线程,所以才叫微线程。

  主要作用:有人要问了,在python中线程是原子操作(意思就是说一句话或者一个动作就能搞定的操作或者计算),怎么还有个叫协程的呢?

  优点:

    1、使用高并发、高扩展、低性能的;一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

    2、无需线程的上下文切换开销(乍一看,什么意思呢?我们都知道python实际上是就是单线程,那都是怎么实现高并发操作呢,就是CPU高速的切换,每个任务都干一点,最后看上去是一起完事儿的,肉眼感觉就是多线程、多进程)

  缺点:

    1、无法利用CPU的多核优点,这个好理解,进程里面包含线程,而协程就是细分后的线程,也就是说一个进程里面首先是线程其后才是协程,那肯定是用不了多核了,不过可以多进程配合,使用CPU的密集运算,平时我们用不到。

  1、一般情况下用的比较多的是asyncio或者是gevent这两个技术实现协程,asyncio是python自带的技术,gevent第三方库,个人比较喜欢gevent这个技术。

  gevent:

    安装:gevent需要安装greenlet,因为它是使用到了greenlet这个库。

pip3 install greenlet
pip3 install gevent

    1、gevent的基本实现,按照下面的写法,程序启动后将会开启许许多多的协程,反而特别影响性能。

 import requests
import gevent
from gevent import monkey #把当前的IO操作,打上标记,以便于gevent能检测出来实现异步(否则还是串行)
monkey.patch_all() def task(url):
'''
1、request发起请求
:param url:
:return:
'''
response = requests.get(url)
print(response.status_code) gevent.joinall([
gevent.spawn(task,url='https://www.baidu.com'),
gevent.spawn(task,url='http://www.sina.com.cn'),
gevent.spawn(task,url='https://news.baidu.com'),
])

gevent+requests

    2、有一个改进版本,就是可以设置到底让它一次发起多少个请求(被忘了,协程=高并发现实之一)。其实里面就是利用gevnet下的pool模块里面的Pool控制每次请求的数量。  

 import requests
import gevent
from gevent import monkey
from gevent.pool import Pool #把当前的IO操作,打上标记,以便于gevent能检测出来实现异步(否则还是串行)
monkey.patch_all() def task(url):
'''
1、request发起请求
:param url:
:return:
'''
response = requests.get(url)
print(response.status_code) #控制最多一次向远程提交多少个请求,None代表不限制
pool = Pool(5)
gevent.joinall([
pool.spawn(task,url='https://www.baidu.com'),
pool.spawn(task,url='http://www.sina.com.cn'),
pool.spawn(task,url='https://news.baidu.com'),
])

gevent+reqeust+Pool(控制每次请求数量)

    3、还有一版本,每次我们都要装greenlet和gevent这肯定是没法子,但是,我们上面写的这个改进版还是有点麻烦,所以就有人写了100多行代码把它们给搞到了一起,对就是搞到了一起,叫grequests,就是前者两个技术的结合。

pip3 install grequests

    这个版本是不是特别变态,直接把requests、greenlet、gevent、Pool都省的导入了,但是装还是要装的,有人说从下面代码中我没看到Pool的参数啊,grequests.map(request_list,size=5),size就是你要同时开几个协程,还有参数你得点进去看,是不是很牛,很轻松

 import grequests

 request_list = [
grequests.get('https://www.baidu.com'),
grequests.get('http://www.sina.com.cn'),
grequests.get('https://news.baidu.com'),
]
# ##### 执行并获取响应列表 #####
response_list = grequests.map(request_list,size=5)
print(response_list)

grequests

    结果返回一个列表,你可以再迭代一下就行了。

    

python爬虫之线程池和进程池的更多相关文章

  1. Python并发复习4- concurrent.futures模块(线程池和进程池)

    Python标准库为我们提供了threading(多线程模块)和multiprocessing(多进程模块).从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提 ...

  2. Python线程池与进程池

    Python线程池与进程池 前言 前面我们已经将线程并发编程与进程并行编程全部摸了个透,其实我第一次学习他们的时候感觉非常困难甚至是吃力.因为概念实在是太多了,各种锁,数据共享同步,各种方法等等让人十 ...

  3. python之线程池和进程池

    线程池和进程池 一.池的概念 池是用来保证计算机硬件安全的情况下最大限度的利用计算机 它降低了程序的运行效率但是保证了计算机硬件的安全从而让你写的程序能够正常运行 ''' 无论是开设进程也好还是开设线 ...

  4. 协程与concurent.furtrue实现线程池与进程池

    1concurent.furtrue实现线程池与进程池 2协程 1concurent.furtrue实现线程池与进程池 实现进程池 #进程池 from concurrent.futures impor ...

  5. GIL全局解释器锁,线程池与进程池 同步异步,阻塞与非阻塞,异步回调

    GIL全局解释器锁 1.什么是GIL 官方解释:'''In CPython, the global interpreter lock, or GIL, is a mutex that prevents ...

  6. 并发编程:GIL,线程池,进程池,阻塞,非阻塞,同步,异步

    一  GIL(global interpreter lock) GIL中文叫全局解释器锁,我们执行一个文件会产生一个进程,那么我们知道进程不是真正的执行单位,而是资源单位,所以进程中放有解释器(cpy ...

  7. 多进程 multiprocessing 多线程Threading 线程池和进程池concurrent.futures

    multiprocessing.procsess 定义一个函数 def func():pass 在if __name__=="__main__":中实例化 p = process( ...

  8. concurrent.futures模块简单介绍(线程池,进程池)

    一.基类Executor Executor类是ThreadPoolExecutor 和ProcessPoolExecutor 的基类.它为我们提供了如下方法: submit(fn, *args, ** ...

  9. 使用concurrent.futures模块中的线程池与进程池

    使用concurrent.futures模块中的线程池与进程池 线程池与进程池 以线程池举例,系统使用多线程方式运行时,会产生大量的线程创建与销毁,创建与销毁必定会带来一定的消耗,甚至导致系统资源的崩 ...

  10. day 7-7 线程池与进程池

    一. 进程池与线程池 在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这 ...

随机推荐

  1. -bash: _docker_machine_ps1: 未找到命令

    场景:在安装完docker-machine,设置docker-machine命令自动补齐的时候,出现以下错误: -bash: __docker_machine_ps1: 未找到命令 解决办法:在~/. ...

  2. hive复杂类型实战

    1.hive 数组简单实践: CREATE TABLE `emp`( `name` string, `emps` array<string>) ROW FORMAT SERDE 'org. ...

  3. No.3

    1.查看httpd进程数(即prefork模式下Apache能够处理的并发请求数): ps -ef | grep httpd | wc -l 返回结果示例: 1388 表示Apache能够处理1388 ...

  4. 【转】Win10开机密码忘了?教你破解Win10开机密码

    [PConline 技巧]Win10开机密码忘记了怎么办?这个问题很多人都遇到过,如何破解一台Win10电脑的开机密码呢(非BIOS开机密码)?其实很简单,利用下面这个方法,分分钟就能搞定. 一招破解 ...

  5. 【vue】钩子函数生命周期

    图1 图2: 图3 相关资料:http://www.zhimengzhe.com/Javascriptjiaocheng/236707.html    https://segmentfault.com ...

  6. 007_Mac上安装Node和NPM

    一.推荐brew来对node和npm版本进行管理. <1>确保brew是安全可靠的,代码如下: $ brew doctor #直接install node会有以下报错https://git ...

  7. 3.if结构

    一.简单if结构1.定义:程序的条件判断2.语法:if(条件){ 语句块1}else{ 语句块2}语句块33:说明:条件必须是条件表达式,其结果必须是一个boolean类型 else是可选项,可以不写 ...

  8. 阅读ARM Memory(L1/L2/MMU)笔记

    <ARM Architecture Reference Manual ARMv8-A>里面有Memory层级框架图,从中可以看出L1.L2.DRAM.Disk.MMU之间的关系,以及他们在 ...

  9. python 链接 redis 失败 由于目标计算机积极拒绝,无法连接

    whereis redis-cli ps -ef |grep redis 1.启动redis redis-server & 2.查看redis 进程 ps -ef |grep redis 3. ...

  10. Python_每日习题-0008-九九乘法表

    题目: 输出9*9乘法口诀表. 程序分析:分行与分列的考虑,共9行9列,i控制行,j控制列. for i in range(1, 10): for j in range(1, i+1): print( ...