Python 图片并行下载

发布时间 2023-09-27 10:33:49作者: 奔跑着看风景

需求:有大量图片的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())

采用上述代码进行并行下载,速度比传统的串行下载提升了好几个数量级(实验了下载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)

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