Python: 基于线程池的异步/多任务异步协程 【asyncio】
同步代码
import time import requests urls = [ 'http://www.chinadaily.com.cn/', 'https://www.163.com', 'https://www.bilibili.com/' ] def get_request(url): page_text = requests.get(url).text print(len(page_text)) if __name__ == "__main__": start = time.time() for url in urls: get_request(url) print('总耗时:',time.time()-start) ## 总耗时: 1.0235168933868408
基于线程池的异步效果
import time from multiprocessing.dummy import Pool import requests urls = [ 'http://www.chinadaily.com.cn/', 'https://www.163.com', 'https://www.bilibili.com/' ] def get_request(url): page_text = requests.get(url).text return len(page_text) if __name__ == "__main__": start = time.time() pool = Pool(3) #启动了三个线程 #参数1:回调函数 #参数2:可迭代的对象,alist #作用:可以将alist中的每一个元素依次传递给回调函数作为参数,然后回调函数会异步 #对列表中的元素进行相关操作运算 #map的返回值就是回调函数返回的所有结果 page_text_len_list = pool.map(get_request,urls) print(page_text_len_list) print('总耗时:',time.time()-start) ## 总耗时: 0.5089230537414551
多任务异步协程 【asyncio】
import requests import asyncio import time import aiohttp from lxml import etree # - 特殊函数 # - 如果一个函数的定义被async关键字修饰,则该函数就编程了一个特殊的函数 # - 特殊之处: # - 该函数调用后函数内部的实现语句不会被【立即】执行 # - 该函数被调用后会返回一个协程对象 # 特殊的函数:不可以出现不支持异步模块的代码,不可以使用requests模块 async def get_request(url): # 使用aiohttp进行网络请求 # - aiohttp的编码使用: # - 编写一个大致的架构 # with aiohttp.ClientSession() as sess: # with sess.get(url=url) as response: # page_text = response.text() # return page_text # - 在架构中补充细节 # - 在每一个with前加上async关键字 # - 在每一个阻塞操作前加上一个await关键字 # - 完整代码: # async with aiohttp.ClientSession() as sess: # async with await sess.get(url=url) as response: # page_text = await response.text() # return page_text # - await关键字可以确保在异步执行操作的过程中可以保证阻塞操作执行完毕。 async with aiohttp.ClientSession() as sess: # 实例化一个请求对象叫做sess # sess.get(url,headers,params,proxy) # sess.post(url,headers,data,proxy) # proxy参数的用法和requests不一样,剩下参数和requests的用法一样 # proxy = "http://ip:port" async with await sess.get(url=url) as response: # 调用get发请求,返回一个响应对象 # text()返回字符串形式的响应数据 # read()返回bytes类型的响应数据 page_text = await response.text() # 获取了页面源码数据 return page_text # 定义一个任务对象的回调函数 # 注意:回调函数必须要有一个参数,该参数表示就是该函数的绑定者 # 多任务的异步爬虫中数据解析或者持久化存储的操作需要写在任务对象的回调函数中 def parse(task): #result():返回的就是特殊函数的返回值 page_text = task.result() len_page_text = len(page_text) print(len_page_text) if __name__ == "__main__": start = time.time() urls = [ 'http://www.chinadaily.com.cn/', 'https://www.163.com', 'https://www.bilibili.com/' ] # 定义一个任务列表 tasks = [] for url in urls: # 创建三个协程对象 # - 协程对象: 该对象是特殊函数调用后返回的协程对象。 # - 协程对象 == 特殊的函数 # - 一个函数表示指定的一组操作,那么一个协程对象表示一组特定的操作 # - 如何创建一个协程对象?- 调用特殊函数返回 c = get_request(url) #创建三个任务对象 # - 任务对象 - 就是一个高级的协程对象。 # - 任务对象 == 协程对象 == 特殊的函数 # - 任务对象也表示一组特定的操作 # - 如何创建一个任务对象?- task = asyncio.ensure_future(c) # c指的是协程对象 task = asyncio.ensure_future(c) # - 问题1:如何获取特殊函数中的返回值? # - 基于任务对象的回调函数。 # - 如何给任务对象绑定回调函数? # - task.add_done_callback(parse) # - parse就是回调函数 # - parse必须要有一个参数,参数就是该函数的绑定者(任务对象) # - result()返回的就是特殊函数的返回值 task.add_done_callback(parse) # 绑定回调 tasks.append(task) # - 事件循环:EventLoop # - 对象。 # - 如何创建该对象 - loop = asyncio.get_event_loop() # - 作用: # - 是用来装载任务(协程)对象的:可以将事件循环当做是一个容器,容器中存放的是多个任务对象 # - 如果事件循环存放了多个任务对象且事件循环启动后,则事件循环对象就可以异步的将每一个任务对象对应的指定操作进行执行。 # - 如何将任务对象存储且启动事件循环对象 # - loop.run_until_complete(task)#将一个任务对象进行了存储 loop = asyncio.get_event_loop() #将任务列表中的多个任务注册到了事件循环中 # - 问题2:任务对象在事件循环中是否被异步的执行? # - 一个任务对象在事件循环中无法检测出是否被异步执行 # - 需要将多个任务对象注册事件循环中进行测试: # - 如何将多个任务注册到事件循环中? # - loop.run_until_complete(asyncio.wait(tasks)) # tasks表示的是任务列表 # - wait()方法的作用: # - 表示挂起的意思。 # - asyncio.wait(tasks)将任务列表中的每一个任务对象进行挂起。 # - 挂起:让当前的任务对象交出cpu的使用权。 loop.run_until_complete(asyncio.wait(tasks)) print('总耗时:',time.time()-start) ## 总耗时: 0.49843263626098633
- 实战说明
- 如果想使用该模式进行异步的数据爬取则必须:
- 将等待即将被爬取的页面的url单独的抽取存储到一个列表中。
- 通常情况下的玩法:
- 使用requests将等待爬取页面的url获取
- 将url写入列表,使用多任务异步协程爬取列表中的页面数据
参考:https://www.bilibili.com/video/BV1tE411F7do(P13~P15)