python批量上傳

環(huán)境

  • python 3.6 你換成其他3x的版本也沒(méi)關(guān)系
  • flask

項(xiàng)目很小俭尖,主要是演示一下使用flask接收頁(yè)面上傳的文件的方法萨醒。項(xiàng)目包含一個(gè)批量示范頁(yè)面。

演示頁(yè)面文件,為了方便演示。我把html其监,css和js寫(xiě)在一起了

演示頁(yè)面 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樣式,請(qǐng)自行下載限匣,版本v3-->
    <script src="/static/js/jquery-3.2.1.min.js"></script>  <!--jquery腳本抖苦,請(qǐng)自行下載,版本不要求-->
    <script src="/static/js/bootstrap.min.js"></script>   <!--bootstrap腳本,請(qǐng)自行下載,版本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>批量上傳演示頁(yè)</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表示打開(kāi)系統(tǒng)文件目錄
                        accept="image/*"  打開(kāi)相冊(cè)
                        accept="video/*"  打開(kāi)視頻
                        capture  指調(diào)用哪些設(shè)備
                        capture='microphone'   調(diào)用錄音機(jī)
                        capture='camera'       調(diào)用相機(jī)
                        capture='camcorder'    調(diào)用攝像機(jī)
                        file類型的input還有一個(gè) multiple 的單值屬性.表示同時(shí)提供打開(kāi)文件和設(shè)備的選項(xiàng).
                        IOS中, multiple  屬性無(wú)效. 必須寫(xiě)2個(gè)input來(lái)自行實(shí)現(xiàn)選擇.
                        判斷是否是蘋(píng)果手機(jī)的方法:
                        navigator.userAgent.toLowerCase() == "iphone os"
                        由于目前拍攝的照片尺寸過(guò)大,所以暫時(shí)關(guān)閉拍照的功能(沒(méi)有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){
    /*
    上傳進(jìn)度處理函數(shù)
    :params event:       文件上傳事的事件,
    :params progress_cb: 回調(diào)函數(shù),本函數(shù)會(huì)把上傳完成的百分?jǐn)?shù)當(dāng)作地一個(gè)參數(shù)傳入此回調(diào)函數(shù).
    默認(rèn)情況下.會(huì)在控制臺(tái)打印上傳完成度. 注意,100并不代表服務(wù)端完整的接收到了文件.
    只代表頁(yè)面已經(jīng)發(fā)送完了所有的文件內(nèi)容.
    */
    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時(shí)的事件,只要服務(wù)器返回狀態(tài)碼200,就會(huì)執(zhí)行本函數(shù),并并不是代表服務(wù)器返回了正確的信息.
    根據(jù)實(shí)際需要可以覆蓋.
    :params event: 文件上傳事的事件,一般由XMLHttpRequest的upload的事件監(jiān)聽(tīng)器來(lái)傳遞事件.
    :params success_cb:   成功時(shí)的回調(diào)函數(shù),
    :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){
    /*
    上傳文件失敗時(shí)的事件,根據(jù)實(shí)際需要可以覆蓋.
    :params event: 文件上傳事的事件,一般由XMLHttpRequest的upload的事件監(jiān)聽(tīng)器來(lái)傳遞事件.
    :params error_cb: 失敗時(shí)的回調(diào)函數(shù),
    :return: nothing
    */
    let handler = error_cb? error_cb: function(a){console.log(event);};
    handler(event);
}

function batch_upload(options){
    /*
    批量上傳文件. 不限制文件大小
    options = {
    files: 數(shù)據(jù)的序列,
    url: str,
    headers: 鍵值對(duì)對(duì)象,
    success_cb: function,
    error_cb: function,
    progress_cb: function,
    }
    :params files:        input標(biāo)簽的files
    :params url:          上傳的服務(wù)器url
    :params headers:      放入header的參數(shù),是鍵值對(duì)形式的字典,鍵名不要用下劃線,因?yàn)槟遣环弦?guī)范
    :params success_cb:   成功時(shí)的回調(diào)函數(shù),會(huì)把服務(wù)器的返回信息作為第一個(gè)參數(shù)傳入此回調(diào)函數(shù).
    :params error_cb:     失敗時(shí)的回調(diào)函數(shù),會(huì)把錯(cuò)誤信息作為第一個(gè)參數(shù)傳入此回調(diào)函數(shù).
    :params progress_cb:  上傳時(shí)的返回上傳進(jìn)度的回調(diào)函數(shù),會(huì)把頁(yè)面上傳文件的百分書(shū)作為第一個(gè)參數(shù)傳入此回調(diào)函數(shù)..
    :return:              不返回?cái)?shù)據(jù),由回調(diào)函數(shù)返回.
    有關(guān)XMLHttpRequest對(duì)象的詳細(xì)信息,請(qǐng)參考.
    https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
    有關(guān)XMLHttpRequest.send方法的詳細(xì)文檔地址:
    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)};  // 進(jìn)度的回調(diào)函數(shù)
    let comp_func = function(event){upload_complete(event, success_cb)};  // 成功時(shí)的回調(diào)函數(shù)
    let erro_func = function(event){upload_error(event, error_cb)};  // 失敗時(shí)的回調(diào)函數(shù)

    // 構(gòu)造數(shù)據(jù)容器
    let data = new FormData();
    for(let file of files){
        data.append(file.name, file);
    }
    // 新建一個(gè)請(qǐng)求對(duì)象
    let req = new XMLHttpRequest();
    // 添加事件監(jiān)聽(tīng)器
    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之后才能給請(qǐng)求頭賦值
    if(headers){
        /*
        * 傳送請(qǐng)求頭信息,目前服務(wù)端還未做對(duì)應(yīng)的處理.這只是與被給后來(lái)使用的.
        * */
        for(let k in headers){
            req.setRequestHeader(k, headers[k]);
        }
    }
    try{
        req.send(data);  // 404錯(cuò)誤會(huì)直接在此拋出
    }catch(e){
        let handler = error_cb? error_cb: function(ms){console.log(ms);};
        handler(e);
    }

}

/*擴(kuò)展函數(shù)注冊(cè)區(qū)域*/

$.extend({
    batch_upload: batch_upload                              // 批量上傳文件锌历,無(wú)尺寸限制
});

// 點(diǎn)擊選擇按鈕的事件
    $("#select_btn").click(function(){
        $("#select_image").click();
    });

    let progress = function(per){
        // 處理上傳進(jìn)度
        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("操作結(jié)束!");
    };

    // 上傳圖片模態(tài)框,提交按鈕事件
    $("#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>

服務(wù)端的文件也很簡(jiǎn)單 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():
    """上傳頁(yè)面"""
    return render_template("upload_demo.html", page_title="批量上傳")


@app.route("/file/<action>", methods=['post', 'get'])
def file_func(action):
    """
    :param action: 動(dòng)作, 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":
        """獲取文件/圖片,未實(shí)現(xiàn)"""
        pass
    return json.dumps(mes)

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

項(xiàng)目的目錄結(jié)構(gòu)也很簡(jiǎn)單贮庞。


2018-09-27 15-40-54屏幕截圖.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市究西,隨后出現(xiàn)的幾起案子贸伐,更是在濱河造成了極大的恐慌,老刑警劉巖怔揩,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脯丝,居然都是意外死亡商膊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門宠进,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晕拆,“玉大人,你說(shuō)我怎么就攤上這事材蹬∈的唬” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵堤器,是天一觀的道長(zhǎng)昆庇。 經(jīng)常有香客問(wèn)我,道長(zhǎng)闸溃,這世上最難降的妖魔是什么整吆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮辉川,結(jié)果婚禮上表蝙,老公的妹妹穿的比我還像新娘。我一直安慰自己乓旗,他們只是感情好府蛇,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著屿愚,像睡著了一般汇跨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妆距,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天扰法,我揣著相機(jī)與錄音,去河邊找鬼毅厚。 笑死塞颁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祠锣,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼酷窥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了伴网?” 一聲冷哼從身側(cè)響起蓬推,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澡腾,沒(méi)想到半個(gè)月后沸伏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡动分,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年毅糟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澜公。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姆另,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坟乾,到底是詐尸還是另有隱情迹辐,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布甚侣,位于F島的核電站明吩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏殷费。R本人自食惡果不足惜贺喝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宗兼。 院中可真熱鬧躏鱼,春花似錦、人聲如沸殷绍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)主到。三九已至茶行,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間登钥,已是汗流浹背畔师。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牧牢,地道東北人看锉。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓姿锭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親伯铣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呻此,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容