点击上方“编程派”,选择设为“设为星标”

优质文章,第一时间送达!

一、前言

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

二、同步

首先我们写一个简化的爬虫,对各个功能细分,有意识进行函数式编程。下面代码的目的是访问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相关及生物信息学、网络爬虫、数据分析、可视化相关的文章。

回复下方「关键词」,获取优质资源回复关键词「 pybook03」,立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版
回复关键词「入门资料」,立即获取主页君整理的 10 本 Python 入门书的电子版
回复关键词「m」,立即获取Python精选优质文章合集
回复关键词「book 数字」,将数字替换成 0 及以上数字,有惊喜好礼哦~
题图:pexels,CC0 授权。好文章,我在看❤️

看几段爬虫代码,详解Python多线程、多进程、协程相关推荐

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

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

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

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

  3. 代码详解Python多线程、多进程、协程

    点击上方"早起Python",关注并星标公众号 和我一起玩Python 一.前言 很多时候我们写了一个爬虫,实现了需求后会发现了很多值得改进的地方,其中很重要的一点就是爬取速度.本 ...

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. 降维后的高维特征的参数_高维超参数调整简介
  2. R语言构建仿真列联表并进行卡方检验(chisq.test):检验两个分类变量是否独立、输出期望的列联表
  3. 利用反射动态调用类成员C#
  4. FreeRTOS之列表和列表项
  5. java线程安全问题之静态变量、实例变量、局部变量
  6. 南通工学院计算机97级,2021年南通理工学院录取结果查询网址入口及录取结果公布时间...
  7. 使用matplotlib画图时不能同时打开太多张图
  8. 洛谷 P1754 球迷购票问题
  9. python程序调用函数的过程是什么_Python:函数定义和调用时都加*,有什么作用?...
  10. android中一个应用程序启动另外一个应用程序,并传递数据。
  11. 阿里云mysql导出表,mysql导出数据库表数据
  12. 抖音直播4种套路,让直播间人气快速破1000+
  13. 启动kafka过一会进程自动挂掉问题原因
  14. uni-app(Vue.js)创建运行微信小程序
  15. 计算机网络笔记1 计算机网络概述
  16. “国防七子”经费暴增,清华足足362亿元,甩第二名101亿 |全国高校2022预算大公开...
  17. Java核心类库之(网络编程:网络编程入门、UDP通信程序、TCP通信程序)
  18. 薰衣草的花语~~~~~~等待爱情
  19. 实战 | AdaBoost算法
  20. 强大视频分割软件:Boilsoft Video Splitter绿色便携版

热门文章

  1. 软件设计模式及体系结构之工厂方法模式
  2. 20年的目标检测大综述(章节2)
  3. LIMIT OFFSET 用法
  4. docker下载镜像慢怎么办?daocloud加速器来帮你
  5. Keepalived脑裂监控
  6. Java 设计模式学习推荐
  7. 财务会计基础(四)会计账户
  8. 【Quartus II 17.0 VWF仿真设置】
  9. 李开复:百亿美元独角兽CEO的共同特点就是偏执、强大、think big
  10. BestCoder Round #29 1003 (hdu 5172) GTY's gay friends [线段树 判不同 预处理 好题]