利用协程asyncio爬取搜狗美女图片(一)——asyncio库的介绍和使用

版权声明:原创不易,如若转载,请注明出处! https://blog.csdn.net/MG1723054/article/details/81778460

上一节,我们通过分析ajax爬取搜狗美女图片,(链接https://blog.csdn.net/MG1723054/article/details/81735834)这样爬取的效率相对来说比较高,在文章的末尾我们使用进程池来提高效率,但是由于爬虫主要是密集型IO操作,利用进程对其提高时效率不高,(上节的代码中的time.sleep(1)若删除,两者的所耗时间几乎相同),对于密集型IO操作,我们可以使用线程或者协程来提高爬取效率。(注意,下面的代码均在python3.5版本下面运行,python3.6.5版本上面运行run_until_complete()该方法会出错。至少我这边是这样)

协程最大的优势就是具有极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。但是根据自己的理解目前,Python对协程的支持还非常有限,本节在基于上一节的基础上,利用协程中asyncio库来爬取搜狗美女图片。

工欲善其事必先利其器,我们先了解在利用asyncio模块的概念

event_loop事件循环:程序开启一个无限循环,程序员会把一些函数注册到时间循环,当事件发生时调用相应的协程函数

coroutine协程:协程对象,指一个使用async关键字定义的函数,他的调用不会立即执行函数,而是返回一个函数对象。协程使用对象需要注册到事件循环,由事件循环调用

task任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态

future:代表将来执行或者未执行的任务结果

async:定义一个协程   await:挂起阻塞的异步调用接口

一、创建协程

在def函数前加入async声明,就可以定义一个协程函数,一个协程不能直接调用直接运行,只能把协程加入到事件循环loop中,asyncio.get_event_loop方法可以创建一个事件循环,run_until_complete将协程注册到事件循环,并启动事件循环。

import asyncio
async def fun():
    print('hello world')
loop=asyncio.get_event_loop()
loop.run_until_complete(fun())
####运行结果:hello world

注意:协程对象不能直接运行,在注册事件循环的时候,其实run_until_complete方法将协程成为一个任务task对象。task对象是Futrue类的子类。保存了协程运行后的状态,用于未来获取协程的结果

二、绑定回调

import asyncio
async def fun():
    print('hello world')
    return 'hello'
def callback(future):
    print('callback:',future.result())
    
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(fun())
task.add_done_callback(callback)
loop.run_until_complete(task)
###运行结果:hello world
   #####    callback: hello

创建task后,task在加入事件循环之前是pending状态,因为fun()没有耗时的阻塞操作,task很快执行完毕,asyncio.ensure_future和loop.creat_task均可以建立一个task,run_until_complete的参数是一个future对象。当传入一个协程,其内部会自动封装成task,task是一个Future子类,在task执行完毕的时候可以获取执行的结果,回调的最后一个参数是future对象,通过add_done_callback()调用。实际上可以不使用回调,直接在task运行完成完毕后直接可以调用resul()方法,代码如下:

import asyncio
async def fun():
    print('hello world')
    return 'hello'
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(fun())
loop.run_until_complete(task)
print(task.result())
###运行结果与上面的一样

三、阻塞

使用asyncio可以定义协程对象,使用await可以针对耗时的操作进行挂起,就像生成器yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他协程也挂起或执行完毕,再进行下一个协程的执行。例子在第五条给出

四、多任务协程

上面的很多例子我们只执行了一次请求,如果我们执行多次请求如何解决,我们可以创建一个task列表,再使用asyncio中的wait方法执行即可

"""
Created on Fri Jul 27 18:31:43 2018

@author: NJUer
"""
import asyncio
import requests
import time
start = time.time()
 
async def image_download(item):
    response = requests.get(item['imgurl'])
    file='C:\\Users\\FangWei\\Desktop\\实例\\'+str(item['title'])+'.jpg'
    with open(file,'wb') as f:
        f.write(response.content)
items=[{'imgurl':'https://img02.sogoucdn.com/app/a/100520020/80de1a7c1181bb60780bee710aa9b2cc','title':12659835},  {'imgurl':'https://img04.sogoucdn.com/app/a/100520020/3186d4cc8dc54b41e62207a99341a830','title':1234},]
start=time.time()
tasks=[asyncio.ensure_future(image_download(item)) for item in items ]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end=time.time()
print('Cost time:', end - start)
#####Cost time: 1.0180580615997314

五、协程的实现

上面我们介绍了怎么建立一个协程以及协程的运行,但是到这里还不是真正的实现了协程,因为并没有因为遇到等待将协程挂起的状态,而await可以将协程挂起,这样我们可以将上面的程序改变,这样就可以实现挂起操作了,但是我们可以发现,该操作程序的总用时没有改变。

"""
Created on Fri Jul 27 18:31:43 2018

@author: NJUer
"""
import asyncio
import requests
import time
start = time.time()
 
async def image_download(item):
    response =await get(item['imgurl'])  #####注意await后面必须是原生coroutine或者Future子类
    file='C:\\Users\\FangWei\\Desktop\\实例\\'+str(item['title'])+'.jpg'
    with open(file,'wb') as f:
        f.write(response.content)
async def get(url):
    return requests.get(url)
items=[{'imgurl':'https://img02.sogoucdn.com/app/a/100520020/80de1a7c1181bb60780bee710aa9b2cc','title':12659835},  {'imgurl':'https://img04.sogoucdn.com/app/a/100520020/3186d4cc8dc54b41e62207a99341a830','title':1234},]
start=time.time()
tasks=[asyncio.ensure_future(image_download(item)) for item in items ]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end=time.time()
print('Cost time:', end - start)
###Cost time: 1.0280702590942383

造成该原因是我们仅仅将IO操作的代码分装到async修饰器中,并未进行异步进行,所以必须使用支持异步操作的请求方式才可以实现真正的异步,aiohttp是专门为实现单线程并发IO操作的库

我们将上面的代码改变一下,利用aiohttp

"""
Created on Fri Jul 27 18:31:43 2018

@author: NJUer
"""
import asyncio,aiohttp,time
async def  image_download(item):
      file='C:\\Users\\FangWei\\Desktop\\实例\\'+str(item['title'])+'.jpg'
      async with aiohttp.ClientSession() as session:
          async with session.get(item['imgurl']) as resp:
              imgcode=await resp.read()
      with open(file,'wb')as f:
          f.write(imgcode)
items=[{'imgurl':'https://img02.sogoucdn.com/app/a/100520020/80de1a7c1181bb60780bee710aa9b2cc','title':12659835},  {'imgurl':'https://img04.sogoucdn.com/app/a/100520020/3186d4cc8dc54b41e62207a99341a830','title':1234},]
start=time.time()
tasks=[asyncio.ensure_future(image_download(item)) for item in items ]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end=time.time()
print('Cost time:', end - start)
####Cost time: 0.4710268974304199

对于爬虫项目实战,我们在其第二个部分再说。

 原创不易,如若转载,请注明出处和作者,谢谢!

猜你喜欢

转载自blog.csdn.net/MG1723054/article/details/81778460