如何让你写的爬虫速度像坐火箭同样快【并发请求】

开坑个新系列,主要面向新手,老司机能够忽略。python

这个系列内的文章将会让你知道如何作到让你写的爬虫在运行的时候速度能像火箭同样快!服务器

不少初学爬虫的朋友对于这方面的知识彷佛是空白的,甚至还有一些在爬虫岗位上工做了一两年的人也搞不清楚在不使用爬虫框架的状况下,如何写出一个速度足够快的爬虫,而网上的文章大可能是基于多进程/Gevent来写的,代码看起来就极其复杂,甚至有些人抄来抄去连多进程和多线程没搞清楚,若是是一个想学习这方面知识的人看到了这样的文章,多半会一脸懵逼。cookie

综上所述,为了让关注我公众号的新手朋友们能快速掌握这些技巧,这个系列就这样诞生了~网络

话很少说,咱们正式开始。在提高爬虫的速度这方面,最基础、最有效、最直接的操做是什么呢?没错,就是并发请求,若是你的爬虫整个逻辑是顺序执行的,请求的时候永远不会并发,那么你就会遇到像他这样的状况:《小白写了个壁纸的爬虫,能跑起来,可是感受很慢,不知道怎么回事,请大佬指点》session

上面这是我昨天刷V2的时候看到的一个帖子,楼主的代码内容简单归纳一下就彻底是顺序执行的,每下载一个图片都须要等待当前这个图片下载完了才能继续下载下一个,这样子作固然会很是慢了!这篇文章就拿他的代码做为样例,在原来的基础上进行一些调整,从而让他写的这个爬虫的运行速度能从龟爬变成像坐火箭同样快!多线程


首先,咱们须要知道什么是并发,这里的并发指的是“并行发送请求”,意思就是一次性发出多个请求,从而达到节省时间的效果!那么并发和不并发的区别在哪呢?简单来讲就是这样子的:并发

把爬虫比喻成工人,在不并发的状况下,一个工人一次只能作一件事情,因此必需要下载完一个图片才能继续下载下一个。框架

顺序执行的状况

而在并发的状况下,就有不少个工人一块儿在干活,每一个工人都被分配了一件事情作,因此能够同时下载多个图片,速度天然就快了不少。异步

并发的状况

固然,上面说的这个例子只是从一个宏观的角度上来看并发,实际在作的时候要让你的爬虫能并发请求的方式是分为多线程、多进程、协程三种的,**并非每一种方式在运行时的效果都像上面说的这样,这里先不作深刻探讨,由于这不是本文的重点。**咱们如今只须要知道,只要能让爬虫并发请求,就能同时下载多个图片,让速度快得飞起,这样就够了。async


那么咱们要用上面说的三种方式里的哪种来实现并发请求呢?这还用问吗?固然是选择代码最简单、改动最小,而且最容易看懂的协程啊!在Python3.4以后Python就引入了一个叫作asyncio的库,原生支持了异步IO,而在3.5以后Python又支持了asyncawait这两个语法,使得写异步代码能够像写同步代码同样简单易读。

刚刚又提到了两个词,同步和异步,这两个词的含义其实就跟上面的并发差很少,同步代码就是顺序执行的,而异步则不是,这里一样不作深刻探讨,先知道有这么个东西就好了。

看到这里确定会有人开始有疑问了,虽然前面说咱们要用协程来实现并发请求,可是后面说的倒是什么Python支持原生异步,那么这个异步跟协程的关系又是什么呢?

其实很简单,协程可让你写异步代码的时候能像写同步代码同样简单,在Python3中写协程代码的核心语法就是asyncawait这两个,举个简单的例子吧:

def func():
    print(1)
    time.sleep(10)
    print(2)
复制代码

这是一段普通的函数,它属于同步代码,里面的time.sleep是普通函数,也属于同步代码。

async def func():  # 调用协程函数的那个函数也须要是一个协程函数
	print(1)
	await asyncio.sleep(10)  # 调用协程函数的时候要在前面加await
	print(2)
复制代码

而这是一个协程函数,它属于异步代码,里面的asyncio.sleep是协程函数,也属于异步代码。

它们的区别显而易见,用协程来写异步代码,除了须要换成异步的库之外,就只是多了个asyncawait而已,是否是很是简单?


那么咱们在了解了怎么写协程代码以后,就能开始优化那段慢成龟速的代码了吗?答案是否认的,那段代码中使用了requests库进行网络请求,而requests是一个同步库,不能在异步环境下使用;一样,文件操做用的openfile.write也是同步的,也不能在异步环境下使用。

因此在开始以前咱们还须要了解两个库,分别是aiohttp和aiofiles,aiohttp是一个异步网络请求库,而aiofiles是一个异步文件操做库。(aiofiles是基于线程池实现的,并非真正的原生异步,但问题不大,不影响使用)

切记,异步代码不能与同步代码混用,不然若是同步代码耗时过长,异步代码就会被阻塞,失去异步的效果。而网络请求和文件操做是整个流程中最耗时的部分,因此咱们必须使用异步的库来进行操做!不然就白搞了!

好了,先来看看aiohttp的用法吧,官方文档上的示例大体以下:

async with aiohttp.ClientSession() as session:
    async with session.get(url) as resp:
        result = await resp.text()
复制代码

是否是以为很麻烦,不像requests库那么方便?还以为两层async with很丑?有没有办法让它像requests库同样方便呢?

答案是有的,有一个叫做aiohttp-requests的库,它能让上面的这段代码变成这样:

resp = await requests.get(url)
result = await resp.text()
复制代码

清爽多了对吧?咱们等下就用它了!记得装这个库的前提是要先装aiohttp哦!

而后咱们来看看aiofiles的用法,官方文档上的示例以下:

async with aiofiles.open('filename', mode='r') as f:
    contents = await f.read()
print(contents)
复制代码

嗯,这个用起来就和用同步代码操做文件差很少了,没啥可挑剔的,直接用就完事了。

提示:aiohttp-requests默认是建立并使用了session的,对于一些须要不保留Cookie进行请求的场景须要本身实例化一个Requests类,并指定cookie_jar为aiohttp.DummyCookieJar


了解完了要用的库以后咱们就能够开始对贴子中的代码进行魔改了,若是你用的不是Python3.5以上版本的话须要先准备一下环境。除了版本号大于等于3.5的Python之外,你还须要安装如下几个库:

  • aiohttp(异步网络请求库)
  • aiohttp-requests(让aiohttp用起来更方便的库)
  • aiofiles(异步文件操做库)
  • pillow(其实就是PIL库,代码中的图片操做有用到)

执行一下pip install aiohttp aiohttp-requests aiofiles pillow一次性装完,若是存在多个不一样版本的Python环境记得区分好。


而后咱们打开编辑器,开始改代码,首先调整一下导包的部分,将里面的requests替换成aiohttp-requests,像这样:

而后搜索一下requests,看看哪些地方用到了它。

接着把全部搜到的部分都给改为异步请求的。

同时不要忘了将全部调用过requests.get的函数都变成协程函数。

而后咱们把文件操做的部分也换成异步的,使用aiofiles.open代替open

最主要的部分都换好了,接着咱们将原先在if __name__ == '__main__':下的代码移到一个新写的协程函数run中,而且将调用前面协程函数的部分都加上await

再导入一下asyncio库,而后在if __name__ == '__main__':下写出这样的代码:

上面这个是Python3.7以后才能用的写法,低于Python3.7要这样写:

如今咱们就能够运行一下看看修改后的代码能不能跑通了。

这里报了个错,从错误堆栈中能够看出问题是出在response = await requests.get(url=url, headers=headers)这里的,缘由是self.session._request方法没有key为url的参数。这个问题很好解决,只须要将url=url变成url就行了(原本也就不必这么指定参数写)。将代码中全部用到requests.get而且存在url=url这种写法的都作一下调整:

调整完以后再运行一次就正常了,效果和原先的代码相同。

注意!仅仅是这样并不会让速度发生很大的变化!咱们最后还须要将这一堆代码中最耗时且是顺序执行、没有并发请求的部分单独放到一个协程函数中,而且用asyncio.gather来并发调用(因为本来的逻辑较为混乱,这里除了并发请求之外还进行了一些其余的微调,主要是计数和文件路径的部分,可有可无)。

运行一下看看效果,刚运行起来一瞬间就刷了一排的下载完成,跟修改以前比起来简直是天差地别。

这就是并发请求的威力!咱们仅仅是对他本来的代码进行了一些微调,把最耗时的下载图片部分简单粗暴地使用asyncio.gather并发执行了一下,速度就从龟爬变成了像坐火箭同样快!(其实代码中还有不少能够优化的点,这里就不一一拿出来说了)


最后给你们提个醒:

虽然并发请求很是牛逼,可让你的爬虫变得飞快,但它也不是不存在任何问题的!

若是你的并发请求数量过大(又称并发数太高),你的爬虫就至关因而在对他人的服务器进行Dos攻击(拒绝服务攻击)了!

举个例子,你在爬一个小网站的时候为了本身爬的速度更快,对并发请求的数量毫无限制,使得你的爬虫一次性发出了几百、上千个请求,但通常的小网站根本扛不住这么高的并发!几乎会在一瞬间就被你的爬虫给打爆掉!试想一下,若是你是站长,看到这样的情形你会怎么想?

若是你不能理解这个例子所产生的效果是什么样的,能够本身搭建一个Web服务,只放一个简单的页面,而后开个几百并发去请求这个页面,这样你就能切身地体会到别人是什么感觉了。

因此记住,必定要合理控制并发请求的数量,不要对对方网站形成过大的压力!你给别人留活路,别人才会给你留活路!

最后再留个小做业吧,如何对这个修改后的代码增长一道并发数的限制?在留言区给出你的答案。(提示:可经过搜索引擎查找【aiohttp并发链接数限制】和【python 列表切割】相关的内容)


这个时代各类东西变化太快,而网络上的垃圾信息又不少,你须要有一个良好的知识获取渠道,不少时候早就是一种优点,还不赶忙关注个人公众号并置顶/星标一波~

发送消息“爬虫速度提高之并发请求”到个人公众号【小周码字】便可得到本文代码下载地址~

相关文章
相关标签/搜索