一、概述
单线程+异步协程是在爬虫中最推荐使用一种提高效率的一种方法。
他相较于开启多线程的方法来说,受量级影响小。多线程或多进程的方式在任务量较小的情况下,可以达到异步提高效率的效果,若是任务过多,效率提高不明显
二、协程相关关键字
- event_loop:事件循环,相当于一个无限循环,将函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。程序是从头到尾顺序执行,当在编写异步程序时,必然其中有部分程序的运行耗时是比较久的,需要先让出当前程序的控制权,让其在背后运行,让另一部分的程序先运行起来。当背后运行的程序完成后,也需要及时通知主程序已经完成任务可以进行下一步操作,但这个过程所需的时间是不确定的,需要主程序不断的监听状态,一旦收到了任务完成的消息,就开始进行下一步。loop就是这个持续不断的监视器。
- coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
- task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
- future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
- 另外我们还需要了解 async/await 关键字,它是从 Python 3.5 才出现的,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。
三、使用
1.创建一个协程的基础方法
import asyncio
async def hello(name):
print('hello to :',name)
#获取了一个协程对象
c = hello('bobo')
#创建一个事件循环对象
loop = asyncio.get_event_loop()
#将协程对象注册到事件循环中,然后启动事件循环对象
loop.run_until_complete(c)
2.task的使用
import asyncio
async def hello(name):
print('hello to :',name)
c = hello('bobo')
loop = asyncio.get_event_loop()
#就协程进行进一步的封装,封装到了task对象中
task = loop.create_task(c)
print(task)
loop.run_until_complete(task)
print(task)
>>>
<Task pending coro=<hello() running at <ipython-input-15-250865fd4d0b>:3>>
hello to : bobo
<Task finished coro=<hello() done, defined at <ipython-input-15-250865fd4d0b>:3> result=None>
3.future的使用
只是与task的封装命令不同
import asyncio
async def hello(name):
print('hello to :',name)
c = hello('bobo')
task = asyncio.ensure_future(c)
loop.run_until_complete(task)
4.绑定回调函数
def callback(task):
print('i am callback:',task.result())
import asyncio
async def hello(name):
print('hello to :',name)
return name
c = hello('bobo')
task = asyncio.ensure_future(c)
#给任务对象绑定一个回调函数
task.add_done_callback(callback)
loop.run_until_complete(task)
四、多任务的异步协程
aiohttp这一个异步的模块。
在协程中遇到阻塞操作不会自动挂起,而是等待,所以在写到阻塞的操纵时,我们要将这个阻塞挂起,在代码前加入我们上面提到的await方法
1.一个简单的多任务异步协程任务
import asyncio
async def request(url):
print('正在下载:',url)
await asyncio.sleep(2)
print('下载成功:',url)
urls = [
'www.baidu.com',
'www.taobao.com',
'www.sogou.com'
]
start = time.time()
loop = asyncio.get_event_loop()
tasks = [] #任务列表,放置多个任务对象
for url in urls:
c = request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
#将多个任务对象对应的列表注册到事件循环中
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时:',time.time()-start)
2.使用aiohttp模块异步实现爬虫
import aiohttp
import asyncio
async def get_page(url):
async with aiohttp.ClientSession() as session:
async with await session.get(url=url) as response:
page_text = await response.text() #read() json()
print(page_text)
start = time.time()
urls = [
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom'
]
tasks = []
loop = asyncio.get_event_loop()
for url in urls:
c = get_page(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时:',time.time()-start)
3.回调函数
多任务中也可加入回调函数,将写入等阻塞的操作放在回调函数中执行
import aiohttp
import asyncio
#回调函数:解析响应数据
def callback(task):
print('this is callback()')
#获取响应数据
page_text = task.result()
print('在回调函数中,实现数据解析')
async def get_page(url):
async with aiohttp.ClientSession() as session:
async with await session.get(url=url) as response:
page_text = await response.text() #read() json()
# print(page_text)
return page_text
start = time.time()
urls = [
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom'
]
tasks = []
loop = asyncio.get_event_loop()
for url in urls:
c = get_page(url)
task = asyncio.ensure_future(c)
#给任务对象绑定回调函数用于解析响应数据
task.add_done_callback(callback)
tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时:',time.time()-start)