Python 图片并行下载

需求:有大量图片的url需要将其快速下载到本地
技术点:采用编写并发代码的库asyncio以及基于asyncio实现的HTTP框架aiohttp

pip install asyncio
pip install aiohttp

代码如下:

import json
import os
import requests
import aiohttp
import asyncio


image_save_dir = "images"  # 要将下载的图片保存到的本地文件夹路径
image_url_list = []  # 要下载的image url元素组成的列表

cnt = 0  # 已下载到本地指定位置的图片数量
n_miss = 0  # 下载失败的图片数量
async def download_image(session, url):
    global cnt
    global n_miss
    file_name = os.path.basename(url)
    # 保存图片到指定位置
    file_path = os.path.join(image_save_dir, file_name)
    if not os.path.exists(file_path):
        try:
            async with session.get(url) as response:
                if response.status == 200:
                    # 获取文件名
                    with open(file_path, 'wb') as file:
                        file.write(await response.read())
                    cnt += 1
                    if cnt % 100 == 0:
                        print("已下载{}/{}".format(cnt,len(image_url_list)))
                else:
                    n_miss += 1
                    print(f'下载失败,状态码:{response.status}, 链接:{url}')
        except Exception as e:
            n_miss += 1
            print(f'下载失败,链接:{url}, 错误:{str(e)}')
    else:
        cnt += 1
        if cnt % 100 == 0:
            print("已下载{}/{}".format(cnt,len(image_url_list)))
            print("下载失败: ",n_miss)

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [download_image(session, url) for url in image_url_list]
        await asyncio.gather(*tasks)
        print("总图片数量: {}".format(len(image_url_list)))
        print("下载成功: {}".format(cnt))
        print("下载失败:{}".format(n_miss))

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

09.27更新:上述代码在图片量较大的情况下需要反复运行才能全部下下来,以下提供修正版的程序模版,一次运行即可:

import json
import os
import requests
import aiohttp
import asyncio

    
image_url_list = []  # 要下载的image url元素组成的列表
image_save_dir = "images"  # 自定义图片保存的本地文件夹路径

cnt = 0  # 已下载到本地指定位置的图片数量
n_miss = 0  # 下载失败的图片数量
flag = 0  # 标记本次并发是否出现下载失败的情况
async def download_image(session, url):
    global cnt
    global n_miss
    global flag
    file_name = os.path.basename(url)
    # 保存图片到指定位置
    file_path = os.path.join(image_save_dir, file_name)
    if not os.path.exists(file_path):
        try:
            async with session.get(url) as response:
                if response.status == 200:
                    # 获取文件名
                    with open(file_path, 'wb') as file:
                        file.write(await response.read())
                    cnt += 1
                    if cnt % 100 == 0:
                        print("已下载{}/{}".format(cnt,len(image_url_list)))
                else:
                    n_miss += 1
                    flag = 1
                    return False
        except Exception as e:
            n_miss += 1
            flag = 1
            return False
            
    else:
        cnt += 1
        if cnt % 100 == 0:
            print("已下载{}/{}".format(cnt,len(image_url_list)))
            print("下载失败: ",n_miss)

async def main():
    global flag
    global n_miss
    global cnt 
    while True:
        flag = 0
        n_miss = 0
        cnt = 0
        async with aiohttp.ClientSession() as session:
            tasks = [download_image(session, url) for url in image_url_list]
            await asyncio.gather(*tasks)
            print("总图片数量: {}".format(len(image_url_list)))
            print("下载成功: {}".format(cnt))
            print("下载失败:{}".format(n_miss))
        if flag == 0:
            break

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

采用上述代码进行并行下载,速度比传统的串行下载提升了好几个数量级(实验了下载20w张图片也很快),但是要注意(1)可能的网络原因导致的后续图片下载失败(在网络连接可以的情况下多运行几次代码即可)以及(2)可能的由于并行等待时间不足等原因导致的图片下载不全(即下载到本地之后,图片有损无法打开),此时,建议再用以下代码进行检查并串行下载完之前下载后有损的图片:

import json
import os
from PIL import Image
import requests

# 一、检查图片下载情况
image_path_list = []  # 需要检查的图片路径组成的列表

err_imgs = []  # 下载到本地但是无法打开的图片名称组成的列表
n_miss = 0  # 没下载到本地的图片数量
n_err = 0  # 下载到本地但是无法打开的图片数量
for i in range(len(image_path_list)):
    image_path = image_path_list[i]
    try:
        image = Image.open(image_path)
    except:
        err_imgs.append(os.path.basename(image_path))
        n_err += 1
        # print(image_path)
    if not os.path.exists(image_path):
        n_miss += 1
print("没下载到本地的图片数量: ", n_miss)
print("下载到本地但是无法打开的图片数量: ", n_err)

err_imgs = list(set(err_imgs))
print(err_imgs)

# 二、如果有下载到本地但是图片有损打不开的情况,进行串行下载
if n_err > 0:  
    image_url_list = []  # 待串行下载的图片url列表

    image_save_dir = "save_dir"  # 待下载到本地保存的图片文件夹路径
    if not os.path.exists(image_save_dir):
        os.mkdir(image_save_dir)
        print(image_save_dir + ' maked')
    for image_url in image_url_list:
        response = requests.get(image_url)
        # 检查响应状态码,确保请求成功
        if response.status_code == 200:
            # 获取图像数据
            image_data = response.content
            # 本地保存图像
            with open(os.path.join(image_save_dir, os.path.basename(image_url)), 'wb') as file:
                file.write(image_data)
                print(os.path.join(image_save_dir, os.path.basename(image_url)))
        else:
            print("下载失败,HTTP响应状态码:", response.status_code)

欢迎在评论区给出宝贵意见,交流学习

posted @ 2023-09-27 10:27  奔跑着看风景  阅读(39)  评论(0编辑  收藏  举报