如何提高Python爬取效率?
如何提高Python爬取效率?
为提高Python爬虫爬取数据的效率,我们较先想到的就是使用多线程或者多进程。但是从性能和计算机资源利用率上来讲,通过协程实现的异步爬取又似乎更加合适。
既然提到了协程,就不得不说一下进程和线程了。首先我们来简单了解一下python中进程、线程、协程。
01 进程、线程、协程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
如下图,较大的黑框可以看作一个运行着的程序,这个程序中包括了三个同时运行的进程,每个进程都是独立的。在每个进程中又包括了三个线程,从而构成了整个程序。
线程,又称为轻量级进程(Lightweight Process,LWP),是程序执行流的较小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
协程,又称微线程,纤程。与线程的选择式调度不同,它是协作式调度。如下图所示,协程较大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
协程的优点是无需线程上下文切换的开销,协程避免了无意义的调度,拥有极高的执行效率。无需原子操作锁定及同步的开销,它是线程安全的。方便切换控制流,简化编程模型。协程的缺点就是无法利用cpu多核。当然简单的解决方法就是多进程+协程,既充分利用多核,又充分发挥协程的gao效率,可获得极高的性能。
02 asyncio实现异步操作
网络模型有很多种,为了实现高并发也有很多方案,多线程,多进程。无论多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态。使用协程可以实现gao效的并发任务。能够实现协程的有asyncio,tornado和gevent,下面将介绍asyncio的使用。
从 Python 3.4 开始,Python 中加入了协程的概念,但这个版本的协程还是以生成器对象为基础的,在 Python 3.5 则增加了 async/await,使得协程的实现更加方便。
首先我们需要了解下面几个概念:
event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。
coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。
定义一个协程
定义协程有两种方式,一种是使用asyncio.coroutine修饰器,需要使用yield from来挂起阻塞方法的执行,另一种是 Python 3.5 增加的,通过async定义一个协程,await用于挂起阻塞的异步调用接口。
上面两种方法基本上一样。第二种使用方法比较方便,所以推荐使用第二种方法。
通过async关键字来定义一个协程。协程不能直接运行,需要把协程加入到事件循环(loop),由loop在适当的时候调用协程。asyncio.get_event_loop方法可以创建一个事件循环,然后使用run_until_complete将协程注册到事件循环,并启动事件循环。因为本例只有一个协程,于是可以看见如下输出:
创建一个task
协程对象是不能直接运行,在注册事件循环的时候,其实是用run_until_complete方法将协程对象包装成为了一个任务(task)对象。task对象是Future类的子类。保存了协程运行后的状态,用于未来获取协程的结果。
可以看到输出结果为:
创建task后,task在加入事件循环之前是pending状态,因为do_some_work中没有耗时的阻塞操作,task很快就执行完毕了。后面打印的finished状态。
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task。
阻塞和await
使用async可以定义协程对象,使用await可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行。
耗时的操作一般是一些IO操作,例如网络请求、文件读取、数据存储等。我们使用asyncio.sleep函数来模拟IO操作。协程的目的也是让这些IO操作异步化。
更多培训课程,学习资讯,课程优惠等学校信息,请进入 北京丰台区Web培训北京石景山区Python培训北京海淀区Linux云计算培训 网站详细了解,免费咨询电话:400-998-6158












