异步爬虫

异步爬虫

以往写爬虫就是单进程单线程,假设要爬取100哥页面,就是一个循环挨个爬。但是要执行下面一条抓取,就需要等待网络IO请求执行完毕,所以效率就不高了。
一开始处理的数据不大,还意识不到,如果要爬取上万个页面,差距马上就能凸显了。
所以爬虫必须要并发执行,异步编程。
在python中并发编程,有三种途径:多进程,多线程和协程。当然这三者还可以组合使用,比如多进程+多线程。
由于存在GIL锁,所以python中多线程实际上是并发而不是并行,这就会影响效率,尤其是对CPU密集型的程序。
所以python的老兵可能会对你说:python下多线程是鸡肋,推荐使用多进程!
其实单单对于爬虫来说还有一种更有效的方法就是协程

协程

什么是协程

协程是一种用户级的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。

为什么用协程

目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。
不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。
而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。
因为协程是用户自己来编写调度逻辑的,对CPU来说,协程其实是单线程,所以CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程。
协程是一种用户态内的上下文切换技术

怎么用

python里面怎么使用协程?答案是使用gevent。
所以最推荐的方法,是多进程+协程(可以看作是每个进程里都是单线程,而这个单线程是协程化的)
多进程+协程下,避开了CPU切换的开销,又能把多个CPU充分利用起来,这种方式对于数据量较大的爬虫还有文件读写之类的效率提升是巨大的。

实际例子

#!/usr/bin/env python
# encoding: utf-8
"""异步爬虫"""
from gevent import monkey
monkey.patch_all()

import requests
from gevent.pool import Pool
import time
import logging

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s  -  %(message)s ', datefmt='[%Y-%m-%d %H:%M:%S]',
                    )

crawl_urls = ["http://www.baidu.com"] * 100


def run_asynchronous():
    pool = Pool(size=10)  # 最大协程数
    for i in range(len(crawl_urls)):
        pool.spawn(crawl, crawl_urls[i], i)
    pool.join()


def crawl(url, id):
    requests.get(url)
    # logging.info("asynchronous finish id :{}".format(id))


def run_synchronous():
    for i in range(len(crawl_urls)):
        requests.get(crawl_urls[i])
        # logging.info("synchronous finish id :{}".format(i))


if __name__ == "__main__":

    start = time.time()
    run_synchronous()
    end = time.time()
    logging.info("synchronous time: {}".format(end - start))

    start = time.time()
    run_asynchronous()
    end = time.time()
    logging.info("asynchronous time: {}".format((end - start)))


可以看到异步以后,爬取100次baidu首页也只要1秒不到。
并且同步的爬虫顺序是依次进行的

而异步爬虫,就是并发的执行

你可能感兴趣的:(python,爬虫)