python项目实战——多协程下载美女图片

协程

文章目录

  • 协程
  • 协程的优劣势
  • 什么是IO密集型任务
      • 特点
      • 示例
      • 与 CPU 密集型任务的对比
      • 处理 I/O 密集型任务的方式
      • 总结
  • 创建并使用协程
    • asyncio模块
  • 创建协程函数
  • 运行协程函数asyncio.run(main())
  • aiohttp模块
    • 调用aiohttp模块步骤
  • aiofiles————协程异步函数
  • 遇到的问题
  • 一 await asyncio.wait(task)的作用
      • 1. **理解 `await` 的作用**
      • 2. **`asyncio.wait(task)` 的作用**
      • 3. **启动多个任务**
  • 多协程案例——爬取图片
    • 完整代码
    • 设计思路
    • 按顺序解读代码
    • 主函数

🌟 亲爱的读者们, 🌟

感谢你们访问我的博客!如果你希望更深入、透彻地了解文章中的内容,欢迎随时私信我。我可以为你录制视频讲解,让知识变得更加生动有趣。

当然,若你们对这个想法感兴趣的朋友们越多,我会更有动力去制作这些视频哦!让我们一起探索更多的知识,共同成长!

期待你的私信!📩

协程(coroutine):也叫作微线程,纤程,协程是单线程下的并发。
协程的作用:在执行函数A时,随时中断去执行函数B,然后再中断函数B.返回来执行函数A,该操作类似多线程,但协程中只有一个线程在执行。
微观上:一个一个任务的切换,切换条件一般是IO操作
宏观上:是多个任务在同时执行——多任务异步操作

协程的优劣势

  • 协程的优势:

    非常适用于I/O密集型任务;

    执行效率高(切换函数,而不切换线程,没有多余开销)

    不需要锁机制。

  • 协程的劣势:

    无法利用多核资源

    进行阻塞操作会阻塞掉整个程序。

什么是IO密集型任务

I/O 密集型任务(I/O-bound task)是指那些在执行过程中主要受限于输入/输出(I/O)操作性能的任务。这类任务的执行效率往往取决于数据的读写速度,而不是 CPU 处理能力。

特点

  1. 依赖于外部设备:I/O 密集型任务常常涉及与外部设备的交互,如硬盘、网络、数据库等。
  2. 等待时间长:由于 I/O 操作(如文件读取、网络请求)通常比 CPU 操作慢,I/O 密集型任务在执行时可能会有较长的等待时间。
  3. CPU 空闲:在等待 I/O 操作完成时,CPU 可能处于空闲状态,未能充分利用计算资源。

示例

  • 文件读取/写入:长时间读取或写入大文件。
  • 网络请求:等待远程服务器的响应,如 HTTP 请求。
  • 数据库操作:查询或写入数据库的过程,这些操作通常涉及网络通信和磁盘访问。

与 CPU 密集型任务的对比

  • I/O 密集型任务:主要依赖于 I/O 操作,CPU 通常在执行期间处于等待状态。
  • CPU 密集型任务:主要依赖于计算和处理,通常会占用大量的 CPU 资源。

处理 I/O 密集型任务的方式

  • 采用 异步编程:如使用回调或 Promises 来处理 I/O 操作,而不是阻塞主线程。
  • 利用 多线程或多进程:同时处理多个 I/O 操作,以减少整体等待时间。

总结

I/O 密集型任务是在执行中高度依赖于数据交换和处理速度的任务。理解 I/O 密集型任务的概念有助于在设计和优化系统时选择合适的编程模型和架构。

因为爬虫主要就是和网络进行交互,不太消耗我们自己电脑的CPU,负责接收网路返回的数据,是一种写入操作,就是适合爬虫

创建并使用协程

asyncio模块

python的标准库

  • 创建协程函数
    async def function()
    await IO操作
  • 调用协程函数
    async def main()
    await function()
  • 并发执行多个协程:
    asyncio.create task()
  • 运行协程:
    asyncio.run(main())
import asyncio  # 导入模块
import time# 创建协程函数
async def singing():print('start singing')time.sleep(2)print('end singing')async def dancing():print('start dance')time.sleep(3)print('end dance')async def main():# 调用协程函数await singing()await dancing()if __name__ == '__main__':start_time = time.time()# 运行协程函数asyncio.run(main())end_time = time.time()print(f'time:{end_time-start_time}')start singing
end singing
start dance
end dance
time:5.002954006195068
发现是5秒,就是串行

上面的这个程序是协程程序,但是是单协程,也就是串行,一个一个来

创建协程函数

asyncio.create_task(singing())

await asyncio.wait([task1,task2])

import asyncio  # 导入模块
import time# 创建协程函数
async def singing():print('start singing')await asyncio.sleep(2)print('end singing')async def dancing():print('start dance')await asyncio.sleep(3)print('end dance')async def main():# 使用协程里面的任务函数,来创建并发多协程task1 = asyncio.create_task(singing())task2 = asyncio.create_task(dancing())await asyncio.wait([task1,task2])if __name__ == '__main__':start_time = time.time()# 运行协程函数asyncio.run(main())end_time = time.time()print(f'time:{end_time-start_time}')start singing
start dance
end singing
end dance
time:3.0030245780944824
和多线程一样,都是有一个模块自己的函数,来启动线程/协程

time是一个同步函数,不能让CPU异步执行,即便是使用了并发执行多任务函数,CPU也不能同时运行

使用await调用协程函数自带的sleep函数

CPU遇到await语句,并且发生了堵塞,比如网络请求还没到,或者这个sleep等待函数未执行完,CPU都会自己切换出去

运行协程函数asyncio.run(main())

import asyncio  # 导入模块
import time# 创建协程函数
async def singing():print('start singing')await asyncio.sleep(2)print('end singing')async def dancing():print('start dance')await asyncio.sleep(3)print('end dance')async def main():# 和多线程一样,可以把要启动的多协程任务放到一个列表里面,最后在启动列表task = []for i in range(3):task.append(asyncio.create_task(singing()))task.append(asyncio.create_task(dancing()))await asyncio.wait(task) # 启动协程 if __name__ == '__main__':start_time = time.time()# 运行协程函数asyncio.run(main())end_time = time.time()print(f'time:{end_time-start_time}')start singing
start dance
start singing
start dance
start singing
start dance
end singing
end singing
end singing
end dance
end dance
end dance
time:3.003164768218994

和多线程一样,先是用asyncio模块下面的create task也就是字面意思,创建一个多协程任务,把协程函数写在里面

外面使用for循环,控制循环次数,也就是协程的数量,每循环一次,就会在这个create task里面多两个任务

所以循环了三次,就有6个任务被创建

因为这是协程任务,里面的sleep就是阻塞,所以程序一直在切换协程

aiohttp模块

相当于异步requests模块,适合在协程中使用使用方法类似 requests模块

官网:https://docs.aiohttp.org/en/stable/client_quickstart.html

请添加图片描述

requests是同步模块在协程中不能及时的释放CPU,所以需要使用协程专用的aiohttp模块

调用aiohttp模块步骤

  1. async with 是关键字
  2. aiohttp.ClientSession() 这是一个方法,然后起名字
  3. async with aiohttp.ClientSession() as session:
  4. async with session.get(url) as r: 前面两个是关键字,后面的是请求方式,和requests.get()一样
  5. 返回方式有三种:text,content,json
  6. 这里打印返回值的时候还要在后面加一个括号,r.text(),r.centent.read(),调用centent的时候还要加一个read()后缀,r.json()
import asyncio
import aiohttpasync def downloads(url):# print(url)name = url.split('/')[-1]# print(name)async with aiohttp.ClientSession() as session:async with session.get(url) as r: # 这里是把得到的网络请求内容赋值给rwith open(fr'E:\python笔记\{name}','wb') as f:f.write(await r.content.read()) # 这里需要使用等待关键字,因为是网络请求,网络响应的速度比不上CPU运算的速度print(f'已完成……{name}')async def main():urls = ('https://i1.huishahe.com/uploads/allimg/202302/9999/bcc80e5d24.jpg','https://i1.huishahe.com/uploads/allimg/202302/9999/8ccff5510c.jpg','https://i1.huishahe.com/uploads/allimg/202302/9999/186094180f.jpg')task = []for url in urls:task.append(asyncio.create_task(downloads(url)))  # 把协程任务存进列表await asyncio.wait(task) # 启动列表内的任务,并告诉协程这是一个等待语句if __name__ == '__main__':asyncio.run(main())

设计思路:

  1. 设计协程函数入口
    1. 把协程函数写入协程任务
    2. 使用await 运行协程任务
  2. 设计协程函数
    1. async with aiohttp.ClientSession() as session这个是aiohttp的方法,必须要写
    2. async with session.get(url) as r 这个就是异步网络请求了,返回内容在r里面

aiofiles————协程异步函数

也是协程里面的函数,也是异步函数,可以主动等待网络的返回值

import asyncio
from os import write
import aiofiles
import aiohttpasync def downloads(url):# print(url)name = url.split('/')[-1]# print(name)async with aiohttp.ClientSession() as session:async with session.get(url) as r: # 这里是把得到的网络请求内容赋值给r# with open(fr'E:\python笔记\{name}','wb') as f:#     f.write(await r.content.read()) # 这里需要使用等待关键字,因为是网络请求,网络响应的速度比不上CPU运算的速度#     print(f'已完成……{name}')async with aiofiles.open(fr'E:\python笔记\{name}','wb') as f:await f.write(await r.content.read())print(f'已完成……{name}')

和原来的文件保存方式相似,就是换了个关键字

  1. 因为要使用协程函数,所以要使用async关键字
  2. 因为网络请求需要等待,可能阻塞,还要写await让程序挂起

遇到的问题

一 await asyncio.wait(task)的作用

在使用 Python 的 asyncio 库时,协程的调度和执行是通过事件循环来处理的。await asyncio.wait(task) 是用来等待一组任务完成的关键工具。下面是它的重要性和工作原理的详细解释:

1. 理解 await 的作用

  • await 用于暂停协程的执行,直到被 await 的任务完成。它允许事件循环在等待的同时执行其他任务,从而提高效率。
  • async 函数中, 使用 await 的时候,协程会被挂起,控制权返回到事件循环,从而允许其他协程运行。

2. asyncio.wait(task) 的作用

  • asyncio.wait 是一个并发等待工具,它接受一个任务或任务列表,直到这些任务中的一个或全部完成。它返回一个集合,包含已完成和未完成的任务,方便后续处理。
  • 通过等待任务的完成,我们可以获得每个任务的结果或获取异常信息。

3. 启动多个任务

  • 使用 asyncio.wait 启动和管理多个协程任务。例如,当有多个协程需要并发执行时,可以将它们封装在一个任务列表中,然后调用 asyncio.wait 来调度。
  • 这样可以让我们更好地控制任务的执行,以及处理结果和异常。

多协程案例——爬取图片

效果图

请添加图片描述

请添加图片描述

完整代码

import pprintimport aiofiles
import aiohttp
import asyncio# 使用BS4解析网页
from bs4 import BeautifulSoup
from requests.packages import target# 获取翻页地址
async def get_all_url(url):# print(url)async with aiohttp.ClientSession() as session:async with session.get(url) as r:# print(await r.text())  # 可等待对象-----网页源代码soup = BeautifulSoup(await r.text(),'html.parser')links = soup.find('div',class_='slist').find_all('a',target="_blank") # 从目录页得到的总链接return_links = []for link in links:return_links.append(link['href'])return return_links# 获取目录页图片地址
async def get_url(url):# print(url)async with aiohttp.ClientSession() as session:async with session.get(url) as r:soup = BeautifulSoup(await r.text(), 'html.parser')pic_address = soup.find('div',class_="photo-pic").find('img')['src']title = soup.find('div',class_="photo-pic").find('img')['title']# print(pic_address,title)return pic_address,title# 下载图片
async def download_pic(jpg_address,title):# print(jpg_address,title)async with aiohttp.ClientSession() as session:async with session.get(jpg_address) as r:# print(r) # okasync with aiofiles.open(fr'E:\python笔记\{title}.jpg','wb') as f:await f.write(await r.content.read())print(f'已完成……{title}')async def main():task1 = []for i in range(1,2):if i == 1:url = 'https://www.moyublog.com/hdwallpapers/meinv/index.html'else:url = f'https://www.moyublog.com/hdwallpapers/meinv/index_{i}.html'task1.append(asyncio.create_task(get_all_url(url)))dones,pending = await asyncio.wait(task1) # await 关键字会返回两个内容,第一个是在函数里面return的# for link in dones: # 这里的dones返回的是一个列表,在改变目录页数的时候,列表的数量也会改变#     print(link.result()) # 当你发起异步操作时,该操作可能尚未完成。任务对象在执行完成前只是一个占位符。只有在它们完成后,调用 result() 才能获取实际的返回值。# 不调用 result() 直接打印任务对象,会返回任务的信息(如状态),而不是实际的返回值。task2 = []for link in dones:pages = link.result() # 把一个列表储存在pages里面for rt in pages: # 这里是在使用rt读取一个pages列表,最后打印出来# print(rt)  # 发现得到网页正常task2.append(asyncio.create_task(get_url(rt)))dones,pending = await asyncio.wait(task2)task3 = []for t in dones:# print(t.result())  # 发现返回的内容都在元组里面jpg_address,title = t.result()  # 可以直接自定义两个参数接收元组里面的内容# print(jpg_address,title) # oktask3.append(asyncio.create_task(download_pic(jpg_address,title)))await asyncio.wait(task3)if __name__ == '__main__':asyncio.run(main())

设计思路

  1. 找个比较合适的网站
  2. 找个你感兴趣的分类
  3. 获取目录页网址,自己找规律组合其他的目录页
  4. 获取目录页里面的每一种图片网址
  5. 从图片的网址找到图片的链接
  6. 下载并保存图片

按顺序解读代码

先要清楚,这里是多协程代码,会有网络请求等语句,也就是程序堵塞的地方,都要使用aio的关键字,比如异步网络请求aiohttp,异步文件储存aiofiles,异步函数执行async,异步函数的运行asyncio.wait,还有异步任务的创建asyncio.creata_task()

  1. 找目录页里面的图片链接的时,首先按照aiohttp.ClientSession()的方法,创建session,在使用sesssion.get的方式向网络发送请求,这里的session.get的用法和requests.get一样
  2. 在上一步已经把返回的内容储存在对象r里面了
  3. 这里使用了BS4的语法进行文件解析,也算是练习一下BS4的语法
  4. 在这里获取到了图片页的链接

我们获得了图片页的链接和名称,这个协程的任务就结束了,现在我们把这两个内容返回出去

这是一个协程函数,它的返回内容有两个dones和pending,前者是已完成的内容,后者是未完成的内容

想要正常获取协程函数的返回值,我们需要使用这两个参数结束返回值,后面的参数不用管

我们对前面的参数使用result()方法解析,就可以正常获取内容了

这里我们得到了很多列表,也就是一个目录页里面的所有链接在一个列表里面

把这个列表传递给下一个函数get_url这个函数的工作是专门从图片页的链接得到图片的jpg文件和名称

同样也是协程函数,和上面的处理方式一样

再把图片的jpg链接和名称给下载及保存图片的函数,这里就使用了异步文件保存的方式,因为网络请求有一定的延迟,还要使用await关键字

主函数

我们使用for循环构造目录页的链接,并存入协程任务

然后是把链接给处理目录页的函数

再把返回内容给图片页函数

再把返回内容给下载图片的函数

这些都是异步协程操作,也就是I/O密集型任务

所以可以在极短的时间保存大量的图片,比之前的多线程快了很多

awaitasyncio.wait 中并不会直接返回 donespendings 两个参数。相反,await asyncio.wait(...) 返回的是一个包含两个集合的元组

  • done: 包含所有已完成的 Task 对象。
  • pending: 包含所有未完成的 Task 对象。

研究的好几天才能较为熟练的使用协程函数,就是比较麻烦,但是因为是异步加载的请求,所以可以不用在等待网络返回内容时一直占用CPU了,CPU会去执行其他的协程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57058.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录跟练21天——LeetCode332.重新安排行程, 51. N皇后,37. 解数独

332.重新安排行程 力扣题目链接(opens new window) 给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出…

【Python可视化系列】一文教你绘制双Y轴的双折线图(案例+源码)

这是我的第369篇原创文章。 一、引言 在日常工作和学习中,我们会遇到将两个折线画在一张图上的情况,且这两个折线代表了两个特征,具有不同的涵义和量纲表示,这时候我们就需要绘制一个双Y轴折线图,一边代表一个特征&…

Redis 持久化 总结

前言 相关系列 《Redis & 目录》(持续更新)《Redis & 持久化 & 源码》(学习过程/多有漏误/仅作参考/不再更新)《Redis & 持久化 & 总结》(学习总结/最新最准/持续更新)《Redis & …

python进阶集锦

一、迭代器和生成器 区别 关于迭代器和生成器 迭代器与生成器的区别 迭代器(Iterator)和生成器(Generator)是Python中处理序列数据的两种不同概念。迭代器是遵循迭代协议的对象,而生成器是一种特殊类型的迭代器&am…

Vue学习笔记(八)

透传attribute "透传attribute"指的是传递给一个组件,却没有被改组件声明为props或emits的attribute或者v-on事件监听器。最常见的例子就是class、style和id。 当一个组件以单个元素为根作渲染时,透传的attribute会自动被添加到根元素上。 …

4个提取音频办法,轻松实现视频转音频!

在信息爆炸的时代,视频内容以其直观、生动的特点占据了互联网的大半江山。然而,在某些场景下,我们可能更倾向于只听取音频部分,无论是驾驶途中听讲座、跑步时享受音乐视频中的纯音乐的场景,还是为了节省流量和存储空间…

C++ 类与对象入门:基础知识与定义

引言: 本来打算用一篇介绍清楚C中的类与对象,再三考虑后觉得不妥:第一,知识点实在太多;第二,对于从刚学完C并打算过渡到C的朋友来说,学的太深较有难度… 总而言之,我打算用三到四篇文…

一篇文章总结 SQL 基础知识点

1. 官方文档 MySQL:https://dev.mysql.com/doc/refman/8.4/en/ SQL Server:What is SQL Server? - SQL Server | Microsoft Learn Oracle:https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/loe.html 2. 术语 SQL S…

电脑程序变化监控怎么设置?实时监控电脑程序变化的五大方法,手把手教会你!

​在现代办公和信息安全领域,实时监控电脑程序变化是一项至关重要的任务。 无论是企业内网安全、员工行为审计,还是个人电脑的隐私保护,了解并设置有效的监控方法都是必不可少的。 本文将详细介绍五种电脑程序变化监控的方法,帮助…

️ Vulnhuntr:利用大型语言模型(LLM)进行零样本漏洞发现的工具

在网络安全领域,漏洞的发现和修复是保护系统安全的关键。今天,我要向大家介绍一款创新的工具——Vulnhuntr,这是一款利用大型语言模型(LLM)进行零样本漏洞发现的工具,能够自动分析代码,检测远程…

SAP-ABAP开发学习-FUNCTION ALV

ALV概览 ALV全称SAP List View,是SAP提供的一个强大的数据报表显示工具。ALV实质上是一个屏幕控件对象,它通过程序传递数据内表的方式来显示数据。 实现方式:调用标准函数;优化接口:用户可以实现对字段的排序、筛选及统计等功能。…

51c嵌入式~IO合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/12383193 一、单片机通信数据接收解析方法 前阵子一朋友使用单片机与某外设进行通信时,外设返回的是一堆格式如下的数据: AA AA 04 80 02 00 02 7B AA AA 04 80 02 00 08 75 AA AA 04 80 02 00 9B E2…

layaair给图片精灵绘制形状并添加点击事件注意点击的区域不是方块

默认的图片添加进来都是方块,哪怕是圆形的图片,点击也是方块区域如下图,这个圆的上下左右方向角的位置都可点击。 利用sprite的Graphics属性则可以编制线条,圆形等形状。如下图 但是点击的区域还是默认根据sprite的高宽来设定的。…

vite 创建了一个项目后,如何实现工程化

当我们使用 vite 创建了一个项目后&#xff0c;要开发自己的东西。首先要做的就是先将开发文件变成自己的样子。 这是一个刚刚创建项目的文件夹及展示 开始整改文件夹 修改文件 main.ts不需要修改App.vue删除无用的内容 <template><div>app组件</div> &l…

推荐一款好用的redis管理工具TinyRDM

简述 对于经常要操作redis的人员来说&#xff0c;一款好用开源的redis可视化管理工具可以事半功倍&#xff0c;今天要介绍的TinyRDM就是这样的工具&#xff0c;支持多端&#xff08;MAC/Window/Linux&#xff09;&#xff0c;并且是开源的。 github地址&#xff1a;https://git…

CSPM(项目管理专业人员评价)证书还不如PMP?垃圾证书……

一、什么是CSPM CSPM&#xff08;China Standards Project Management&#xff09;是指依据中国标准开展的项目管理专业人员能力评价。它是由中国标准化协会&#xff08;全国项目管理标准化技术委员会秘书处&#xff09;设立&#xff0c;运用已发布的项目管理国家标准&#xf…

平衡相图在资源可持续利用方向的应用

平衡相图是描述物质在特定温度、压力和组成条件下达到相平衡状态的图表。在环境科学中&#xff0c;平衡相图可以用于预测和解释自然环境中物质的分布、迁移和转化过程&#xff0c;对于资源的可持续利用和环境污染的防治具有重要意义。 平衡相图在资源的可持续利用方向的应用主…

【YOLOv11[基础]】目标对象模糊处理

目录 一 安装YOLOv11 二 实践 使用Ultralytics YOLO11进行目标对象模糊处理,包括对图像或视频中特定检测到的对象应用模糊效果。这可以使用YOLO11模型功能来识别和操作给定场景中的对象。 目标对象模糊处理的优点: 隐私保护

Day16-非关系型数据库服务-redis

Day16-非关系型数据库服务-redis 1、非关系型数据库服务Redis1.1 Redis服务概述介绍1.1.1 缓存服务介绍1.1.2 缓存服务产品介绍1.1.3 缓存服务应用场景 1.2 Redis服务安装部署1.2.1 缓存服务安装部署 1.3 Redis服务基础管理操作1.3.1 缓存服务配置文件介绍1.3.2 缓存服务安全配…

如何对pdf文件进行加密?pdf文件加密全攻略与深度解析(5个方法)

如何对pdf文件进行加密&#xff1f; 只见&#xff0c;在深夜的情报局里&#xff0c;特工小李将一份绝密PDF文件放在保险箱内&#xff0c;以为这样就天衣无缝了。 细细推敲&#xff0c;漏洞百出&#xff1a; 如果钥匙被盗呢&#xff1f;如果被神匠破解出密码呢&#xff1f;如果…