高性能异步爬虫

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
aiohttp使用事例

单线程+多任务异步协程

  协程协程对象。使用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()
协程/协程对象---async/await

   任务对象:本质上就是对协程对象进一步封装,好比是一个特殊函数。任务对象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()
server构建

  单线程多任务异步数据采集代码

 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
单线程多任务异步数据采集代码

 

posted @ 2019-08-06 22:34  笑得好美  阅读(353)  评论(0编辑  收藏  举报