1.单线程+多任务的异步协程
特殊函数: 如果一个函数的定义被asyncio修饰后,则该函数就成了一个特殊的函数。 协程: 就是一个协程对象。特殊函数调用的返回就是协程对象。 特殊函数被调用后,函数内部的实现语句不会被立即执行,然后该函数调用就会返回一个协程对象。 特殊函数的调用的返回 == 协程对象。 任务对象(只是在爬虫里面才会有任务对象): 其实就是对协程对象的进一步封装。 结论: 任务对象,就是一个高级的协程对象 任务对象 == 高级的协程对象 == 特殊函数调用的返回 为什么要使用任务对象? 因为任务对象可以绑定回调函数,协程对象和特殊的函数都没有回调函数 事件循环对象:是整个异步中的重要操作,没有它就实现不了异步 作用:将其内部注册的任务对象进行异步执行。 使用单线程+多任务的异步协程的编码流程 1.定义特殊函数 2.创建协程对象 3.封装任务对象 4.创建事物循环对象 5.将任务对象注册到事物循环对象并且开启循环对象
2.代码实现
import asyncio import time from time import sleep start = time.time() # 函数调用 async def get_request(url): # 加上async之后,在调用特殊函数的时候,里面的代码不会被执行,但是会返回一个coroutine(协程)对象 print('get url:',url) await asyncio.sleep(3) # 必须等待阻塞操作执行完毕之后,才执行后面的代码,这一步是在模拟请求网页,如果不等待的话,那么事件循环对象不会等待这段代码执行,所以网页获取不到 print('请求结束',url) return url # 回调函数:必须有一个参数 def parse(task): print('我是回调函数',task.result()) # 创建3个协程对象 urls = [ '1.com','2.com','3.com' ] # 封装任务列表 tasks_list = [] # 事件循环对象列表 for url in urls: c = get_request(url) # 根据特殊函数返回协程对象 task = asyncio.ensure_future(c) # 创建一个任务对象:基于携程创建 # 绑定回调函数 task.add_done_callback(parse) # parse回调函数名,不加扩号 tasks_list.append(task) loop = asyncio.get_event_loop() # get_event_loop返回一个事件循环对象 loop.run_until_complete(asyncio.wait(tasks_list)) # 将任务对象注册到事件循环对象中,并且开启事件循环。 # asyncio.wait是挂起的意思,当任务出现阻塞的时候挂起,去执行其他任务,如果不加的话,在遇到阻塞的时候不会挂起。有挂起监听的意思。 # 事件循环体现在当任务遇到阻塞是挂起(挂起等待阻塞结束)去执行其他任务。当挂起的任务不阻塞之后,loop事件函数回去执行该任务。事件循环是体现在这里面。但是具体的循环多少次是无法确定的。循环:应该是cpu在任务间来回执行 print(time.time()-start)注意事项:
1.回调函数什么时候被执行? 任务对象执行结束之后,才执行的函数叫做回调函数 # 回调函数:必须有一个参数,就是该回调函数默认绑定的任务对象 参数的作用是:用来返回该任务对象所对应特殊函数的返回值 注意事项: 在特殊函数内部的实现语句中不可以出现不支持异步的模块对象的代码,否则就会终止多任务异步协程的特殊效果 time模块不支持异步。requests也不支持异步
多任务异步爬虫

# -*- coding: utf-8 -*- # @Time : 2019/10/11 20:52 # @Author : p0st # @Site : # @File : 6多任务异步爬虫.py # @Software: PyCharm import asyncio import requests import time import aiohttp from lxml import etree # 特殊函数:发起请求获取页面源码数据 # async def get_requets(url): # # 其中异步效果失效了,所以变成串行了 # page_text = requests.get(url).text # return page_text async def get_requets(url): # 获取一个请求对象。使用aiohttp在请求结束后必须要关闭这个aiohttp请求对象。 # 但是我们不知道什么时候去手动关闭,所以我们使用with来自动关闭 async with aiohttp.ClientSession() as obj: # 在使用aiohttp对象时,后台requests模块是一样 # 就proxy参数不一样是字符串,不是字典了,是http://ip:port async with await obj.get(url) as response: # 也要关闭response对象 page_text = await response.text() return page_text # 这里使用text()方法来获取字符串的响应数据,使用read来返回bytes类型的数据 # 回调函数 def get_text(task): page_text = task.result() tree =etree.HTML(page_text) print(task,tree.xpath('//*[@id="10"]//text()')) urls = [ 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip2', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip2', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip2', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', 'http://127.0.0.1:5000/ip1', ] start = time.time() # 创建协程对象 tasks = [] for url in urls: c = get_requets(url) task = asyncio.ensure_future(c) # 给任务对象绑定回调函数,用于数据解析 task.add_done_callback(get_text) # get_text tasks.append(task) loop = asyncio.get_event_loop() # 创建时间循环对象 loop.run_until_complete(asyncio.wait(tasks)) print(time.time()-start)
pass