asyncio+aiohttp---异步I/O实现request爬虫

异步

所谓异步IO,就是你发起得到IO操作时,无需一直等待其结束再进行其他事情,能够利用等待的时间继续做其他事,当发起的IO操作结束时,会得到通知。

Asyncio

asyncio 是用来编写 并发 代码的库,使用 async/await 语法。

asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。

asyncio 往往是构建 IO 密集型和高层级 结构化 网络代码的最佳选择。

协程

协程 (coroutine) 几乎是 Python 里最为复杂的特性之一了

我们可以使用协程来实现异步操作

等待一个 future 结束
等待另一个协程(产生一个结果,或引发一个异常)产生一个结果给正在等它的协程
引发一个异常给正在等它的协程

简单的同步、异步处理IO操作比较

同步
在这里插入图片描述
五个任务,耗时五秒

异步
在这里插入图片描述
五个任务,同时完成

编程模式

模块中有一个消息循环,我们从asyncio模块中直接获取一个Event Loop的引用,然后把需要执行的协程放到Event Loop中执行,就实现了异步。

关键字说明

  1. event_loop 事件循环:程序开启一个无线循环,把一些函数注册到事件循环中,当满足条件时,调用相应的协程函数。

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

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

  4. future 代表将来执行或者没有执行的任务结果,它和task上没有本质的区别。

  5. async/await async定义一个协程,await用于挂起堵塞异步调用接口

定义一个协程

import asyncio
#通过async关键字定义了一个协程,协程不能直接运行,需要将协程加入到事件循环中
async def run(x):
    print('waiting: %d'%x)

#得到一个协程对象,这个时候run()函数没有执行
coroutine = run(2)
print(coroutine)

#创建一个循环事件(注意:真实情况是在asyncio模块中获取一个引用)
loop = asyncio.get_event_loop()
#将协程对象加入到事件循环
loop.run_until_complete(coroutine)

创建一个Task

import asyncio
async def run(x):
    print('waiting: %d'%x)

coroutine = run(2)
loop = asyncio.get_event_loop()
#将协程对象加入到事件循环,协程对象不能直接运行,在注册事件循环的时候
#其实是run_until_complete方法将协程对象包装成了一个任务对象
#即Task对象,是Future类的子类,保存了协程运行后的状态,用于未来获取协程的结果
# loop.run_until_complete(coroutine)

#创建任务
task = asyncio.ensure_future(coroutine)
# task = loop.create_task(coroutine)
print(task)
loop.run_until_complete(task)
print(task)

以上只是单个任务,所以还没到异步,先知道使用的方法。

绑定回调

import asyncio
async def run(x):
    print('waiting: %d'%x)
    return "done after %ds"%x

#定义一个回调函数,参数为future,任务对象
def callback(future):
    print("callback:",future.result())

coroutine = run(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
#给任务添加回调,在任务结束后调用函数
task.add_done_callback(callback)
loop.run_until_complete(task)

回调就是为了得到函数的返回值,那就能搞事情了

await

import asyncio
"""
async可以定义协程对象,使用await可以针对耗时的操作进行挂起
就像生成器里的yiled一样,函数交出控制权,协程遇到await,
事件循环将会挂起该协程,执行别的协程。
直到其他协程也挂起或者执行完毕,再进行下一个协程执行
"""
async def run(x):
    print('waiting: %d' % x)
    #调用了一个耗时的IO操作
    #asyncio.sleep(x)另一个协程对象
    await asyncio.sleep(x)
    return "done after %ds"%x

def callback(future):
    print("callback:", future.result())

coroutine = run(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
task.add_done_callback(callback)
loop.run_until_complete(task)

await就理解成yiled,将函数挂起。

接下来将开始实现异步请求

会涉及到并发和并行的思想,这里就不再解释了。

咱先自己写一个服务器代码

from flask import Flask
import time

app = Flask(__name__)

@app.route('/')
def index():
    time.sleep(3)
    return 'Hello!'

if __name__ == '__main__':
    app.run(threaded=True)

启动之后,每次访问 http://127.0.0.1:5000/都需要等三秒才能出Hello!

使用aiohttp

没装就装一下,报错的话就自己google。、

aiohttp 是一个支持异步请求的库,利用它和 asyncio 配合我们可以非常方便地实现异步请求操作。

添加headers以及proxies的方法与requests的使用方法一样,session也支持post请求,传值得方法与requests一致

import asyncio
import aiohttp
import time

start = time.time()

async def get(url):
    session = aiohttp.ClientSession()
    response = await session.get(url)
    result = await response.text()
    session.close()
    return result

async def request():
    url = 'http://127.0.0.1:5000'
    print('Waiting for', url)
    result = await get(url)
    print('Get response from', url, 'Result:', result)

tasks = [asyncio.ensure_future(request()) for _ in range(5)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

end = time.time()
print('Cost time:', end - start)

关于函数之间的调用

上面已经提到过await和yiled的用法类似,正常使用await将函数挂起即可
在这里插入图片描述

总结

原理就是上面铺垫的哪些知识,现在只是将一个url换成了多个url,再使用了一个新的库aiohttp。

通过将任务放入事件循环中,任务全部挂起,最后几乎同时完成,本应该耗时15秒,实际结果3秒

当然,你也可以试试100个,基本也是3秒,1000个你自己试一下。

发布了65 篇原创文章 · 获赞 41 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_43870646/article/details/93309984