摘要:图片下载属于操作,比较耗时,基于此,可以利用中的多线程将其实现。更多精彩滚雪球学完结滚雪球学第二轮完结滚雪球学第三轮滚雪球学番外篇完结
在 python 编码过程中,有时存在这样的一个需求,同时下载 N 张图片,并且要快。
一般这样的需求,只需要编写一个 for 循环即可实现,但是加上 快 这个要求,就不好实现了。
图片下载属于 I/O 操作,比较耗时,基于此,可以利用 python 中的多线程将其实现。
为了不让大家学的太困倦,特意找来 6 张美丽的图片,本次学习将围绕这几张图片进行。
https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg
使用 for 循环,同步代码如下所示:
import timeimport requestsurls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]# 文件保存路径SAVE_DIR = "./928/"def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": # 下载开始时间 start_time = time.perf_counter() for url in urls: save_img(url) print("下载 6 张图片消耗时间为:", time.perf_counter() - start_time) # 下载 6 张图片消耗时间为: 1.911142665
接下来使用 concurrent.futures 模块
实现对 6 张图片的下载,这个模块实现了 ThreadPoolExecutor
类和 ProcessPoolExecutor
类,都继承自Executor
,分别被用来创建 线程池 和 进程池,接受 max_workers
参数,代表创建的线程数或者进程数。
这两个类可以在不同的线程或进程中执行 可调用对象,ProcessPoolExecutor
的 max_workers
参数可以为空,程序会自动创建与电脑 CPU
数目相同的进程数。
使用 ThreadPoolExecutor
实现多线程下载
import timeimport requestsfrom concurrent import futuresMAX_WORKERS = 20 # 最大线程数SAVE_DIR = "./928/" # 文件保存路径urls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": start_time = time.perf_counter() # 下载开始时间 with futures.ThreadPoolExecutor(MAX_WORKERS) as executor: res = executor.map(save_img, urls) # executor.map() 方法会返回一个生成器,后续代码可以迭代获取每个线程的执行结果 print("下载 6 张图片消耗时间为:", time.perf_counter() - start_time) # 下载 6 张图片消耗时间为: 0.415939759
当使用多线程代码之后,时间从单线程的 1.9s
变为了多线程的 0.4s
,能看到效率的提升。
在上述多线程代码中,使用了 concurrent
库中的 future
对象,该对象是 Future
类的对象,它的实力表示可能已经完成或尚未完成的 延迟计算,该类具备 done()
方法,返回调用对象是否已经执行,有该方法的同时还具备一个 add_done_callback()
方法,表示调用对象执行完毕的回调函数。
from concurrent.futures import ThreadPoolExecutordef print_name(): return "橡皮擦"def say_hello(obj): """可调用对象执行完毕,绑定的回调函数""" w_name = obj.result() s = w_name + "你好" print(s) return swith ThreadPoolExecutor(1) as executor: executor.submit(print_name).add_done_callback(say_hello)
在上述代码中用到了如下知识点:
executor.map()
:该方法类似 map
函数,原型为 map(func, *iterables, timeout=None, chunksize=1)
,异步执行 func
,并支持多次并发调用;executor.submit()
:方法原型为 submit(fn, *args, **kwargs)
,安排可调用对象 fn
以 fn(*args, **kwargs)
的形式执行,并返回 Future
对象来表示它的执行结果,该方法只能进行单个任务,如果需要并发多个任务,需要使用 map
或者 as_completed
;future对象.result()
:返回调用返回的值,有个等待时间参数 timeout
可以设置;future对象add_done_callback()
:该方法中绑定的回调函数在 future
取消或者完成后运行,表示 future
本身as_completed() 方法
该方法参数是一个 Future
列表,返回值是一个 Future
组成的生成器,在调用 as_completed()
方法时不会阻塞,只有当对迭代器进行循环时,每调用一次 next()
方法,如果当前 Future
对象还未完成,则会阻塞。
修改下载 6 张图片的代码,使用 as_completed()
进行实现,最后得到的时间优于单线程,但如果对结果进行迭代,调用 result()
方法,则时间会加长。
import timeimport requestsfrom concurrent.futures import ThreadPoolExecutor, as_completedMAX_WORKERS = 20 # 最大线程数SAVE_DIR = "./928/" # 文件保存路径urls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": start_time = time.perf_counter() # 下载开始时间 with ThreadPoolExecutor(MAX_WORKERS) as executor: tasks = [executor.submit(save_img, url) for url in urls] # 去除下部分代码,时间基本与 map 一致。 for future in as_completed(tasks): print(future.result()) print("下载 6 张图片消耗时间为:", time.perf_counter() - start_time) # 下载 6 张图片消耗时间为: 0.840261401
wait
方法
wait
方法可以让主线程阻塞,直到满足设定的要求,该要求为 return_when
参数,其值有 ALL_COMPLETED
,FIRST_COMPLETED
,FIRST_EXCEPTION
。
import timeimport requestsfrom concurrent.futures import ThreadPoolExecutor, as_completed, wait, ALL_COMPLETED, FIRST_COMPLETEDMAX_WORKERS = 20 # 最大线程数SAVE_DIR = "./928/" # 文件保存路径urls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": start_time = time.perf_counter() # 下载开始时间 with ThreadPoolExecutor(MAX_WORKERS) as executor: tasks = [executor.submit(save_img, url) for url in urls] wait(tasks, return_when=ALL_COMPLETED) print("程序运行完毕") print("下载 6 张图片消耗时间为:", time.perf_counter() - start_time) # 下载 6 张图片消耗时间为: 0.48876672
最后一句:ProcessPoolExecutor
的用法与 ThreadPoolExecutor
的用法基本一致,所以可以互通。
以上内容就是本文的全部内容,希望对学习路上的你有所帮助~
今天是持续写作的第 235 / 365 天。
期待 关注,点赞、评论、收藏。
更多精彩
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/122205.html
摘要:的安装博客补充知识年最新安装教程,滚雪球学第四季。操作操作数据库一般被程序员成为操作增删改查,其中各个字符分别代表新增,读取,更新,删除。可以返回受影响行数,可以直接通过该值判断是否修改成功。 ...
摘要:看起来好像是废话,它还有一个补充的说明,在函数式编程中要避免状态变化和使用可变对象。函数式编程的特点在中,函数即对象,例如声明一个函数之后,你可以调用其属性。 ...
摘要:在流程控制中,你将同步学到关系运算符与逻辑运算符。关系运算符在中关系运算符其实就是比大小的概念,所以要学习的就是大于小于等于等内容。逻辑运算符逻辑运算符在中有个,分别是。含有逻辑运算符的式子,最终返回的结果也是布尔值。 滚雪球学 Python,目标就是让 Python 学起来之后,越滚越大。三、无转折不编程如果...
摘要:时间永远都过得那么快,一晃从年注册,到现在已经过去了年那些被我藏在收藏夹吃灰的文章,已经太多了,是时候把他们整理一下了。那是因为收藏夹太乱,橡皮擦给设置私密了,不收拾不好看呀。 ...
摘要:返回值,非必须,返回多个值使用逗号分隔即可。注意第一行末尾的分号无参数无返回值的函数该内容将演示函数的使用便捷性。函数的返回值可以赋值给一个变量,通过打印该变量,即可知道返回的具体内容。先学习一下局部变量与全局变量。 Python 学习的第一个难关 -- 函数,这个地方学会的人觉得没有啥,没学过的学的时候迷迷瞪...
阅读 1021·2021-10-11 10:59
阅读 3583·2021-09-26 09:55
阅读 878·2019-08-30 15:55
阅读 2628·2019-08-30 15:44
阅读 389·2019-08-30 14:06
阅读 643·2019-08-30 11:26
阅读 3305·2019-08-30 10:49
阅读 2410·2019-08-29 12:53