高性能异步爬虫
asyncio异步携程模块
在python3.4之后新增了asyncio模块,可以帮我们检测IO(只能是网络IO【HTTP连接就是网络IO操作】),实现应用程序级别的切换(异步IO)。注意:asyncio只能发tcp级别的请求,不能发http协议。
- 异步IO:所谓「异步 IO」,就是你发起一个 网络IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知。 - 实现方式:单线程+协程实现异步IO操作。 - 异步协程用法 协程的实现,从 Python 3.4 开始,Python 中加入了协程的概念,但这个版本的协程还是以生成器对象为基础的,在 Python 3.5 则增加了 async/await,使得协程的实现更加方便。首先需要了解下面几个概念: event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。 coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。 task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。 future:代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。 从 Python 3.5 出现的async/await 关键字,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。
aiohttp异步网络请求模块
aiohttp
是基于asyncio
实现的HTTP框架,pip install aiohttp。
1 import aiohttp 2 3 #简单的基本架构: 4 async def request(url): 5 async with aiohttp.ClientSession() as s: 6 #s.get/post和requests中的get/post用法几乎一样:url,headers,data/prames 7 #在s.get中如果使用代理操作:proxy="http://ip:port" 8 async with await s.get(url) as response: 9 #获取字符串形式的响应数据:response.text() 10 #获取byte类型的:response.read() 11 page_text = await response.text() 12 return page_text
单线程+多任务异步协程
协程:协程对象。使用async关键字修饰一个函数的定义(特殊的函数),当该特殊的函数被调用后,就可以返回一个协程对象。当函数调用后,函数内部的实现语句不会被立即执行。协程就是一个用async关键字修饰的特殊函数。协程定义:async def func():pass,创建协程对象:c=func()
1 ''' 2 协程是一个用async关键字修饰的特殊函数; 3 在这个特殊函数内不允许出现不支持异步模块的代码,否则异步非阻塞将失效; 4 在特殊函数中遇到阻塞的代码前必须使用await关键字手动挂起(后续示例) 5 6 ''' 7 #定义一个协程 8 async def func(): 9 print('一个协程对象') 10 11 #创建协程对象 12 c=func()
任务对象:本质上就是对协程对象进一步封装,好比是一个特殊函数。任务对象task=asyncio.ensure_future(c)可以绑定一个回调函数:task.add_done_callback(func_callback)-----func_callback回调函数只能接受task任务对象执行结果返回值,类比进程池/线程池中submit提交任务后的回调函数使用!
1 ''' 2 任务对象是对协程对象进行封装后的一个特殊函数; 3 任务对象可以绑定一个回调函数; 4 ''' 5 import asyncio 6 #定义一个协程 7 async def func(n): 8 print(n) 9 10 #定义任务对象的回调函数:只能接受以个参数,该参数为任务对象执行结果返回值 11 def call_back(task): 12 print(task.result()) 13 14 #创建协程对象 15 c=func(-1) 16 17 18 #创建任务对象 19 task=asyncio.ensure_future(c) 20 #任务对象绑定回调函数 21 task.add_done_callback(call_back) 22 23 # 创建多个任务对象并为其绑定回调函数 24 task_list=[] 25 for i in range(5): 26 c=func(i) 27 task=asyncio.ensure_future(c) 28 task.add_done_callback(task) 29 task_list.append(task)
事件循环:事件循环对象的创建loop=asyncio.get_event_loop()。必须将任务对象注册到事件循环对象中,自动开启事件循环对象,在执行任务对象的时候是基于异步的。单个任务对象的注册:loop.run_until_complete(task);多个任务对象的注册:loop.run_and_complete(asyncio.wait(task_list))。
1 ''' 2 创建事件循环对象对任务对象进行注册调用; 3 注册单个任务对象/注册多个任务对象并自动调用执行; 4 5 ''' 6 import asyncio 7 #定义一个协程 8 import time 9 10 11 async def func(n): 12 await asyncio.sleep(2)#必须使用支持异步模块的代码,同时在遇到阻塞的代码前必须使用await手动挂起 13 print(n) 14 return n 15 16 #定义任务对象的回调函数:只能接受以个参数,该参数为任务对象执行结果返回值 17 def call_back(task): 18 print(task.result()) 19 20 #创建协程对象 21 c=func(-1) 22 23 start=time.time() 24 #创建任务对象 25 task0=asyncio.ensure_future(c) 26 #任务对象绑定回调函数 27 task0.add_done_callback(call_back) 28 29 # 创建多个任务对象并为其绑定回调函数 30 task_list=[] 31 for i in range(5): 32 c=func(i) 33 task=asyncio.ensure_future(c) 34 task.add_done_callback(call_back) 35 task_list.append(task) 36 37 #创建事件循环对象 38 loop=asyncio.get_event_loop() 39 #注册单个任务对象 40 loop.run_until_complete(task0) 41 #注册多个任务对象 42 loop.run_until_complete(asyncio.wait(task_list)) 43 44 loop.close() 45 print(time.time()-start)#2.00264573097229
注意事项:
- 在特殊函数(协程)中不能出现不支持异步模块对应的代码;
- 在特殊函数(协程)遇到阻塞的代码必须添加关键字await对其手动挂起;
- 如果将多个任务对象注册到事件循环对象中,先将多个任务对象添加到一个列表中,在注册时必须使用asyncio.wait()将列表中的任务对象挂起后再进行注册!
单线程多任务异步爬虫实例
flask服务器脚本构建:
import time from flask import Flask app=Flask(__name__) @app.route('/<id>') def func1(id): time.sleep(2) return id if __name__ == '__main__': app.run()
单线程多任务异步数据采集代码:
1 import time 2 import asyncio # pip install asyncio 3 import aiohttp #pip install aiohttp 4 headers = { 5 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36' 6 } 7 #创建urls列表 8 urls = [] 9 for i in range(500): 10 urls.append(f'http://127.0.0.1:5000/{i}') 11 12 #定义携程函数-----简单的基本架构 13 async def request(url): 14 async with aiohttp.ClientSession() as s: 15 #s.get/post和requests中的get/post用法几乎一样:url,headers,data/prames 16 #在s.get中如果使用代理操作:proxy="http://ip:port" 17 async with await s.get(url) as response: 18 #获取字符串形式的响应数据:response.text() 19 #获取byte类型的:response.read() 20 page_text = await response.text() 21 return page_text 22 23 #定义任务对象回调函数 24 def parse(task): 25 page_text = task.result() 26 print(f'响应结果:{page_text}') 27 28 29 #创建任务列表 30 start = time.time() 31 tasks = [] 32 for url in urls: 33 c = request(url) 34 task = asyncio.ensure_future(c) 35 task.add_done_callback(parse) 36 tasks.append(task) 37 #创建事件循环对象并将任务对象列表进行注册 38 loop = asyncio.get_event_loop() 39 loop.run_until_complete(asyncio.wait(tasks)) 40 loop.close() 41 print(time.time()-start)#3.4196295738220215