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删除指定目录下过期文件的2个脚本分享
Apr 10 Python
进一步探究Python中的正则表达式
Apr 28 Python
python Celery定时任务的示例
Mar 13 Python
解决Python获取字典dict中不存在的值时出错问题
Oct 17 Python
解决pyinstaller打包exe文件出现命令窗口一闪而过的问题
Oct 31 Python
Python3.5装饰器原理及应用实例详解
Apr 30 Python
Django框架实现的普通登录案例【使用POST方法】
May 15 Python
获取Pytorch中间某一层权重或者特征的例子
Aug 17 Python
在Django下创建项目以及设置settings.py教程
Dec 03 Python
基于Python 的语音重采样函数解析
Jul 06 Python
python 中关于pycharm选择运行环境的问题
Oct 31 Python
Python爬虫如何破解JS加密的Cookie
Nov 19 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执行sql语句的写法
2009/03/10 PHP
解析PHP缓存函数的使用说明
2013/05/10 PHP
解析PHP中的file_get_contents获取远程页面乱码的问题
2013/06/25 PHP
php结合安卓客户端实现查询交互实例
2015/05/05 PHP
typecho插件编写教程(六):调用接口
2015/05/28 PHP
深入理解PHP+Mysql分布式事务与解决方案
2020/12/03 PHP
JQueryEasyUI datagrid框架的基本使用
2013/04/08 Javascript
addEventListener 的用法示例介绍
2014/05/07 Javascript
ext中store.load跟store.reload的区别示例介绍
2014/06/17 Javascript
DEDECMS如何为文章添加HOT NEW标志图片
2015/08/14 Javascript
javascript实现别踩白块儿小游戏程序
2015/11/22 Javascript
js过滤HTML标签完整实例
2015/11/26 Javascript
详解Angularjs中的依赖注入
2016/03/11 Javascript
JS实现的颜色实时渐变效果完整实例
2016/03/25 Javascript
jquery div模态窗口的简单实例
2016/05/28 Javascript
前端面试题及答案整理(二)
2016/08/26 Javascript
js基于myFocus实现轮播图效果
2017/02/14 Javascript
JS表单传值和URL编码转换
2018/03/03 Javascript
JS实现的透明度渐变动画效果示例
2018/04/28 Javascript
JavaScript数据结构之栈实例用法
2019/01/18 Javascript
详解Vue的组件中data选项为什么必须是函数
2020/08/17 Javascript
python实现数据图表
2017/07/29 Python
Python中 传递值 和 传递引用 的区别解析
2018/02/22 Python
Django项目中用JS实现加载子页面并传值的方法
2018/05/28 Python
Python 3.x基于Xml数据的Http请求方法
2018/12/28 Python
python3下载抖音视频的完整代码
2019/06/05 Python
python误差棒图errorbar()函数实例解析
2020/02/11 Python
pytorch 限制GPU使用效率详解(计算效率)
2020/06/27 Python
德国网上药房:Apotal
2017/04/04 全球购物
Tessabit美国:集世界奢侈品和设计师品牌的意大利精品买手店
2020/06/29 全球购物
什么是SCM(软件配置管理)
2014/08/16 面试题
授权委托书样本及填写说明
2014/09/19 职场文书
优秀学生干部事迹材料
2014/12/24 职场文书
2015年园林绿化工作总结
2015/05/23 职场文书
实战 快速定位MySQL的慢SQL
2022/03/22 MySQL
Java版 简易五子棋小游戏
2022/05/04 Java/Android