协程最大的优势就是:
协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。但相应的是协程的控制难度比较高,容易出错。
*单线程-异步协程:
event-loop:
时间循环,相当于一个无限循环,我们可以把一些函数注册到这个时间当中去,当满足某些条件时,函数就会被循环执行
coroutine:
协程对象,我们可以将协程对象注册到事件循环中,它被事件循环调用,我们可以使用async关键字来定义方法,这个方法在调用的时候不会立即执行,而是返回一个协程对象
task:
任务,它是对协程对象的进一步封装,包含了任务的各种执行状态
future:
代表将来执行或者还没有执行的任务,和task没区别
async:
定义一个协程
await:
用来挂起阻塞方法的执行
使用方法:
1.导入模块:
import async asyncio的编程模型就是一个消息循环
2.创建事件循环对象
loop = asyncio.get_event_loop()
3.创建任务
c=func()
c为函数执行的结果
task = asyncio.ensure_future(c) #用于显示状态和传递参数
4.将协程对象注册到loop中然后启动
loop.run_until_complete(task)
5.回调函数用法:
import asyncio
#async修饰函数,调用之后的一个协程对象
async def request(url):
print("正在请求的url",url)
print("请求成功",url)
return url
c=request("www.baidu.com")
#绑定回调
def callback_func(task):
#result返回的是任务对象中封装的函数的返回值
print("result :",task.result()
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
#将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
loop.run_until_complete(task)
结果:
正在请求的url www.baidu.com
请求成功 www.baidu.com
result : www.baidu.com
看一个协程的代码:
当然也可以不需要task操作,直接将函数返回的协程对象传给注册到的loop中也可以。
import asyncio
import time
async def request(url):
print("正在下载 ",url)
#在异步协程中如果出现了同步模块相关的代码,异步无法实现,如time.sleep()
#time.sleep(2)
#async中也有sleep方法,是基于异步的
await asyncio.sleep(2) #遇到阻塞必须手动挂起
print("下载完成 ",url)
start_time=time.time()
urls=[
"www.baidu.com",
"www.sougou.com",
"www.goubanjia.com"
]
#任务列表,存放多个任务对象
tasks = []
for url in urls:
c = request(url)
task=asyncio.ensure_future(c)
tasks.append(c)
loop = asyncio.get_event_loop()
#固定的语法格式,需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
结果:
正在下载 www.baidu.com
正在下载 www.sougou.com
正在下载 www.goubanjia.com
下载完成 www.baidu.com
下载完成 www.sougou.com
下载完成 www.goubanjia.com
2.0048012733459473
应用协程如何爬取网页
使用异步网页模块:aiohttp模块
flask服务器:
from flask import Flask
import time
app=Flask(__name__)
@app.route('/tian')
def index_tian():
time.sleep(2)
return "hello tian"
@app.route('/jian')
def index_jian():
time.sleep(2)
return "hello jian"
@app.route('/ying')
def index_ying():
time.sleep(2)
return "hello ying"
if __name__ == '__main__':
app.run(threaded=True)
在没有aiohttp异步请求时:
import asyncio
import requests
import aiohttp
import time
start_time=time.time()
urls=[
"http://127.0.0.1:5000/tian","http://127.0.0.1:5000/jian","http://127.0.0.1:5000/ying"
]
async def get_page(url):
print("正在下载",url)
res=requests.get(url=url) #requests模块是同步的,必须使用异步的网络请求模块
print("下载完毕",res.text)
tasks=[]
for url in urls:
c=get_page(url)
task=asyncio.ensure_future(c)
tasks.append(task)
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
结果:
正在下载 http://127.0.0.1:5000/tian
下载完毕 hello tian
正在下载 http://127.0.0.1:5000/jian
下载完毕 hello jian
正在下载 http://127.0.0.1:5000/ying
下载完毕 hello ying
6.032746315002441
使用aiohttp异步请求时:
单线程的方式,让其效率大大提高
import asyncio
import requests
import aiohttp
import time
start_time=time.time()
urls=[
"http://127.0.0.1:5000/tian","http://127.0.0.1:5000/jian","http://127.0.0.1:5000/ying"
]
async def get_page(url):
async with aiohttp.ClientSession() as session: #所有with前必须有async修饰
#get()/post params/data headers proxy="http://ip:port"
async with await session.get(url) as res: #耗时的操作必须进行挂起操作
page_text=await res.text() #在获取响应数据之前必须手动挂起
#text()字符串格式数据
#read()二进制格式响应数据
#json()返回json对象
print(page_text)
tasks=[] #创建任务列表
for url in urls: #将函数的返回值逐一加入列表中
c=get_page(url)
task=asyncio.ensure_future(c)
tasks.append(task)
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) #将参数传入并运行
print(time.time()-start_time)
结果:
hello tian
hello jian
hello ying
2.06882905960083
视频下载实例代码:
使用单线程异步协程爬取视频网站视频
import time
import asyncio
import aiohttp
import os
import requests
from lxml import etree
import re
path="./多线程,多进程/aiohttp实例视频/"
if not os.path.exists(path):
os.mkdir(path)
start_time=time.time()
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"
}
# urls=[
# "https://video.pearvideo.com/mp4/third/20191120/cont-1624716-10964589-180710-hd.mp4",
# "https://video.pearvideo.com/mp4/adshort/20191120/cont-1624622-14612668_adpkg-ad_hd.mp4",
# "https://video.pearvideo.com/mp4/third/20191121/cont-1624965-11472701-151804-hd.mp4"
# ]
def find_url(url): #遍历抓取视频url的函数
res=requests.get(url=url,headers=headers).text
tree=etree.HTML(res)
name=tree.xpath('//div[@class="popularem-ath"]/a/h2/text()')
detail_url=tree.xpath('//li[@class="popularem clearfix"]/div[@class="popularem-ath"]/a/@href')
video_lis=[]
for n,d in zip(name,detail_url):
video_name=n+".mp4"
video_path=path+video_name
detail_url="https://www.pearvideo.com/"+d
res_detail=requests.get(url=detail_url,headers=headers).text
video_url=re.findall(r'srcUrl="(.*?)",vdoUrl=',res_detail)[0]
video_lis.append(video_url)
return video_lis
async def get_page(url): #定义协程方法爬取url
async with aiohttp.ClientSession() as session: #所有with前必须有async修饰
async with await session.get(url,headers=headers) as res: #耗时的操作必须进行挂起操作
down_time=time.time()
page_text=await res.read() #在获取响应数据之前必须手动挂起
name=url.split("/")[-1]
path_name=path+name
with open(path_name,"wb") as f:
f.write(page_text)
print(url+" 下载完成,用时: %.2f秒"%(time.time()-down_time))
urls=find_url("https://www.pearvideo.com/popular_4") #得到所有视频的url地址列表
tasks=[]
for url in urls: #将函数的返回值逐一加入到task中
c=get_page(url)
task=asyncio.ensure_future(c)
tasks.append(task)
loop=asyncio.get_event_loop() #注册循环
loop.run_until_complete(asyncio.wait(tasks)) #启动循环,参数为列表时,固定方法用wait
print(time.time()-start_time)
结果:
单线程异步协程用时171秒
https://video.pearvideo.com/mp4/third/20191120/cont-1624716-10964589-180710-hd.mp4 下载完成,用时: 21.13秒
https://video.pearvideo.com/mp4/third/20191121/cont-1624965-11472701-151804-hd.mp4 下载完成,用时: 23.08秒
https://video.pearvideo.com/mp4/adshort/20191120/cont-1624622-14612668_adpkg-ad_hd.mp4 下载完成,用时: 36.18秒
https://video.pearvideo.com/mp4/third/20191121/cont-1624908-12308265-134207-hd.mp4 下载完成,用时: 43.87秒
https://video.pearvideo.com/mp4/adshort/20191118/cont-1623848-14604356_adpkg-ad_hd.mp4 下载完成,用时: 71.18秒
https://video.pearvideo.com/mp4/third/20191121/cont-1624864-12308265-115404-hd.mp4 下载完成,用时: 108.35秒
https://video.pearvideo.com/mp4/adshort/20191120/cont-1624599-14612561_adpkg-ad_hd.mp4 下载完成,用时: 124.40秒
https://video.pearvideo.com/mp4/third/20191118/cont-1623816-10887340-142811-hd.mp4 下载完成,用时: 165.08秒
171.87573504447937
使用线程池爬取视频网站:
import requests
from lxml import etree
import re
import os
import time
from multiprocessing.dummy import Pool
path="./多线程,多进程/li_video/" #设置路径
if not os.path.exists(path):
os.mkdir(path)
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"
}
def download_video(dic): #抓取视频的函数,传入参数为字典,得先获取字典内的键值
video_url=dic['url']
video_path=dic['path']
video_name=dic['name']
down_time=time.time()
video_data=requests.get(url=video_url,headers=headers).content
with open(video_path,"wb") as f:
f.write(video_data)
print(video_name+" 下载完成,用时: %.2f秒"%(time.time()-down_time))
def find_url(url): #遍历抓取视频url的函数
res=requests.get(url=url,headers=headers).text
tree=etree.HTML(res)
name=tree.xpath('//div[@class="popularem-ath"]/a/h2/text()')
detail_url=tree.xpath('//li[@class="popularem clearfix"]/div[@class="popularem-ath"]/a/@href')
video_lis=[]
for n,d in zip(name,detail_url):
video_name=n+".mp4"
video_path=path+video_name
detail_url="https://www.pearvideo.com/"+d
res_detail=requests.get(url=detail_url,headers=headers).text
video_url=re.findall(r'srcUrl="(.*?)",vdoUrl=',res_detail)[0]
print(video_url)
dic={ #将参数存储在字典中
"path":video_path,
"url":video_url,
"name":video_name
}
video_lis.append(dic)
pool=Pool(len(video_lis)) #实例话线程池
pool.map(download_video,video_lis)
pool.close()
pool.join()
start_time=time.time()
find_url("https://www.pearvideo.com/popular_4")
end_time=time.time()
print("一共用时 %.2f秒"%(end_time-start_time))
结果:
线程池用时187秒
小姐姐要买车,销售竟然想出了这招.mp4 下载完成,用时: 92.09秒
从机油看爱车健康,保养还能这么玩.mp4 下载完成,用时: 100.49秒
环法上海站,我坐它体验了一圈赛道.mp4 下载完成,用时: 138.14秒
林肯总裁不惧车市寒冬,看好飞行家.mp4 下载完成,用时: 145.14秒
在机场举办汽车发布会,他是飞行家.mp4 下载完成,用时: 159.37秒
车市不好,供应商如何调整自己?.mp4 下载完成,用时: 162.65秒
实拍改装领克03:竟和法拉利撞脸?.mp4 下载完成,用时: 187.50秒