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

Dash大文件上传优化:如何实现分块/流式上传规避浏览器崩溃?

问题:Dash大文件上传导致浏览器崩溃,如何实现分块/流式上传?

我正在开发一款Dash应用,需要支持最小100MB的大文件上传,因此设置了max_size=-1(无文件大小限制),相关代码如下:

dcc.Upload(
 id="upload_dataset",
 children=html.Div(
  [
    "Drag and Drop or ",
    html.A(
      "Select File",
      style={
              "font-weight": "bold",
            },
            title="Click to select file.",
    ),
   ]
 ),
 multiple=False,
 max_size=-1,
)

上传的文件会保存到服务器端,但dcc.Upload组件的contents属性会将完整文件数据以Base64字符串格式存储,且在发送至服务器前会先存入浏览器内存。小文件时这种方式没问题,但大文件会导致浏览器崩溃、应用冻结。

请问是否有办法绕过这一默认行为,实现文件分块或流式上传?如何通过dcc.Upload组件或其他方式实现?


解决方案

一、绕过dcc.Upload,用原生JavaScript+Flask路由实现分块上传

dcc.Upload的设计本身不支持分块上传,最可靠的方式是直接用前端JS处理文件分块,再通过自定义Flask路由接收块数据,最后在服务器端合并。

步骤1:替换dcc.Upload为原生HTML上传组件

在Dash布局中加入原生文件输入和触发按钮:

html.Div([
    html.Input(id="file_input", type="file", style={"display": "none"}),
    html.Button("选择文件", id="upload_btn", n_clicks=0),
    html.Div(id="upload_status")
])

步骤2:用回调触发文件选择

让按钮点击触发原生文件输入的选择事件:

@app.callback(
    Output("file_input", "n_clicks"),
    Input("upload_btn", "n_clicks"),
    prevent_initial_call=True
)
def trigger_file_input(n_clicks):
    return n_clicks

步骤3:编写前端JS处理分块上传

在Dash应用中嵌入自定义JS,实现文件分块、逐块发送:

document.addEventListener('DOMContentLoaded', function() {
    const fileInput = document.getElementById('file_input');
    const statusDiv = document.getElementById('upload_status');
    
    fileInput.addEventListener('change', async function(e) {
        const file = e.target.files[0];
        if (!file) return;
        
        const chunkSize = 10 * 1024 * 1024; // 10MB每块
        const totalChunks = Math.ceil(file.size / chunkSize);
        let currentChunk = 0;
        
        statusDiv.textContent = `开始上传:${file.name}`;
        
        while (currentChunk < totalChunks) {
            const start = currentChunk * chunkSize;
            const end = Math.min(start + chunkSize, file.size);
            const chunk = file.slice(start, end);
            
            const formData = new FormData();
            formData.append('fileChunk', chunk);
            formData.append('fileName', file.name);
            formData.append('chunkIndex', currentChunk);
            formData.append('totalChunks', totalChunks);
            
            try {
                const response = await fetch('/upload_chunk', {
                    method: 'POST',
                    body: formData
                });
                
                if (!response.ok) throw new Error('上传失败');
                statusDiv.textContent = `上传进度:${Math.floor((currentChunk+1)/totalChunks*100)}%`;
                currentChunk++;
            } catch (err) {
                statusDiv.textContent = `上传出错:${err.message}`;
                break;
            }
        }
        
        if (currentChunk === totalChunks) {
            // 所有块上传完成,通知服务器合并
            await fetch('/merge_chunks', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({fileName: file.name, totalChunks: totalChunks})
            });
            statusDiv.textContent = `上传完成:${file.name}`;
        }
    });
});

将这段JS通过app.index_string嵌入到Dash应用的HTML中:

app.index_string = '''
<!DOCTYPE html>
<html>
    <head>
        {%metas%}
        <title>{%title%}</title>
        {%favicon%}
        {%css%}
    </head>
    <body>
        {%app_entry%}
        <footer>
            {%config%}
            {%scripts%}
            {%renderer%}
            <script>
                // 上述JS代码
            </script>
        </footer>
    </body>
</html>
'''

步骤4:在Flask后端实现分块接收与合并

为Dash的Flask app添加自定义路由:

import os
from flask import request, jsonify

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

@app.server.route('/upload_chunk', methods=['POST'])
def upload_chunk():
    file_chunk = request.files['fileChunk']
    file_name = request.form['fileName']
    chunk_index = int(request.form['chunkIndex'])
    total_chunks = int(request.form['totalChunks'])
    
    # 创建临时目录存储块
    temp_dir = os.path.join(UPLOAD_FOLDER, f'temp_{file_name}')
    os.makedirs(temp_dir, exist_ok=True)
    
    chunk_path = os.path.join(temp_dir, f'chunk_{chunk_index}')
    file_chunk.save(chunk_path)
    
    return jsonify({'status': 'success'})

@app.server.route('/merge_chunks', methods=['POST'])
def merge_chunks():
    data = request.get_json()
    file_name = data['fileName']
    total_chunks = data['totalChunks']
    
    temp_dir = os.path.join(UPLOAD_FOLDER, f'temp_{file_name}')
    final_path = os.path.join(UPLOAD_FOLDER, file_name)
    
    # 合并所有块
    with open(final_path, 'wb') as final_file:
        for i in range(total_chunks):
            chunk_path = os.path.join(temp_dir, f'chunk_{i}')
            with open(chunk_path, 'rb') as chunk_file:
                final_file.write(chunk_file.read())
            os.remove(chunk_path)
    os.rmdir(temp_dir)
    
    return jsonify({'status': 'merged'})

二、使用第三方Dash组件快速集成

如果不想手动实现原生逻辑,可以使用dash-uploader组件,它原生支持大文件分块上传:

  1. 安装组件:
    pip install dash-uploader
    
  2. 基本使用示例:
    import dash_uploader as du
    import dash
    import dash_html_components as html
    
    app = dash.Dash(__name__)
    du.configure_upload(app, folder="uploads")
    
    app.layout = html.Div([
        du.Upload(
            id='uploader',
            text="Drag and Drop or Select File",
            text_completed="Uploaded: ",
            cancel_button=True,
            pause_button=True,
        ),
        html.Div(id='output'),
    ])
    
    @app.callback(
        Output('output', 'children'),
        Input('uploader', 'isCompleted'),
        State('uploader', 'fileNames'),
        prevent_initial_call=True,
    )
    def update_output(isCompleted, fileNames):
        if isCompleted:
            return html.Ul([html.Li(f) for f in fileNames])
        return ""
    
    if __name__ == '__main__':
        app.run_server(debug=True)
    

注意事项

  • 分块大小建议设置在5-20MB之间,平衡上传效率和请求次数
  • 服务器端需确保有足够的临时存储空间
  • 可添加断点续传逻辑(记录已上传的块),提升大文件上传可靠性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:45:47