使用 gevent 实现高并发爬虫
现在给定这么一个场景,有一千个 url 需要采集,请大家思考下,如何能高效完成采集任务?在上一节课中,我们学到了 requests 这个强大的第三方库,有的同学会说,我们直接遍历采集就好了,那么会写出如下代码
经过测试得到耗时 390 秒,可见单个请求去跑效率是很低的,那么我们就要考虑多请求一起跑。 想让计算机实现并发,那就要引入多进程,多线程,协程的概念了,我简单介绍下这三种
多进程
多进程是指在一个应用程序中同时执行多个独立的进程,每个进程有自己独立的内存空间,相互之间完全独立,不会影响彼此。Python 的 multiprocessing 模块提供了创建和管理进程的功能,可以使用 Process 类来创建进程,使用 Queue 或 Pipe 实现进程间通信。
优点:
能够充分利用多核 CPU,适用于 CPU 密集型任务。
进程之间相对独立:每个进程都有自己的内存空间,避免了多个线程之间的资源竞争问题。
可以充分利用操作系统的调度算法:操作系统可以在不同进程之间进行调度,提高了并发性。
缺点:
进程之间切换开销较大,进程间通信相对复杂。
进程间通信比较复杂:在进程之间进行通信需要使用特殊的机制,如队列、管道等。
多线程
多线程是指在同一进程中执行多个线程,共享进程的内存空间,但每个线程有自己独立的执行流程。 Python 的 threading 模块提供了创建和管理线程的功能,可以使用 Thread 类来创建线程,使用 Lock 或 Semaphore 实现线程间同步。
优点:
相比多进程,线程之间切换开销较小,适用于 IO 密集型任务。
数据共享更方便:线程之间可以直接共享内存,可以方便地进行数据传递和共享。
线程间通信较简单:线程之间可以直接通过共享内存进行通信。
缺点:
由于 GIL(Global Interpreter Lock)的存在,Python 中的多线程并不能实现真正的并行,只能在单个 CPU 核心上执行。
PS. 对于爬虫工程师来说,最简单的选用方法就是,cpu 计算密集型就选用多进程,io 密集型,如网络请求等就选多线程
协程
协程是一种轻量级的线程,可以在同一线程内实现多个任务之间的切换,而不需要进行线程上下文切换。 Python 3.5 引入了 asyncio 模块,提供了原生的协程支持,使用 async 和 await 关键字定义协程。
优点:
协程可以有效地提高 IO 密集型任务的并发性能,同时减少了线程和进程的资源消耗。
缺点:
由于需要显式地在代码中标记异步操作点,对现有代码的改造较大。
从以上来看,对于爬虫来说,多进程过于重,多线程和协程更适合爬虫,下边分别看下这两种的区别
Demo
多线程
在不同的线程数下得到以下数据
协程
协程耗时 20 秒
由上可见,线程池扩大以后消耗的时间和协程几乎相同,但是在实际的开发中,逻辑往往比这复杂,会涉及到通信等问题,多线程编程需要考虑线程间的同步和竞态条件,需要使用锁、信号量等同步机制来避免多线程之间的冲突,会让代码看起来臃肿,而协程用 async 和 await 关键字实现异步操作,写起来更像同步代码,不需要显式的处理线程同步问题,对开发者更友好。
有的同学会想,async 和 await 还是麻烦,有没有更简单的实现方法?当然是有的,那就是 gevent
gevent
介绍:
gevent 是一个基于协程的 Python 网络库,它使用 greenlet 在 libev 或 libuv 事件循环之上提供高级同步 API。
简单来说,可以理解成一个协程框架,让我们快速开发出协程代码
由于是第三方库,需要使用命令安装:pip install gevent
Demo
下边来看代码
是不是看起来简洁多了,这也是 gevent 的一个优点,侵入性小,可以将历史代码很容易的改成协程,实现异步运行。
相信你还对 monkey.patch_all(ssl=False)感到好奇, 这是一个猴子补丁,补丁顾名思义是有修补的作用,这个也不例外,可以替换网络请求库的部分行为,从而支持阻塞式请求,可以试下,如果不加这个补丁,程序耗时还是和单个请求一样,注意 ssl=False 是不对 ssl 打补丁,解决报 requests 超过最大递归深度的问题。
带宽问题
在生产环境中,往往一台服务器上有多个任务,所以我们不能让自己的代码把带宽都占满,那会导致线上事故,所以我们需要对并发需要限制,在 gevevt 中,可以创建一个信号量,限制同时进行的 greenlet 数量,比如我想同时最多 5 个请求在跑,则可以在代码中加上:semaphore = Semaphore(5),再去运行代码就能防止带宽被占满了
作者:炼数成金
链接:https://juejin.cn/post/7372396174249426982
评论