likes
comments
collection
share

前端一次性批量下载多个文件【多种方案及优缺点分析】

作者站长头像
站长
· 阅读数 11

问题描述

最近项目中遇到一个需求,前端接收后端的urls(数组类型),循环批量下载日志文件。如果仅仅同步循环,通过a标签下载,只能下载urls中的最后一个文件,前面的文件会被取消掉,如图:

前端一次性批量下载多个文件【多种方案及优缺点分析】

原因:由于安全限制,现代浏览器会阻止同时进行的多个下载,用户必须对每个下载都进行交互(点击或接受下载)。脚本试图启动超过一个的下载可能会被阻止。这是浏览器制定的安全策略,目前无法绕过。

// 你的 url 数组
let urls = [
    'http://example.com/file1.jpg',
    'http://example.com/file2.jpg',
    // ...
];

function downloadFiles() {
    urls.forEach((url, index) => {
        // 创建新的 a 标签
        var link = document.createElement('a');
        link.href = url;
        link.download = 'file' + (index + 1); // 文件名 如果 要文件原名可以通过url爬取或后端提供
        link.style.display = 'none';   
        // 添加到 body,否则 Firefox 无法启动下载
        document.body.appendChild(link);
        link.click();
        // 移除元素
        document.body.removeChild(link);
    });
}
</script>

方案一:使用**setTimeout**实现文件逐个下载。

使用预定的时间间隔来防止浏览器阻止批量下载操作,模拟用户点击行为。

//setTimeout方法
function downloadFiles(urls) {
    urls.forEach((url, i) => {
        setTimeout(function () {
            let link = document.createElement('a');
            link.href = url;
            link.setAttribute('download', ''); // 或者设置为具体的下载文件名
            link.style.display = 'none';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }, i * 1000);
    });
}

优点:

可以实现多个文件下载。

局限性:

你需要确保每个文件的下载时间都不超过你设定的时间间隔,并且如果你设定的时间间隔太大,那么整个下载过程可能会消耗较长的时间。如果设定时间间隔太小,网络快慢、文件大小等多种因素可能使文件无法全部下载。

前端一次性批量下载多个文件【多种方案及优缺点分析】

如图,把网络调慢后下载会超时,导致没有全部下载完成。

方案二:使用 Blob 对象和 window.URL.createObjectURL

let urls = [
    'http://example.com/file1.jpg',
    'http://example.com/file2.jpg',
    // ...
];

for (let i = 0; i < urls.length; i++) {
    fetch(urls[i])
        .then(response => response.blob())  // 获取文件数据流
        .then(blob => {
            const url = window.URL.createObjectURL(blob);  // 生成文件在浏览器中的链接
            const a = document.createElement('a');
            a.href = url;
            a.download = `file${i + 1}`;  // 文件名
            a.style.display = 'none';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);  // 清除文件链接
        })
        .catch(console.error);
}

解释:对于 urls 数组中的每个 URL,我们使用 fetch API 从服务器获取文件数据,然后使用 response.blob() 将文件数据转化为 Blob 对象。接着使用 window.URL.createObjectURL 创建一个指向这个 Blob 对象的 URL,然后创建一个隐藏的 <a> 标签来进行下载。下载完成后移除 <a> 标签并使用 window.URL.revokeObjectURL 清除对 Blob 的引用,释放内存。

优点:

  • 对于需要特殊认证或者其他复杂 HTTP headers 的文件下载请求更方便,因为 fetch 允许你自定义请求头。
  • 如果你需要处理文件数据,例如将其转为 Blob 对象或者用 FileReader 进行读取等,fetch 更方便。

局限性:

  • 尽管我们在代码中自动点击了 <a> 标签来触发下载,但是在某些浏览器中如果下载操作过于频繁或者数量过多,仍可能被视为不安全的操作而被阻止。
  • 对于大文件,fetch 需要将整个文件数据读入内存,可能导致大量内存消耗。(建议使用Streams API等处理工具,避免一次性加载整个文件到内存,而是通过逐块处理文件数据)
  • 它只能用于同源的请求,或者 CORS 允许的请求。如果文件在别的域名且没有设定 CORS,fetch 无法获取文件。

方案三:使用iframe实现批量下载

urls.forEach((url, index) => {
    // 创建一个新的 iframe 元素
    let iframe = document.createElement('iframe');

    // 将 iframe 的 'src' 属性设置为文件的 URL
    iframe.src = url;

    // 设置 iframe 的 'id' 以便稍后移除
    iframe.id = 'download_iframe_' + index;

    // 将 iframe 设置为隐藏
    iframe.style.display = 'none';

    // 将 iframe 添加到页面中
    document.body.appendChild(iframe);
});

// 一段时间后移除这些 iframe
setTimeout(() => {
    urls.forEach((url, index) => {
        let iframe = document.getElementById('download_iframe_' + index);
        document.body.removeChild(iframe);
    });
}, 5000);

为什么iframe可以批量下载,a标签不可以?

a标签是用来创建超链接的,当点击一个a标签时,浏览器通常会打开一个新页面加载链接的资源。如果这个资源是一个可下载文件,浏览器会启动文件下载。这种方式虽然对单个文件很有效,但是浏览器通常会限制同时打开的新页面数,因此对于多个文件的下载可能不那么有效。

iframe则实际上是在当前页面内嵌入了一个新的浏览器上下文,它可以独立于其它内容加载链接的资源。当在iframe中加载一个可下载文件时,浏览器会直接启动文件下载,而不会影响当前页面的其它内容。

优点:

  • 对于大文件,iframe 可以直接触发下载,不需要将文件数据读入内存,对内存消耗较小。
  • iframe 方法对文件的源没有限制。

局限性:

  • 浏览器可能对大量 iframe 创建与下载行为进行限制,避免恶意下载。
  • 对于需要自定义 HTTP headers(如认证信息)的请求,或者需要预处理文件数据的情况,iframe 不太方便。

还有其他方案,比如Promise.all()异步下载文件等

总结:

由于浏览器的安全策略,批量下载可能会被浏览器视为恶意行为并被阻止。不同浏览器会有不同表现,一般会提示“阻止多个弹窗”或者“您是否允许一次性下载多个文件”,用户体验不够好。最佳的方案是通过服务器先将这些文件打包成一个 .zip 文件,然后让浏览器只下载这一个文件。