点击上方“早起Python”,关注并星标公众号

和我一起玩Python

一、前言

很多时候我们写了一个爬虫,实现了需求后会发现了很多值得改进的地方,其中很重要的一点就是爬取速度。本文就通过代码讲解如何使用多进程、多线程、协程来提升爬取速度。注意:我们不深入介绍理论和原理,一切都在代码中。

二、同步

首先我们写一个简化的爬虫,对各个功能细分,有意识进行函数式编程。下面代码的目的是访问300次百度页面并返回状态码,其中parse_1函数可以设定循环次数,每次循环将当前循环数(从0开始)和url传入parse_2函数。

import requestsdef parse_1():url = 'https://www.baidu.com'for i in range(300):parse_2(url)def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

性能的消耗主要在IO请求中,当单进程单线程模式下请求URL时必然会引起等待

示例代码就是典型的串行逻辑,parse_1将url和循环数传递给parse_2parse_2请求并返回状态码后parse_1继续迭代一次,重复之前步骤

三、多线程

因为CPU在执行程序时每个时间刻度上只会存在一个线程,因此多线程实际上提高了进程的使用率从而提高了CPU的使用率

实现多线程的库有很多,这里用concurrent.futures中的ThreadPoolExecutor来演示。介绍ThreadPoolExecutor库是因为它相比其他库代码更简洁

为了方便说明问题,下面代码中如果是新增加的部分,代码行前会加上 > 符号便于观察说明问题,实际运行需要去掉

import requests
> from concurrent.futures import ThreadPoolExecutordef parse_1():url = 'https://www.baidu.com'# 建立线程池> pool = ThreadPoolExecutor(6)for i in range(300):> pool.submit(parse_2, url)> pool.shutdown(wait=True)def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

跟同步相对的就是异步。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式,也就是说多线程是异步处理异步就意味着不知道处理结果,有时候我们需要了解处理结果,就可以采用回调

import requests
from concurrent.futures import ThreadPoolExecutor# 增加回调函数
> def callback(future):> print(future.result())def parse_1():url = 'https://www.baidu.com'pool = ThreadPoolExecutor(6)for i in range(300):> results = pool.submit(parse_2, url)# 回调的关键步骤> results.add_done_callback(callback)pool.shutdown(wait=True)def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

Python实现多线程有一个无数人诟病的GIL(全局解释器锁),但多线程对于爬取网页这种多数属于IO密集型的任务依旧很合适。

四、多进程

多进程用两个方法实现:ProcessPoolExecutormultiprocessing

1. ProcessPoolExecutor

和实现多线程的ThreadPoolExecutor类似

import requests
> from concurrent.futures import ProcessPoolExecutordef parse_1():url = 'https://www.baidu.com'# 建立线程池> pool = ProcessPoolExecutor(6)for i in range(300):> pool.submit(parse_2, url)> pool.shutdown(wait=True)def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

可以看到改动了两次类名,代码依旧很简洁,同理也可以添加回调函数

import requests
from concurrent.futures import ProcessPoolExecutor> def callback(future):> print(future.result())def parse_1():url = 'https://www.baidu.com'pool = ProcessPoolExecutor(6)for i in range(300):> results = pool.submit(parse_2, url)> results.add_done_callback(callback)pool.shutdown(wait=True)def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

2. multiprocessing

直接看代码,一切都在注释中。

import requests
> from multiprocessing import Pooldef parse_1():url = 'https://www.baidu.com'# 建池> pool = Pool(processes=5)# 存放结果> res_lst = []for i in range(300):# 把任务加入池中> res = pool.apply_async(func=parse_2, args=(url,))# 获取完成的结果(需要取出)> res_lst.append(res)# 存放最终结果(也可以直接存储或者print)> good_res_lst = []> for res in res_lst:# 利用get获取处理后的结果> good_res = res.get()# 判断结果的好坏> if good_res:> good_res_lst.append(good_res)# 关闭和等待完成> pool.close()> pool.join()def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

可以看到multiprocessing库的代码稍繁琐,但支持更多的拓展。多进程和多线程确实能够达到加速的目的,但如果遇到IO阻塞会出现线程或者进程的浪费,因此有一个更好的方法……

五、异步非阻塞

协程+回调配合动态协作就可以达到异步非阻塞的目的,本质只用了一个线程,所以很大程度利用了资源

实现异步非阻塞经典是利用asyncio库+yield,为了方便利用逐渐出现了更上层的封装 aiohttp,要想更好的理解异步非阻塞最好还是深入了解asyncio库。而gevent是一个非常方便实现协程的库

import requests
> from gevent import monkey
# 猴子补丁是协作运行的灵魂
> monkey.patch_all()
> import geventdef parse_1():url = 'https://www.baidu.com'# 建立任务列表> tasks_list = []for i in range(300):> task = gevent.spawn(parse_2, url)> tasks_list.append(task)> gevent.joinall(tasks_list)def parse_2(url):response = requests.get(url)print(response.status_code)if __name__ == '__main__':parse_1()

gevent能很大提速,也引入了新的问题:如果我们不想速度太快给服务器造成太大负担怎么办?如果是多进程多线程的建池方法,可以控制池内数量。如果用gevent想要控制速度也有一个不错的方法:建立队列。gevent中也提供了Quene类,下面代码改动较大

import requests
from gevent import monkey
monkey.patch_all()
import gevent
> from gevent.queue import Queuedef parse_1():url = 'https://www.baidu.com'tasks_list = []# 实例化队列> quene = Queue()for i in range(300):# 全部url压入队列> quene.put_nowait(url)# 两路队列> for _ in range(2):> task = gevent.spawn(parse_2)> tasks_list.append(task)gevent.joinall(tasks_list)# 不需要传入参数,都在队列中
> def parse_2():# 循环判断队列是否为空> while not quene.empty():# 弹出队列> url = quene.get_nowait()response = requests.get(url)# 判断队列状态> print(quene.qsize(), response.status_code)if __name__ == '__main__':parse_1()

结束语

以上就是几种常用的加速方法。如果对代码测试感兴趣可以利用time模块判断运行时间。爬虫的加速是重要技能,但适当控制速度也是爬虫工作者的良好习惯,不要给服务器太大压力,拜拜~

早起Python原创作者:陈熹

简介:一只有着码农梦想的眼科狗。更多内容欢迎关注简书:半为花间酒,会不定期更新一些python、R语言、SQL相关及生物信息学、网络爬虫、数据分析、可视化相关的文章。

往期内容(????猛戳可查看)

热门文章:

➤情人节网站➤岗位对比分析➤爬取网易云音乐

➤微博热搜分析➤自动追踪快递➤Python画樱花树

➤Python斗地主➤Matplotlib神器➤全球疫情动态图

数据分析:

➤统计检验➤数据分析报告➤数据分析技巧

➤数据可视化➤Pandas学习➤缺失值处理

➤Python库整理➤数据降维➤疫情数据汇总

记得点个在看支持下~????

代码详解Python多线程、多进程、协程相关推荐

  1. python 协程可以嵌套协程吗_Python | 详解Python中的协程,为什么说它的底层是生成器?...

    今天是Python专题的第26篇文章,我们来聊聊Python当中的协程. 我们曾经在golang关于goroutine的文章当中简单介绍过协程的概念,我们再来简单review一下.协程又称为是微线程, ...

  2. python多线程原理_代码详解Python多线程、多进程、协程-阿里云开发者社区

    云栖号资讯:[点击查看更多行业资讯] 在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 一.前言 很多时候我们写了一个爬虫,实现了需求后会发现了很多值得改进的地方,其中很重要的一点就是爬 ...

  3. Python 多线程 多进程 协程 yield

    python中多线程和多进程的最大区别是稳定性和效率问题 多进程互相之间不影响,一个崩溃了不影响其他进程,稳定性高 多线程因为都在同一进程里,一个线程崩溃了整个进程都完蛋 多进程对系统资源开销大,多线 ...

  4. python 线程安全的数据类型_详解python多线程、锁、event事件机制的简单使用

    详解python多线程.锁.event事件机制的简单使用 发布时间:2020-09-25 02:04:12 来源:脚本之家 阅读:117 作者:君惜 线程和进程 1.线程共享创建它的进程的地址空间,进 ...

  5. python线程延时函数_详解Python 多线程 Timer定时器/延迟执行、Event事件

    Timer继承子Thread类,是Thread的子类,也是线程类,具有线程的能力和特征.这个类用来定义多久执行一个函数. 它的实例是能够延迟执行目标函数的线程,在真正执行目标函数之前,都可以cance ...

  6. python多线程操作列表_详解Python多线程下的list

    list 是 Python 常用的几个基本数据类型之一.正常情况下我们会对 list 有增删改查的操作,显然易见不会有任何问题.那么如果我们试着在多线程下操作list 会有问题吗? 多线程下的 lis ...

  7. 看几段爬虫代码,详解Python多线程、多进程、协程

    点击上方"编程派",选择设为"设为星标" 优质文章,第一时间送达! 一.前言 很多时候我们写了一个爬虫,实现了需求后会发现了很多值得改进的地方,其中很重要的一点 ...

  8. python 多线程和协程结合_如何让 python 处理速度翻倍?内含代码

    阿里妹导读:作为在日常开发生产中非常实用的语言,有必要掌握一些python用法,比如爬虫.网络请求等场景,很是实用.但python是单线程的,如何提高python的处理速度,是一个很重要的问题,这个问 ...

  9. python 多线程和协程结合_一文讲透 “进程、线程、协程”

    本文从操作系统原理出发结合代码实践讲解了以下内容: 什么是进程,线程和协程? 它们之间的关系是什么? 为什么说Python中的多线程是伪多线程? 不同的应用场景该如何选择技术方案? ... 什么是进程 ...

最新文章

  1. 不到顶会现场也能听论文讲解?这个视频集合网站值得收藏
  2. [转]安装和使用JD-Eclipse插件
  3. 2017级数据结构助教批改方案
  4. webapp 状态栏沉浸式
  5. Hadoop之MapTask工作机制
  6. 系统升级后找不到网络计算机,Windows10系统局域网中共享计算机找不到怎么办
  7. 研究生计划-心得征程
  8. Socket选项之SO_RCVTIMEO 和SO_SNDTIMEO
  9. The Economist经济学人是如何使用Go语言构建内容平台微服务架构的?
  10. mysql 多端口备份_mysql数据库迁移、多端口运行、innobackupex备份
  11. java密码查询回显和修改流程_[求助]数据库信息如何回显在我的程序界面中
  12. error: not found: value sc
  13. 基于Multisim的波形发生器
  14. Python-懒人必备-语音小助手
  15. MySQL数据库综合运用——快递管理系统
  16. Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能
  17. java时间戳转换日期格式_Java时间戳与日期格式字符串的互转
  18. 对象存储OSS之阿里云OSS介绍及开通
  19. 第七周项目四—队列数组
  20. 二级列表ExpandableListView的CheckBox使用

热门文章

  1. 用python画波浪线的句子_matplotlib中的锯齿线或波浪线
  2. 上市公司独立董事履职
  3. 使用cmake宝葫芦炼化Opencv第一丹
  4. DELPHI中build和compile有什么区别?
  5. 第一节 openvx(PC)
  6. 多个linux服务器免密登录,使用公钥密钥直接连接
  7. Excel选中图片图片在单元格自动居中支持多选
  8. 基于PHP+MySQL的电影网站的设计与开发
  9. dgi数据治理_什么是数据治理?什么是数据安全治理?两者关系如何?
  10. during怎么念_during什么意思(during这个单词怎么读)