python爬虫之线程池和进程池功能与用法详解


Posted in Python onAugust 02, 2018

本文实例讲述了python爬虫之线程池和进程池功能与用法。分享给大家供大家参考,具体如下:

一、需求

最近准备爬取某电商网站的数据,先不考虑代理、分布式,先说效率问题(当然你要是请求的太快就会被封掉,亲测,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的密集运算,平时我们用不到。

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

gevent:

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

pip3 install greenlet
pip3 install gevent

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

gevent+requests:

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'),
])

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

gevent+reqeust+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'),
])

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

pip3 install grequests

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

grequests:

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)

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

python爬虫之线程池和进程池功能与用法详解

更多关于Python相关内容可查看本站专题:《Python Socket编程技巧总结》、《Python正则表达式用法总结》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python入门与进阶经典教程》及《Python文件与目录操作技巧汇总》

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
Python实现数通设备端口使用情况监控实例
Jul 15 Python
Python 实现数据库更新脚本的生成方法
Jul 09 Python
浅谈用Python实现一个大数据搜索引擎
Nov 28 Python
Python 实现字符串中指定位置插入一个字符
May 02 Python
对python判断是否回文数的实例详解
Feb 08 Python
django 数据库连接模块解析及简单长连接改造方法
Aug 29 Python
python模块导入的方法
Oct 24 Python
详解python opencv、scikit-image和PIL图像处理库比较
Dec 26 Python
Python字符串格式化f-string多种功能实现
May 07 Python
Python使用socketServer包搭建简易服务器过程详解
Jun 12 Python
pytorch锁死在dataloader(训练时卡死)
May 28 Python
如何Python使用re模块实现okenizer
Apr 30 Python
Python列表推导式与生成器用法分析
Aug 02 #Python
python flask实现分页的示例代码
Aug 02 #Python
Django分页查询并返回jsons数据(中文乱码解决方法)
Aug 02 #Python
Python实现正整数分解质因数操作示例
Aug 01 #Python
Python列表生成式与生成器操作示例
Aug 01 #Python
Python开发最牛逼的IDE——pycharm
Aug 01 #Python
django从请求到响应的过程深入讲解
Aug 01 #Python
You might like
php实现网站插件机制的方法
2009/11/10 PHP
PHP中常用的字符串格式化函数总结
2014/11/19 PHP
PHP list() 将数组中的值赋给变量的简单实例
2016/06/13 PHP
php实现xml与json之间的相互转换功能实例
2016/07/07 PHP
PHP常用字符串函数用法实例总结
2020/06/04 PHP
List the Stored Procedures in a SQL Server database
2007/06/20 Javascript
Jquery CheckBox全选方法代码附js checkbox全选反选代码
2010/06/09 Javascript
当json键为数字时的取值方法解析
2013/11/15 Javascript
全面兼容的javascript时间格式化函数(比较实用)
2014/05/14 Javascript
js分页工具实例
2015/01/28 Javascript
js实现鼠标移到链接文字弹出一个提示层的方法
2015/05/11 Javascript
JS动态创建DOM元素的方法
2015/06/09 Javascript
jquery常用函数与方法汇总
2015/09/01 Javascript
JavaScript缓冲运动实现方法(2则示例)
2016/01/08 Javascript
详解jquery事件delegate()的使用方法
2016/01/25 Javascript
AngularJS入门教程之Select(选择框)详解
2016/07/27 Javascript
JS实现鼠标滑过显示边框的菜单效果
2016/09/21 Javascript
基于JavaScript实现数码时钟效果
2020/03/30 Javascript
详解VScode编辑器vue环境搭建所遇问题解决方案
2019/04/26 Javascript
实例分析JS中的相等性判断===、 ==和Object.is()
2019/11/17 Javascript
AI小程序之语音听写来了,十分钟掌握百度大脑语音听写全攻略
2020/03/13 Javascript
JavaScript this指向相关原理及实例解析
2020/07/10 Javascript
通过实例解析jQ Ajax操作相关原理
2020/09/23 Javascript
Python实现遍历目录的方法【测试可用】
2017/03/22 Python
python3下实现搜狗AI API的代码示例
2018/04/10 Python
python中多层嵌套列表的拆分方法
2018/07/02 Python
Python+PyQT5的子线程更新UI界面的实例
2019/06/14 Python
python中while和for的区别总结
2019/06/28 Python
将pymysql获取到的数据类型是tuple转化为pandas方式
2020/05/15 Python
main 主函数执行完毕后,是否可能会再执行一段代码,给出说明
2012/12/05 面试题
元旦联欢会感言
2014/03/04 职场文书
施工安全生产承诺书
2014/05/23 职场文书
2014年秋季开学典礼主持词
2014/08/02 职场文书
2015年卫生监督工作总结
2015/05/21 职场文书
女性励志书籍推荐
2019/08/19 职场文书
Nginx配置https原理及实现过程详解
2021/03/31 Servers