python批量上传

环境

  • python 3.6 你换成其他3x的版本也没关系
  • flask

项目很小,主要是演示一下使用flask接收页面上传的文件的方法。项目包含一个批量示范页面。

演示页面文件,为了方便演示。我把html,css和js写在一起了

演示页面 demo.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
    <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <!--bootstrap样式,请自行下载,版本v3-->
    <script src="/static/js/jquery-3.2.1.min.js"></script>  <!--jquery脚本,请自行下载,版本不要求-->
    <script src="/static/js/bootstrap.min.js"></script>   <!--bootstrap脚本,请自行下载,版本v3-->    
    <style>
    body {
      font-size: 14px;
    }
    .my_div {
  min-height: 400px;
  background-color: #f7f7f7;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.my_div .json_div {
  min-width: 100%;
  padding: 10px;
}
.my_div .json_div >.json_div_inner {
  min-width: 100%;
  min-height: 50px;
  border: 1px solid #d3d3d3;
  border-radius: 5px;
  padding: 10px;
}
.my_div .json_div >.json_div_inner >li {
  margin-left: 20px;
}
.my_div #my_progress {
  width: 96%;
  position: relative;
}
.my_div #my_progress >.my_per {
  color: #ff7d7f;
  position: absolute;
  display: block;
  width: 4em;
  top: 1px;
  left: 50%;
}
.my_div .my_bottom {
  padding: 10px 10px 20px;
}
.my_div .my_bottom >#select_image {
  display: none;
}

    </style>
    <title>批量上传演示页</title>
</head>
<body>

    <div class="container-fluid">
        <div class="row">
            <div class="col-lg-6 col-lg-offset-3 col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12 my_div">
                <div class="json_div">
                    <ul class="json_div_inner"></ul>
                </div>
                <div id="my_progress" class="progress">
                  <div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">

                  </div>
                    <span class="my_per">0%</span>
                </div>
                <div class="my_bottom">
                    <!--
                        <input id="select_image" type="file" accept="image/*" capture="camera" multiple>
                        accept表示打开系统文件目录
                        accept="image/*"  打开相册
                        accept="video/*"  打开视频
                        capture  指调用哪些设备
                        capture='microphone'   调用录音机
                        capture='camera'       调用相机
                        capture='camcorder'    调用摄像机
                        file类型的input还有一个 multiple 的单值属性.表示同时提供打开文件和设备的选项.
                        IOS中, multiple  属性无效. 必须写2个input来自行实现选择.
                        判断是否是苹果手机的方法:
                        navigator.userAgent.toLowerCase() == "iphone os"
                        由于目前拍摄的照片尺寸过大,所以暂时关闭拍照的功能(没有capture="camera" 和 multiple属性)
                    -->
                    <input id="select_image" type="file" multiple size="200">
                    <button id="select_btn" class="btn btn-default btn-primary">选择</button>
                    <button id="upload_btn" class="btn btn-default btn-primary">上传</button>
                </div>
            </div>
        </div>

    </div>

</body>
<script>
function upload_progress(event, progress_cb){
    /*
    上传进度处理函数
    :params event:       文件上传事的事件,
    :params progress_cb: 回调函数,本函数会把上传完成的百分数当作地一个参数传入此回调函数.
    默认情况下.会在控制台打印上传完成度. 注意,100并不代表服务端完整的接收到了文件.
    只代表页面已经发送完了所有的文件内容.
    */
    if (event.lengthComputable) {
        var complete_percent = Math.round(event.loaded * 100 / event.total);
        var handler = progress_cb?progress_cb: function(num){console.log(`上传完成度:${num}`)};
        handler(complete_percent);
    }else{}
}

function upload_complete(event, success_cb){
    /*
    上传文件success时的事件,只要服务器返回状态码200,就会执行本函数,并并不是代表服务器返回了正确的信息.
    根据实际需要可以覆盖.
    :params event: 文件上传事的事件,一般由XMLHttpRequest的upload的事件监听器来传递事件.
    :params success_cb:   成功时的回调函数,
    :return: nothing
    */
    let str = event.target.responseText;
    let handler = success_cb? success_cb: function(a){console.log(a);};
    handler(str);
}

function upload_error(event, error_cb){
    /*
    上传文件失败时的事件,根据实际需要可以覆盖.
    :params event: 文件上传事的事件,一般由XMLHttpRequest的upload的事件监听器来传递事件.
    :params error_cb: 失败时的回调函数,
    :return: nothing
    */
    let handler = error_cb? error_cb: function(a){console.log(event);};
    handler(event);
}

function batch_upload(options){
    /*
    批量上传文件. 不限制文件大小
    options = {
    files: 数据的序列,
    url: str,
    headers: 键值对对象,
    success_cb: function,
    error_cb: function,
    progress_cb: function,
    }
    :params files:        input标签的files
    :params url:          上传的服务器url
    :params headers:      放入header的参数,是键值对形式的字典,键名不要用下划线,因为那不符合规范
    :params success_cb:   成功时的回调函数,会把服务器的返回信息作为第一个参数传入此回调函数.
    :params error_cb:     失败时的回调函数,会把错误信息作为第一个参数传入此回调函数.
    :params progress_cb:  上传时的返回上传进度的回调函数,会把页面上传文件的百分书作为第一个参数传入此回调函数..
    :return:              不返回数据,由回调函数返回.
    有关XMLHttpRequest对象的详细信息,请参考.
    https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
    有关XMLHttpRequest.send方法的详细文档地址:
    https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/send
    */
    let files = options['files'];
    let file_data = options['file_data'];
    let url = options['url'];
    let headers = options['headers'];
    let success_cb = options['success_cb'];
    let error_cb = options['error_cb'];
    let progress_cb = options['progress_cb'];
    let prog_func = function(event){upload_progress(event, progress_cb)};  // 进度的回调函数
    let comp_func = function(event){upload_complete(event, success_cb)};  // 成功时的回调函数
    let erro_func = function(event){upload_error(event, error_cb)};  // 失败时的回调函数

    // 构造数据容器
    let data = new FormData();
    for(let file of files){
        data.append(file.name, file);
    }
    // 新建一个请求对象
    let req = new XMLHttpRequest();
    // 添加事件监听器
    req.upload.addEventListener("progress", prog_func, false);
    req.addEventListener("load", comp_func, false);
    req.addEventListener("error", erro_func, false);
    req.addEventListener("abort", erro_func, false);
    req.open("post", url);
    // 必须在open之后才能给请求头赋值
    if(headers){
        /*
        * 传送请求头信息,目前服务端还未做对应的处理.这只是与被给后来使用的.
        * */
        for(let k in headers){
            req.setRequestHeader(k, headers[k]);
        }
    }
    try{
        req.send(data);  // 404错误会直接在此抛出
    }catch(e){
        let handler = error_cb? error_cb: function(ms){console.log(ms);};
        handler(e);
    }

}

/*扩展函数注册区域*/

$.extend({
    batch_upload: batch_upload                              // 批量上传文件,无尺寸限制
});

// 点击选择按钮的事件
    $("#select_btn").click(function(){
        $("#select_image").click();
    });

    let progress = function(per){
        // 处理上传进度
        console.log(`per is ${per}`);
        $("#my_progress>.progress-bar").attr("style", `width: ${per}%;`).attr("aria-valuenow", `${per}`);
        if(per > 52){
            $(".my_per").css("color", "#fff");
        }
        $(".my_per").text(`${per}%`);
    };

    // 上传图片按钮事件
    $("#select_image").change(function(){
        let ul = $(".json_div_inner:first");
        ul.empty();
        let files = this.files;
        for(let f of files){
            ul.append(`<li id="${f.name}"><span>${f.name}</span></li>`);
        }
    });
    let call_back = function(){
        $("#upload_btn, #select_btn").attr("disabled", false).removeClass("disabled");
        alert("操作结束!");
    };

    // 上传图片模态框,提交按钮事件
    $("#upload_btn").click(function(){
            let $obj = $("#select_image");
            let files = $obj[0].files;
            let opts = {
                "url": "/file/save",
                "files": files,
                "success_cb": call_back,
                "error_cb": call_back,
                "progress_cb": progress
            };
            $("#upload_btn, #select_btn").attr("disabled", true).addClass("disabled");
            $.batch_upload(opts);
        });
</script>
</html>

服务端的文件也很简单 server.py

# -*- coding: utf-8 -*-
from flask import Flask
from flask import render_template
from flask import request
import json
import os


app = Flask(__name__)
port = 7002
root_dir = os.path.dirname(os.path.realpath(__file__))
resource_dir = os.path.join(root_dir, 'resource')

@app.route("/")
def upload_demo():
    """上传页面"""
    return render_template("upload_demo.html", page_title="批量上传")


@app.route("/file/<action>", methods=['post', 'get'])
def file_func(action):
    """
    :param action: 动作, save/get(保存/获取)
    :return:
    """
    mes = {"message": "success"}
    if action == "save":
        """保存文件"""
        if os.path.exists(resource_dir):
            pass
        else:
            os.makedirs(resource_dir)

        for key_name, file_storage in request.files.items():
            if file_storage is not None:
                file_name = file_storage.filename
                file_storage.save(os.path.join(resource_dir, file_name))
                file_storage.close()
    elif action == "get":
        """获取文件/图片,未实现"""
        pass
    return json.dumps(mes)

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=port, debug=True, threaded=True)

项目的目录结构也很简单。


2018-09-27 15-40-54屏幕截图.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容