You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效下载大量图片URL并打包为Zip文件?

高效实现方案

原代码的核心问题

  • 每次循环重复打开/关闭Zip文件并进入Update模式:该模式需要每次扫描整个Zip的目录结构,4万次操作会产生巨大的额外开销,是效率低下的主要原因。
  • 串行下载:网络IO是主要瓶颈,串行下载无法充分利用带宽。
  • 未合理复用HttpClient:若_httpClient是每次循环创建,会导致连接池无法复用,增加网络握手开销。

优化方案

  1. 复用HttpClient:使用单例HttpClient,利用其内置的连接池减少网络握手次数。
  2. 一次性打开ZipArchive:使用Create模式打开Zip流并保持其全程打开,避免重复扫描Zip结构。
  3. 并行下载+串行写入Zip:通过并行下载提升网络利用率,同时用线程锁保证Zip写入的线程安全(因为ZipArchive本身非线程安全)。
  4. 控制并发数:避免并发过高导致目标网站封禁或本地资源耗尽。
  5. 异常隔离:单个URL下载失败不中断整个流程,记录错误信息。

代码实现

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;

class ImageDownloader
{
    // 复用单例HttpClient,减少连接开销
    private static readonly HttpClient _httpClient = new HttpClient()
    {
        Timeout = TimeSpan.FromSeconds(30) // 设置合理超时时间
    };

    public static async Task DownloadAndZipImages(string urlListPath, string zipOutputPath, int maxDegreeOfParallelism = 10)
    {
        // 1. 读取所有URL到内存(数十万条URL占用内存可忽略)
        var urls = new List<string>();
        using (var reader = new StreamReader(urlListPath))
        {
            string line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                if (!string.IsNullOrWhiteSpace(line))
                {
                    urls.Add(line.Trim());
                }
            }
        }

        // 2. 一次性打开ZipArchive(Create模式,全程保持打开)
        using (var zipStream = new FileStream(zipOutputPath, FileMode.Create, FileAccess.Write, FileShare.None))
        using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create))
        {
            var zipLock = new object(); // 保证Zip写入操作的线程安全
            var failedUrls = new List<string>();

            // 3. 并行下载,控制并发数避免资源过载
            await Parallel.ForEachAsync(urls, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, async (url, token) =>
            {
                try
                {
                    // 生成唯一文件名,避免重复覆盖
                    var fileName = $"{Guid.NewGuid()}{Path.GetExtension(new Uri(url).AbsolutePath)}";

                    // 下载图片流
                    using (var responseStream = await _httpClient.GetStreamAsync(url, token))
                    {
                        // 加锁写入Zip条目,保证线程安全
                        lock (zipLock)
                        {
                            var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal); // 可根据需求调整压缩级别
                            using (var entryStream = entry.Open())
                            {
                                responseStream.CopyTo(entryStream);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    // 记录失败URL,不中断整体流程
                    lock (failedUrls)
                    {
                        failedUrls.Add($"{url} - {ex.Message}");
                    }
                }
            });

            // 输出失败记录(可选)
            if (failedUrls.Count > 0)
            {
                await File.WriteAllLinesAsync("failed_urls.txt", failedUrls);
                Console.WriteLine($"下载完成,共{failedUrls.Count}个URL下载失败,详情见failed_urls.txt");
            }
            else
            {
                Console.WriteLine("所有图片下载并打包完成");
            }
        }
    }

    // 调用示例
    static async Task Main(string[] args)
    {
        await DownloadAndZipImages("UrlList.txt", "images.zip", maxDegreeOfParallelism: 15);
    }
}

额外优化建议

  • 压缩级别调整:若不需要压缩,可将CompressionLevel.Optimal改为CompressionLevel.NoCompression,进一步提升写入速度。
  • 文件名策略:如果需要保留原文件名,可对重复文件名添加序号(如image.jpg→image_1.jpg),避免Zip条目覆盖。
  • 重试机制:若目标网站有反爬限制,可添加重试逻辑(如捕获特定异常后重试2-3次)。

内容的提问来源于stack exchange,提问作者AnMSLbR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 15:08:28