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

本文实例讲述了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 Socket编程技巧总结》、《Python正则表达式用法总结》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python入门与进阶经典教程》及《Python文件与目录操作技巧汇总》

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

(0)

相关推荐

  • Python自定义线程池实现方法分析

    本文实例讲述了Python自定义线程池实现方法.分享给大家供大家参考,具体如下: 关于python的多线程,由与GIL的存在被广大群主所诟病,说python的多线程不是真正的多线程.但多线程处理IO密集的任务效率还是可以杠杠的. 我实现的这个线程池其实是根据银角的思路来实现的. 主要思路: 任务获取和执行: 1.任务加入队列,等待线程来获取并执行. 2.按需生成线程,每个线程循环取任务. 线程销毁: 1.获取任务是终止符时,线程停止. 2.线程池close()时,向任务队列加入和已生成线程等量的

  • python线程池的实现实例

    直接上代码: 复制代码 代码如下: # -*- coding: utf-8 -*- import Queue import threadingimport urllibimport urllib2import os def down(url,n):    print 'item '+str(n)+' start '    filename=urllib2.unquote(url).decode('utf8').split('/')[-1]    urllib.urlretrieve(url, f

  • php与python实现的线程池多线程爬虫功能示例

    本文实例讲述了php与python实现的线程池多线程爬虫功能.分享给大家供大家参考,具体如下: 多线程爬虫可以用于抓取内容了这个可以提升性能了,这里我们来看php与python 线程池多线程爬虫的例子,代码如下: php例子 <?php class Connect extends Worker //worker模式 { public function __construct() { } public function getConnection() { if (!self::$ch) { sel

  • 用Python实现一个简单的线程池

    线程池的概念是什么? 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是 如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收.所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些 很耗资源的对象创建和销毁.如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因. 我理解为线程池是一个存放很多线程的单位,同时还有一个对应的任务队列.整个执行过程其实就是使

  • Python探索之自定义实现线程池

    为什么需要线程池呢? 设想一下,如果我们使用有任务就开启一个子线程处理,处理完成后,销毁子线程或等得子线程自然死亡,那么如果我们的任务所需时间比较短,但是任务数量比较多,那么更多的时间是花在线程的创建和结束上面,效率肯定就低了.     线程池的原理: 既然是线程池(Thread pool),其实名字很形象,就是把指定数量的可用子线程放进一个"池里",有任务时取出一个线程执行,任务执行完后,并不立即销毁线程,而是放进线程池中,等待接收下一个任务.这样内存和cpu的开销也比较小,并且我们

  • Python线程池模块ThreadPoolExecutor用法分析

    本文实例讲述了Python线程池模块ThreadPoolExecutor用法.分享给大家供大家参考,具体如下: python3内置的有Threadingpool和ThreadPoolExecutor模块,两个都可以做线程池,当然ThreadPoolExecutor会更好用一些,而且也有ProcessPoolExecutor进程池模块,使用方法基本一致. 首先导入模块 from concurrent.futures import ThreadPoolExecutor 使用方法很简单,最常用的可能就

  • 浅谈python 线程池threadpool之实现

    首先介绍一下自己使用到的名词: 工作线程(worker):创建线程池时,按照指定的线程数量,创建工作线程,等待从任务队列中get任务: 任务(requests):即工作线程处理的任务,任务可能成千上万个,但是工作线程只有少数.任务通过          makeRequests来创建 任务队列(request_queue):存放任务的队列,使用了queue实现的.工作线程从任务队列中get任务进行处理: 任务处理函数(callable):工作线程get到任务后,通过调用任务的任务处理函数即(re

  • python实现线程池的方法

    本文实例讲述了python实现线程池的方法.分享给大家供大家参考.具体如下: 原理:建立一个任务队列,然多个线程都从这个任务队列中取出任务然后执行,当然任务队列要加锁,详细请看代码 文件名:thrd_pool.py 系统环境:ubuntu linux & python2.6 import threading import time import signal import os class task_info(object): def __init__(self): self.func = No

  • 详解python中的线程与线程池

    线程 进程和线程 什么是进程? 进程就是正在运行的程序, 一个任务就是一个进程, 进程的主要工作是管理资源, 而不是实现功能 什么是线程? 线程的主要工作是去实现功能, 比如执行计算. 线程和进程的关系就像员工与老板的关系, 老板(进程) 提供资源 和 工作空间, 员工(线程) 负责去完成相应的任务 特点 一个进程至少由一个线程, 这一个必须存在的线程被称为主线程, 同时一个进程也可以有多个线程, 即多线程 当我们我们遇到一些需要重复执行的代码时, 就可以使用多线程分担一些任务, 进而加快运行速

  • Python 线程池用法简单示例

    本文实例讲述了Python 线程池用法.分享给大家供大家参考,具体如下: # -*- coding:utf-8 -*- #! python3 ''' Created on 2019-10-2 @author: Administrator ''' from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import os,time,random def task(n): print('%s is runing' %

随机推荐