最近一个月一直在理清爬虫多任务化的问题,结论是在 python 爬虫领域,实现多任务的正确姿势是单线程异步 IO 模型。
在写出同时能爬取多个链接的代码前,楼主必须先理解这个模型的原理:单线程异步 IO 模型的基础---- [事件循环+回调函数] 模型。
先说事件循环,它是一个系统,这个系统内由以下函数组成:
1.连接服务器的函数,
2.发送 GET 请求到服务器的函数,
3.接收并读取服务器响应的函数,
4.最后是解析响应内容用来获取数据的函数;
可以看到这几个函数基本就是我们写普通爬虫代码的一个流程:函数与函数之间都需要等待,也就是说只有函数 1 返回结果后才能执行函数 2 ,函数 2 返回结果后才能执行函数 3 。。。
那么,用事件循环来控制这些函数和写普通爬虫代码有什么不同呢?
答案就是,事件循环可以由程序员手动控制多个爬虫(任务),而不是像多线程那般把分配权交给操作系统随机分配。
当一号爬虫(任务)在执行函数 1 时,一执行完就立即返回(意思就是不等待最后获取值),并将控制权交还给事件循环,交给它之后,开始执行二号爬虫(任务);二号爬虫开始执行函数 1 ,同样,一执行完就立即返回,并将控制权交给事件循环;交给它之后,开始执行三号爬虫(任务),三号爬虫开始执行函数 1 ,同样,一执行完就立即返回。。。以此类推。。
问题来了,当一号爬虫(任务)的函数 1 处理完并返回值后该如何处理这个值?这个时候,回调函数就能派上用场了,回调函数起到通知的作用,告知循环系统在咱们这一号爬虫(任务)有个函数处理完了,要用它返回的结果来执行函数 2 。循环机制听到通知后,便开始执行一号爬虫(任务)的函数 2 。执行函数 2 和执行函数 1 的机制完全相同,也是一执行完就返回,并立即将分配权交给循环机制,这样让循环机制同时地、不停地处理二号、三号、四号。。。爬虫(任务)。
最后,直到一号爬虫获取最终想要爬取的数据,同时,二号、三号、四号。。。爬虫仍在同时工作,没有停止,然后二号爬虫也执行完了所有函数并得到数据,然后是三号、四号。。。
以上就是基于事件循环+回调函数的异步 IO 爬虫模型,虽然是单线程但是效率非常高,像 twisted , tornado 这些流行的异步 IO 库基本都是基于这个模型。但是这种模型也有很多弊端,最令人不爽的两个地方是, 1.调试起来非常恼火,根本看不到 traceback 。 2.一旦事件循环内的函数数量变多,代码逻辑也变的复杂。
So.python 3.4 在基于事件循环+回调函数模型的基础上利用生成器的特性,搞了一套改良版的异步 IO 模型,完美解决了以上两个问题。在 python3.5 进一步迭代,推出了 asyncio 库,再次优化了 python 异步 IO 性能。
目前我会写简单的基于事件循环+回调函数的异步 IO 爬虫,仍在理清基于生成器的异步 IO 模型,如果楼主要深入了解,请参考:
http://aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html ( python 之父写的异步爬虫教程)