要实现异步协程,需要满足几个条件:
1,创建协程对象,且协程内部操作需要支持异步。
2,创建任务对象,如需为协程执行结果进行进一步处理,则需要为任务对象绑定回调函数。
3,创建事件循环,并将任务启动。
1 import asyncio 2 import requests 3 from lxml import etree 4 import aiohttp 5 import os 6 7 headers = { 8 9 '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' 10 } 11 12 13 # 普通单线程,同步执行任务 14 # url_ = 'https://www.qiushibaike.com/pic/page/{}/' 15 # for i in range(1, 100): 16 # url = url_.format(i) 17 # 18 # ret = requests.get(url=url, headers=headers).text 19 # tree = etree.HTML(ret) 20 # url_list = tree.xpath('//div[@id="content-left"]/div/div/a/img/@src') 21 # for url in url_list: 22 # name = url.split('/')[-1] 23 # print(name) 24 # img_url = 'https:' + url 25 # img = requests.get(img_url, headers=headers).content 26 # with open(os.path.join('qiutu',name), 'wb') as f: 27 # f.write(img) 28 29 # 使用async修饰过的函数,调用函数时,函数不执行,而是生成了一个协程对象 30 async def get_img(url): 31 """ 32 # aiohttp的细节处理: 33 # asnyc:硬性的语法要求关键字,应用aiohttp的代码模块 34 # await: 会出现阻塞的操作的代码中,需要用await修饰 35 36 """ 37 38 # ret = requests.get(url=url, headers=headers).text 39 name = url.split('/')[-1] 40 41 # 固定写法,aiohttp.ClientSession(),实例化一个异步请求对象 42 async with aiohttp.ClientSession() as aio: 43 # 用该对象向url发送异步请求 44 async with await aio.get(url=url, headers=headers) as ret: 45 # 将响应对象(图片)读取为二进制流,以便存储。(.text().json().read())对应requests的(.text.json.content) 46 res = await ret.read() 47 48 return name, res 49 50 51 # 定义一个回调函数(名字随意),回调函数接收该任务对象本身,(像self) 52 def callback(task): 53 # task.result()接收协程(特殊函数执行的返回结果,可进一步处理) 54 name, res = task.result() 55 # 将二进制流持久化成本地文件 56 with open(os.path.join('qiutu', name), 'wb') as f: 57 f.write(res) 58 print(name, '下载完成') 59 60 61 # 通过糗图百科通用页面url,生成十页数据,并将十页数据中的所有图片链接存到urls中 62 urls = [] 63 url_ = 'https://www.qiushibaike.com/pic/page/{}/' 64 for i in range(1, 10): 65 page_url = url_.format(i) 66 ret = requests.get(url=page_url, headers=headers).text 67 # 通过xpath定位并获取糗图图片的src 68 tree = etree.HTML(ret) 69 url_list = tree.xpath('//div[@id="content-left"]/div/div/a/img/@src') 70 # 将每个图片的url存到urls中 71 for img_url in url_list: 72 urls.append(img_url) 73 print(img_url) 74 75 tasks = [] 76 77 for url in urls: 78 url = 'https:' + url 79 # 执行协程对象函数(函数并不会真正去执行,而是生成一个协程对象) 80 cor = get_img(url) 81 # 将协程对象添加。生成一个任务对象 82 # task = asyncio.create_task(cor) # python3.7新加入 83 task = asyncio.ensure_future(cor) 84 # 为任务对象绑定添加一个回调函数,传入函数名 85 task.add_done_callback(callback) 86 # 将所有的任务对象添加到任务列表中 87 tasks.append(task) 88 89 # 获取当前事件循环对象(没有则创建),asyncio.new_event_loop()是创建事件循环 90 loop = asyncio.get_event_loop() 91 92 # run_until_complete:运行直到 future (可等待任务对象) 被完成。 93 94 # 将任务列表加入事件循环,并发开始执行 95 # 请注意此函数不会引发 asyncio.TimeoutError。当超时发生时,未完成的 Future 或 Task 将在指定秒数后被返回,指定timeout参数。 96 loop.run_until_complete(asyncio.wait(tasks)) 97 98 # asyncio.gather:如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。 99 # 如果发生超时,任务将取消并引发 asyncio.TimeoutError. 100 # loop.run_until_complete(asyncio.gather(tasks))
注意!:
一个线程内最多大概可以注册500个协程。。任务数过多,需要多线程加异步协程实现。