spider 11-min.png

爬取 www.mzitu.com 全站图片,截至目前共4564个图集,13.7万多张美女图片,使用 asyncio 和 aiohttp 实现的异步版本只需要不到2小时就能爬取完成。按日期创建图集目录,保存更合理。控制台只显示下载的进度条,详细信息保存在日志文件中。支持异常处理,不会终止爬虫程序。失败的请求,下次再执行爬虫程序时会自动下载

spider 09-min.jpg

支持HTTP协议的异步网络I/O库:aiohttp,我们的爬虫需要使用该库的Client功能。需要结合asyncio一起使用,经过测试,单线程的异步编程比多线程版本的性能还要好一些,毕竟没有创建线程的开销和线程间上下文切换。另外,如果你下载的是视频等大文件,此时,将网络数据保存到本地磁盘的这个过程,本身是阻塞的,所以它会阻塞事件循环。asyncio不支持磁盘I/O异步,需要使用aiofiles,其实它背后也只是创建一个线程池而已。后续爬虫实战时,动辄下载数十万个文件,为保持控制台清爽,需要使用 progressbar2 显示进度条,而详细信息将保存到日志文件中

spider 08-min.jpg

asyncio 模块于Python 3.4添加到标准库中,它在单线程中使用事件循环来驱动协程从而实现并发。对事件循环来说,调用回调与在暂停的协程上调用 .send() 方法效果差不多。各个暂停的协程是要消耗内存,但是比线程消耗的内存数量级小。而且,协程能避免可怕的"回调地狱"。使用 asyncio 包时,我们编写的协程被包装成Task对象(相当于调用方),并且在我们编写的协程中,会通过调用 await 或 yield from 来使用由 asyncio 模块或第三方库(如aiohttp)所提供的协程(即委派生成器),而生成器最终把职责委托给Future对象,这种处理方式相当于架起了管道,让 asyncio 事件循环(通过我们编写的协程)驱动执行低层异步 I/O 操作的库函数

spider 07-min.jpg

生成器可以作为协程(coroutine)使用,称为 "基于生成器的协程"。协程和生成器类似,都是定义体中包含 yield 关键字的函数。但它们也有本质区别,生成器用于 生成 供迭代的数据,next()方法只允许调用方从生成器中获取数据; 而协程与迭代无关,协程是数据的消费者,调用方会把数据推送给协程。PEP 342给生成器增加了 send() 方法,允许调用方和协程之间双向交换数据。PEP 380允许生成器中可以return返回值,并新增了 yield from 语法结构,打开了调用方和子协程的双向通道。PEP 492新增了async和await关键字,实现了 "原生协程",以便于跟生成器进行区分。协程不等于异步编程,所以将在下一篇博客中介绍 asyncio 模块,它提供了事件循环,利用Coroutines、Tasks、Futures一起才能实现异步I/O(底层基于selectors模块,请回头查看本爬虫系列的第一篇博客I/O models中的I/O multiplexing)