AB型技術系 主に備忘録

ほぼプログラム関連の備忘録

bottle + pythonでCSVファイルをアップロードした時のBOM問題

bottle + pythonCSVファイルをアップロードした時にはまった時のメモです。

index.pyを下記の用に実装しました。
CSVをアップロードしてサーバ上には保存せずバイトデータを文字列に変換して画面に表示。
最終的にCSVexcelで保存したファイルをアップロードします。

サンプルプログラム1

index.py

from bottle import post, get, run, request, response,template

@get('/upload/<name>')
def upload_page(name):
    return template("view/upload/upd_" + name + ".html")

@post('/upload/<name>')
def upload(name):
    res = {}
    data = request.files.data
    if data and data.file:

        # ファイル読み込み
        data = data.file.read()

        # 文字列型にデコード
        data = data.decode()
        res["str"] = data
    return template("view/upload/upd_" + name + "_result.html", res=res)

アップロード用html

<!DOCTYPE html>
<html lang="ja">
   <head>
       <meta charset="UTF-8">
       <title>アップロード</title>
   </head>
   <body>
       <h1>アップロード</h1>
       <form action="upload/dividend" method="post" enctype="multipart/form-data">
           ファイル:<input type="file" name="data"/>
           <input type="submit" value="アップロード" />
       </form>
    </body>
</html>

アップロード結果用html

<!DOCTYPE html>
<html lang="ja">
   <head>
       <meta charset="UTF-8">
       <title>アップロード結果</title>
   </head>
   <body>
       <h1>アップロード結果</h1>
       {{res["str"]}}
    </body>
</html>

テスト用CSV

cd,value
001,データ1
002,データ2

アップロード結果

f:id:freelancer13:20220127023142p:plain
アップロード結果

読み込んだCSVファイルの中身が表示されました。

サンプルプログラム2

CSVデータを操作しやすいように標準ライブラリのcsv.DictReaderを使用するように変更しました。

index.py

import io
import csv
from bottle import post, get, run, request, response,template

@get('/upload/<name>')
def upload_page(name):
    return template("view/upload/" + name + ".html")

@post('/upload/<name>')
def upload(name):
    res = {}
    data = request.files.data
    if data and data.file:

        # ファイル読み込み
        data = data.file.read()

        # 文字列型にデコード
        data = data.decode()
       
      # 文字列をファイルとして扱う
        strio = io.StringIO(data)

        # csvをDict型で読み込む
        data = csv.DictReader(strio)

        res["csv"] = data
    return template("view/upload/" + name + "_result.html", res=res)

アップロード結果用html

<!DOCTYPE html>
<html lang="ja">
   <head>
       <meta charset="UTF-8">
       <title>アップロード結果</title>
   </head>
   <body>
       <h1>アップロード結果</h1>
       %for dt in res["csv"]:
          {{dt}}     
       %end
    </body>
</html>

アップロード結果

f:id:freelancer13:20220127030422p:plain
アップロード結果

csv.DictReaderによりcsvデータを1行目の項目をkeyとしたDict型の配列に変換してくれます。
dt["date"]と記述すれば個別に値を出力することができます。

最後にExcelから出力したcsvで試します。
テスト用CSV(Excelで保存)

date,seq,value
20220101,1,20220101_データ1
20220101,2,20220101_データ2
20220102,1,20220102_データ1

アップロード結果

f:id:freelancer13:20220127030856p:plain
アップロード結果

一つ目のkeyに\ufeffが表示されてますね。
excelcsvを出力するとUTF-8のBOM付で出力されるようです。

サンプルプログラム3

BOMに対応するためdecode部分を変更しました。

# 文字列型にデコード
data = data.decode("utf-8-sig")

アップロード結果

f:id:freelancer13:20220127031501p:plain
アップロード結果

無事に\ufeffが表示されなくなりました。