看几段爬虫代码,详解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_2
,parse_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密集型的任务依旧很合适。
四、多进程
多进程用两个方法实现:ProcessPoolExecutor
和multiprocessing
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相关及生物信息学、网络爬虫、数据分析、可视化相关的文章。
回复下方「关键词」,获取优质资源回复关键词「 pybook03」,立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版
回复关键词「入门资料」,立即获取主页君整理的 10 本 Python 入门书的电子版
回复关键词「m」,立即获取Python精选优质文章合集
回复关键词「book 数字」,将数字替换成 0 及以上数字,有惊喜好礼哦~
题图:pexels,CC0 授权。好文章,我在看❤️
看几段爬虫代码,详解Python多线程、多进程、协程相关推荐
- python 协程可以嵌套协程吗_Python | 详解Python中的协程,为什么说它的底层是生成器?...
今天是Python专题的第26篇文章,我们来聊聊Python当中的协程. 我们曾经在golang关于goroutine的文章当中简单介绍过协程的概念,我们再来简单review一下.协程又称为是微线程, ...
- python多线程原理_代码详解Python多线程、多进程、协程-阿里云开发者社区
云栖号资讯:[点击查看更多行业资讯] 在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 一.前言 很多时候我们写了一个爬虫,实现了需求后会发现了很多值得改进的地方,其中很重要的一点就是爬 ...
- 代码详解Python多线程、多进程、协程
点击上方"早起Python",关注并星标公众号 和我一起玩Python 一.前言 很多时候我们写了一个爬虫,实现了需求后会发现了很多值得改进的地方,其中很重要的一点就是爬取速度.本 ...
- Python 多线程 多进程 协程 yield
python中多线程和多进程的最大区别是稳定性和效率问题 多进程互相之间不影响,一个崩溃了不影响其他进程,稳定性高 多线程因为都在同一进程里,一个线程崩溃了整个进程都完蛋 多进程对系统资源开销大,多线 ...
- python 线程安全的数据类型_详解python多线程、锁、event事件机制的简单使用
详解python多线程.锁.event事件机制的简单使用 发布时间:2020-09-25 02:04:12 来源:脚本之家 阅读:117 作者:君惜 线程和进程 1.线程共享创建它的进程的地址空间,进 ...
- python多线程操作列表_详解Python多线程下的list
list 是 Python 常用的几个基本数据类型之一.正常情况下我们会对 list 有增删改查的操作,显然易见不会有任何问题.那么如果我们试着在多线程下操作list 会有问题吗? 多线程下的 lis ...
- python线程延时函数_详解Python 多线程 Timer定时器/延迟执行、Event事件
Timer继承子Thread类,是Thread的子类,也是线程类,具有线程的能力和特征.这个类用来定义多久执行一个函数. 它的实例是能够延迟执行目标函数的线程,在真正执行目标函数之前,都可以cance ...
- python 多线程和协程结合_如何让 python 处理速度翻倍?内含代码
阿里妹导读:作为在日常开发生产中非常实用的语言,有必要掌握一些python用法,比如爬虫.网络请求等场景,很是实用.但python是单线程的,如何提高python的处理速度,是一个很重要的问题,这个问 ...
- python 多线程和协程结合_一文讲透 “进程、线程、协程”
本文从操作系统原理出发结合代码实践讲解了以下内容: 什么是进程,线程和协程? 它们之间的关系是什么? 为什么说Python中的多线程是伪多线程? 不同的应用场景该如何选择技术方案? ... 什么是进程 ...
最新文章
- 降维后的高维特征的参数_高维超参数调整简介
- R语言构建仿真列联表并进行卡方检验(chisq.test):检验两个分类变量是否独立、输出期望的列联表
- 利用反射动态调用类成员C#
- FreeRTOS之列表和列表项
- java线程安全问题之静态变量、实例变量、局部变量
- 南通工学院计算机97级,2021年南通理工学院录取结果查询网址入口及录取结果公布时间...
- 使用matplotlib画图时不能同时打开太多张图
- 洛谷 P1754 球迷购票问题
- python程序调用函数的过程是什么_Python:函数定义和调用时都加*,有什么作用?...
- android中一个应用程序启动另外一个应用程序,并传递数据。
- 阿里云mysql导出表,mysql导出数据库表数据
- 抖音直播4种套路,让直播间人气快速破1000+
- 启动kafka过一会进程自动挂掉问题原因
- uni-app(Vue.js)创建运行微信小程序
- 计算机网络笔记1 计算机网络概述
- “国防七子”经费暴增,清华足足362亿元,甩第二名101亿 |全国高校2022预算大公开...
- Java核心类库之(网络编程:网络编程入门、UDP通信程序、TCP通信程序)
- 薰衣草的花语~~~~~~等待爱情
- 实战 | AdaBoost算法
- 强大视频分割软件:Boilsoft Video Splitter绿色便携版