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.rhkb.cn/news/458638.html

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

相关文章

AI最新动态概览-2024年10月28日

1. 字节跳动加速欧洲布局,拟建AI研发中心 近日,有消息称字节跳动正积极筹备在欧洲设立AI研发中心,此举标志着该公司在全球技术版图上的又一重要扩张。随着人工智能技术的飞速发展,字节跳动正通过招兵买马,进一步巩固其…

Linux 进程优先级 进程切换

目录 优先级 概念 为什么优先级要限制在一定范围内 进程切换 方式 EIP寄存器(程序计数器) 进程在运行时会使用寄存器来保存临时数据 进程的上下文是什么? 进程的上下文保存到哪? 内核栈或专门的上下文结构也在内核空间?那为什么不直…

java 提示 避免用Apache Beanutils进行属性的copy。

避免用Apache Beanutils进行属性的copy。 Inspection info: 避免用Apache Beanutils进行属性的copy。 说明:Apache BeanUtils性能较差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。 TestObject a new TestObject(); TestObject b new Te…

2024 最新 frida技术栈 第一部分

目录 1.下载 2. 安装 2.1. 命令 3.基本使用 3.1 列出运行的APP 3.2 列出所有APP 3.3 杀死进程 4. frida hook 方法 4.1 frida客户端命令行的参数 4.2. Frida两种操作模式 4.3. Frida操作APP的两种方式 4.3.1. attach模式 4.3.2. spawn模式 4.3.3 转发端口启…

RabbitMQ的Overview Totals是空

一、问题描述 RabbitMQ 版本:4.0.2,Erlang 版本:26.2.5.4。 RabbitMQ 页面管理(rabbitmq_management)的 Overview > Totals 是空: 二、原因分析 RabbitMQ 的配置: management_agent.disable_metrics_collector…

Hive的数据存储格式

目录 一、前言 二、存储格式 2.1、文本格式(TextFile) 2.1.1、定义与特点 2.1.2、存储与压缩 2. 1.3、使用场景 2.2、行列式文件(ORCFile) 2.2.1、ORC的结构 2.2.2、ORC的数据类型 2.2.3、ORC的压缩格式 2.2.3、ORC存储…

LVGL移植教程(超详细)——基于GD32F303X系列MCU

版本:LVGL Kernel V8.3.0,运行压力测试Demo Stress首先放一张最终Stress Demo 运行图: 一、准备 1. GD32 Keil工程 准备任意一个屏幕可以正常显示的GD32工程: 2. LVGL源码 最新版现在已经是V9.2了,这里我选择了…

XQT_UI 组件|03 |加载组件 XQtLoading

XQtLoading 使用文档 简介 XQtLoading 是一个自定义的加载动画组件,旨在为用户提供可配置的旋转花瓣动画效果。它可以在应用程序中用于指示加载状态,提升用户体验。 特征 可配置性:用户可以根据需求调整旋转周期、缩放周期、最大/最小缩放…

Bi-LSTM-CRF实现中文命名实体识别工具(TensorFlow)

项目源码获取方式见文章末尾! 回复暗号:13,免费获取600多个深度学习项目资料,快来加入社群一起学习吧。 **《------往期经典推荐------》**项目名称 1.【MobileNetV2实现实时口罩检测tensorflow】 2.【卫星图像道路检测DeepLabV3P…

关于嵌入式学习的一些短浅经验

一、写在前面 感谢在 10.23,各位大佬对我进行的模拟面试,我也发现了我对知识的不熟练的部分,比如 IPC 方法和线程同步方法的知识。模拟面试第四期-已经拿到大厂 OFFER 的研究生大佬-LINUX 卷到飞起_哔哩哔哩_bilibili 然后,沈阳…

uniapp+uniCloud前端独立开发全栈项目Vue3版本学习路线,轻松开发H5、微信小程序、APP

概述 嗨,大家好,我是爱搞知识的咸虾米,这个学习路线是uniappuniCloud生态开发微信小程序、H5、APP等实战项目,从零基础开始到各种类型的项目案例,使用比较新的vue3语法糖版本,通过前端的技术可以轻松开发上…

微信小程序——消息订阅

首先用到的就是wx.requestSubscribeMessage接口。 注意:用户发生点击行为或者发起支付回调后,才可以调起订阅消息界面 requestSubscribeMessage() {uni.requestSubscribeMessage({tmplIds: [],//需要订阅的消息模板的id的集合,一次调用最多可…

Docker 常用命令全解析:提升对雷池社区版的使用经验

Docker 常用命令解析 Docker 是一个开源的容器化平台,允许开发者将应用及其依赖打包到一个可移植的容器中。以下是一些常用的 Docker 命令及其解析,帮助您更好地使用 Docker。 1. Docker 基础命令 查看 Docker 版本 docker --version查看 Docker 运行…

向量检索学习记录

1、Faiss Faiss是一个用于高效相似搜索和密集向量聚类的库;(支持单个/多个GPU)官方文档:Home facebookresearch/faiss Wiki GitHub 安装(如果编译有问题,有些选项需要关下,比如GPU, Python&a…

从 Hadoop 迁移到数据 Lakehouse 的架构师指南

从 Hadoop 到数据湖仓一体架构的演变代表了数据基础架构的重大飞跃。虽然 Hadoop 曾经以其强大的批处理能力统治着大数据领域,但如今的组织正在寻求更敏捷、更具成本效益和现代化的解决方案。尤其是当他们越来越多地开始实施 AI 计划时。根本没有办法让 Hadoop 为 A…

【福建医科大学附属第一医院-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞 …

微信机器人自动回复了解下

现在使用微信来做私域营销和维护客户的非常多,在工作上会频繁地遭遇客户提出的相同问题,当我们的好友数量众多时,手动逐个回复可能会耗费大量的时间。 幸运的是,多微管理系统的自动回复功能为我们带来了福音。帮助咱们解决这一难…

原创作品——GIS和监控软件设计

这套数据可视化大屏软件UI设计旨在提供一个直观、高​‌‌效且易于理解的界面,用于展示和分析大量的能源数据和信息。以下是关于该系统UI设计的详细介绍: 整体布局与设计风格: 界面以蓝色调为主,代表冷静、专业和科技的氛围。 布…

VisualStudio2022配置2D图形库SFML

文章目录 1. 下载安装SFML库2. 创建C项目并配置SFML配置include目录和库目录链接SFML库配置动态链接库 3. 测试 1. 下载安装SFML库 SFML(Simple and Fast Multimedia Library)C库,适合2D游戏和图形界面,提供了以下模块&#xff1…

通过conda install -c nvidia cuda=“11.3.0“ 安装低版本的cuda,但是却安装了高版本的12.4.0

问题 直接通过 conda install -c nvidia cuda"11.3.0"安装得到的却是高版本的 不清楚原理 解决方法 不过我们可以分个安装 runtime toolkit 和 nvcc 安装指定版本的 cudatoolkit 和 nvcc conda install -c nvidia cuda-cudart"11.3.58" conda instal…