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

如何用JavaScript/C#无界面下载多格式文件?解决批量下载仅最后一个生效问题

你当前的代码只下载最后一个文件,问题出在两个地方:一是window.location.assign会直接替换当前页面的导航上下文,前一个下载请求还没触发就被后一个覆盖了;二是setTimeout的用法完全错了——它第一个参数得是函数,你直接写setTimeout(20000)根本不会延迟执行,代码会立刻跳转到第二个URL。下面分别给你JavaScript和C#的无界面批量下载方案:

JavaScript 无界面批量下载方案

这里的“无界面”指的是不需要用户手动点击,自动触发下载,基于浏览器环境实现。

方法1:动态创建<a>标签模拟点击

这是最直接的方案,利用浏览器原生下载行为,适合同源或后端允许跨域的场景。

// 把所有需要下载的文件链接整理成数组
const fileUrls = [
  "https://site.site.net/servlet/DocumentServlet?documentId=123456789&action=viewUncontrolledCopy",
  "https://site.site.net/servlet/DocumentServlet?documentId=987654321&action=viewUncontrolledCopy"
];

// 批量下载函数,可自定义间隔避免浏览器拦截
function batchDownload(urls, delay = 1000) {
  urls.forEach((url, index) => {
    setTimeout(() => {
      const downloadLink = document.createElement('a');
      downloadLink.href = url;
      // 若后端返回了Content-Disposition响应头,可留空让浏览器自动获取文件名
      downloadLink.download = `document_${index + 1}`;
      // 模拟点击触发下载
      downloadLink.click();
      // 清理临时创建的标签
      document.body.removeChild(downloadLink);
    }, index * delay);
  });
}

// 调用批量下载,间隔2秒避免被浏览器拦截
batchDownload(fileUrls, 2000);

注意事项:

  • 跨域场景下,需要后端配置CORS,且响应头要包含Access-Control-Expose-Headers: Content-Disposition才能获取服务器返回的文件名。
  • 间隔时间别太短,否则浏览器可能会判定为恶意批量下载并拦截。

方法2:Fetch API 获取Blob后下载

这种方案更灵活,能处理跨域(需后端允许),还能在下载前做校验(比如检查文件大小、类型)。

const fileUrls = [
  "https://site.site.net/servlet/DocumentServlet?documentId=123456789&action=viewUncontrolledCopy",
  "https://site.site.net/servlet/DocumentServlet?documentId=987654321&action=viewUncontrolledCopy"
];

// 单个文件下载函数
async function downloadSingleFile(url, defaultFilename) {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`请求失败,状态码:${response.status}`);

    // 从响应头提取服务器返回的文件名
    const contentDisposition = response.headers.get('Content-Disposition');
    let finalFilename = defaultFilename;
    if (contentDisposition) {
      const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
      if (filenameMatch) finalFilename = filenameMatch[1];
    }

    // 转换为Blob并创建下载链接
    const fileBlob = await response.blob();
    const blobUrl = URL.createObjectURL(fileBlob);
    
    const downloadLink = document.createElement('a');
    downloadLink.href = blobUrl;
    downloadLink.download = finalFilename;
    downloadLink.click();

    // 释放Blob URL资源
    URL.revokeObjectURL(blobUrl);
    document.body.removeChild(downloadLink);
  } catch (err) {
    console.error(`下载失败:${err.message}`);
  }
}

// 批量下载函数,串行执行避免拦截
async function batchDownloadWithFetch(urls, delay = 1500) {
  for (let i = 0; i < urls.length; i++) {
    await downloadSingleFile(urls[i], `temp_file_${i + 1}`);
    // 等待指定间隔后再下载下一个
    await new Promise(resolve => setTimeout(resolve, delay));
  }
}

// 启动批量下载
batchDownloadWithFetch(fileUrls);
C# 无界面批量下载方案

如果需要完全脱离浏览器的无界面下载(比如后台自动化任务、控制台程序),C#的HttpClient是最佳选择。

控制台程序示例

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

class BatchFileDownloader
{
    static async Task Main(string[] args)
    {
        // 整理需要下载的文件链接
        var fileUrls = new List<string>
        {
            "https://site.site.net/servlet/DocumentServlet?documentId=123456789&action=viewUncontrolledCopy",
            "https://site.site.net/servlet/DocumentServlet?documentId=987654321&action=viewUncontrolledCopy"
        };

        // 设置文件保存目录,自动创建不存在的目录
        string saveDirectory = Path.Combine(Environment.CurrentDirectory, "DownloadedDocuments");
        Directory.CreateDirectory(saveDirectory);

        // 复用HttpClient实例,避免资源泄漏
        using var httpClient = new HttpClient();
        // 根据文件大小调整超时时间
        httpClient.Timeout = TimeSpan.FromMinutes(10);

        foreach (var url in fileUrls)
        {
            try
            {
                Console.WriteLine($"开始下载:{url}");

                // 获取响应流(先读取头信息,再处理内容)
                using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
                response.EnsureSuccessStatusCode(); // 校验请求是否成功

                // 确定文件名:优先用服务器返回的文件名,否则用documentId+扩展名
                string fileName = "unknown_document";
                if (response.Content.Headers.ContentDisposition != null)
                {
                    fileName = response.Content.Headers.ContentDisposition.FileName.Trim('"');
                }
                else
                {
                    // 从URL中提取documentId
                    var documentId = url.Split("documentId=")[1].Split('&')[0];
                    fileName = $"document_{documentId}";
                    // 根据Content-Type推断文件扩展名
                    var contentType = response.Content.Headers.ContentType.MediaType;
                    var fileExtension = contentType switch
                    {
                        "application/pdf" => ".pdf",
                        "application/msword" => ".doc",
                        "application/vnd.ms-excel" => ".xls",
                        _ => ""
                    };
                    fileName += fileExtension;
                }

                string savePath = Path.Combine(saveDirectory, fileName);

                // 将响应流写入本地文件
                using var fileStream = new FileStream(savePath, FileMode.Create);
                await response.Content.CopyToAsync(fileStream);

                Console.WriteLine($"下载完成:{savePath}");

                // 可选:添加间隔,避免给服务器造成过大压力
                await Task.Delay(2000);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"下载失败 {url}:{ex.Message}");
            }
        }

        Console.WriteLine("所有下载任务已完成");
    }
}

注意事项:

  • 务必复用HttpClient实例,频繁创建会导致Socket资源泄漏。
  • 如果目标网站需要登录,需在HttpClient中添加Cookie或Authorization请求头(比如Bearer Token)。
  • 可根据实际需求调整超时时间、重试逻辑等。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:13:48