Python PandasとJavascriptで高速にWeb開発 サンプル付き (2/5)
データ分析で用いられるPython Pandasと、Javascriptフレームワークを組み合わせて、シンプルなSPA(単一ページ)型のWeb開発を行う。この方法はWebアプリの行数を減らし、高速に開発できる。このソースは2019年に作成したものであり、現在の環境ではうまく動かない可能性がある。
Webアプリの作成
まずHTMLファイルを作成する。templatesディレクトリにindex.htmlファイルを作る。head内にJavascriptフレームワークとCSSフレームワークを指定する。以下はすべてCDNを利用する場合の指定方法である。ダウンロードする場合はstaticディレクトリに入れる。BootstrapとjQuery、C3.jsとD3.jsにはそれぞれ依存関係があり、以下の順番でないと正しく動かない。
<head> … <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.11/c3.min.css" rel="stylesheet"> <script src="https://d3js.org/d3.v5.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.11/c3.min.js"></script> … </head>
次にFlaskによるWebアプリを作成する。親ディレクトリにapp.pyファイルを作る。ここから上記のHTMLファイルを読み込み、Webアプリ上に表示する。以下の指定で'/'というアドレスに対してindex.htmlを表示するようになる。
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run(debug=True)
自分用のCSS・Javascriptファイルを作っておく。staticディレクトリにmy.cssファイルとmy.jsファイルを作り、HTMLで指定する。
<head> … <link href="/static/my.css?ver=1" rel="stylesheet"> <script src="/static/my.js?ver=2"></script> … </head>
上記の指定にverとあるのは、ブラウザに変更したファイルを必ず読ませるためのテクニックである。ver以降の数字が変わると別のソースと見なされるため、ブラウザが必ず再読み込みするようになる。
FlaskのWebアプリにおいてブラウザから直接アクセスできるファイルは、static配下だけである。他のファイルにアクセスする際は、Flaskに何らかの指定をする必要がある。
Bootstrapの全体構成とボタン
Bootstrapを使うと、Web画面の全体構成ができる。以下は画面を3分割した例である。Bootstrapはレスポンシブデザインに対応しており、スマートフォンなどの小さな画面でも問題なく表示できるようになっている。
この画面のHTMLは以下のようになる。基本的にclassの内部がBootstrapの設定である。全体をheader、footer、sectionで分けて、それぞれにBootstrapのcontainerを指定している。containerには複数のrow(行)とcol(列)を指定できる。colにおいて最大12の数値で横幅を指定する。下記の例では上部の行では12、下部の行では3と9を指定している。構造を見やすくするため、丸みを付けた枠線を指定している。rowやcolは入れ子構造にする事もできる。
<header class="container"> <h1>Irisデータ分析</h1> </header> <section class="container"> <div class="row"> <div class="col-sm-12 border border-info rounded py-2"> <button type="button" class="btn btn-default" style="margin:10px;">ボタン1</button> <button type="button" class="btn btn-primary" style="margin:10px;">ボタン2</button> </div> </div> <div class="row"> <div class="col-sm-3 border border-info rounded py-3"> </div> <div class="col-sm-9 border border-info rounded py-3"> </div> </div> </section> <footer class="container"></footer>
Bootstrapではボタンなどの標準的なデザインも提供している。上記の例ではボタン1にdefaultを、ボタン2にprimaryを指定している。この指定によりボタンの色を変えられる。
Bootstrapをそのまま使うと文字サイズが大きめになるので、以下の指定をmy.cssに入れて全体の文字サイズを小さくしている。
html { font-size: 15px; }
元データ取得とボタン処理
分析に使うデータは、統計や機械学習で一般的なIrisのデータである。これをWebから取得する処理を実装する。まずHTML上にあるボタンからJavascriptを呼び出す。下記の指定によりボタンをクリックした際にgetWebData関数を実行する。
<button type="button" class="btn btn-primary" onclick="getWebData()" style="margin:10px;">Web元データ取得</button>
次にJavascriptからPythonを呼び出す。下記の$.post関数により、Flaskが管理するWeb処理(/get-webdata)を実行できる。$.post関数はjQueryの機能である。
function getWebData() { $.post('/get-webdata') .done(function(ret) { alert('Web元データを取得しました'); }) .fail(function(ret) { alert('Web元データの取得に失敗しました'); }); }
$.post関数は、HTMLのformで使われるpostと基本的に同じものである。HTMLでのpostは別画面の遷移が必須になるが、Javascriptでpostを使うと画面の遷移を行う必要がなくなる。これによりSPA(単一ページ)型のWebアプリを作成できる。画面遷移をなくして処理を簡略化できる事が、Javascriptを使う利点になる。
最後にPythonにより元データを取得する。元データには列名が入っていないので、Pandasを使って列名を追加している。Pandasを使うとCSVファイルの読み書きが簡単にできる。またwriteにより先頭行にコメントを入れている。Excelで読むことを想定して、出力ファイルはShift JIS(CP932)にしている。元データはsrcディレクトリに格納している。
import os import pandas as pd @app.route('/get-webdata', methods=['POST']) def get_webdata(): df_src = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None) df_src.columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'name'] f = open('src/iris.csv', 'w', encoding='CP932') f.write('Webから取得\n') f.close() df_src.to_csv('src/iris.csv', index=False, mode='a', encoding='CP932') return 'ok'
ダウンロードとアップロード
元データのダウンロードとアップロードができるようにする。ファイルをShift JISにしているので、Excelで内容を加工してアップロードできる。
まずダウンロードから作成する。ダウンロードはHTMLのpostでも画面遷移せず、SPA(単一ページ)型に収まる。
ダウンロードボタンのHTMLは以下のようになる。ダウンロード時の画面のちらつきを抑えるため、iframeによるテクニックを使っている。
<form method="post" target="hidden-download" action="/download"> <input type="submit" class="btn btn-primary" value="元データダウンロード" style="margin:10px;"> </form> … <iframe style="display:none" name="hidden-download"></iframe>
Javascriptは不要なので、Pythonによるダウンロード処理だけでよい。Flask管理外のファイルを扱うため、send_fileという関数を使っている。
from flask import send_file … @app.route('/download', methods=['POST']) def download(): return send_file('src/iris.csv', as_attachment=True, attachment_filename='iris.csv')
次に、アップロードを作成する。アップロードには専用のHTMLによる書き方があり、以下のように記載する。
<form id="upload-form" style="margin:10px;"> <input type="file" name="file"> <button type="button" class="btn btn-primary" onclick="upload()">元データアップロード</button> </form>
アップロード用のJavascriptは以下のようになる。ファイルを送る部分である。$.ajaxという関数が出てくるが、実は今までの$.post関数は$.ajax関数を簡略表記したものである。処理の内容は基本的に同じで、サーバに対してpostなどを行う関数である。アップロード用の機能は$.post関数にはないので、$.ajax関数による細かい指定を利用する。
関数名にあるAJAXは「非同期Javascript XML通信」の意味である。今回は非同期通信を使わず、XMLでなくJSONを使う。$.ajax関数は単にpostをするためのツールとして扱う。
FormData関数により、アップロードするファイルの内容を受け取る事ができる。$('#upload-form')とあるのは、jQueryによるHTMLの指定である。このようにJavascriptからHTMLを操作する機能をDOMと呼ぶ。'#upload-form'の部分はidによるHTMLの指定で、これはCSSの場合と同様の形式である。
function upload() { let fd = new FormData($('#upload-form').get(0)); $.ajax({ type: 'POST', url: '/upload', data: fd, processData: false, contentType: false, dataType: 'html' }).done(function(ret) { if (ret == 'fileErr') { alert('ファイルが不正です'); } else { alert('アップロードしました'); } }).fail(function(ret) { alert('アップロードに失敗しました'); }); }
アップロード用のPythonは以下のようになる。ファイルを受け取る部分である。requestによりpostの内容を受け取る事ができる。CSVファイルでなければエラーを返すようになっている。
from flask import request … @app.route('/upload', methods=['POST']) def upload(): file = request.files['file'] filename = file.filename if filename == '' or not filename.lower().endswith('.csv'): return 'fileErr' file.save('src/iris.csv') return 'ok'
ボタン全体
ボタン全体のHTMLは以下のようになる。Bootstrapの幅12の所に入れている。Bootstrapのform-inline指定により、ボタンを横並びにしている。
<div class="col-sm-12 border border-info rounded py-2"> <div class="form-inline"> <button type="button" class="btn btn-primary" onclick="getWebData()" style="margin:10px;">Web元データ取得</button> <form method="post" target="hidden-download" action="/download"> <input type="submit" class="btn btn-primary" value="元データダウンロード" style="margin:10px;"> </form> <form id="upload-form" style="margin:10px;"> <input type="file" name="file"> <button type="button" class="btn btn-primary" onclick="upload()">元データアップロード</button> </form> </div> </div>
ボタン全体の画面表示は以下のようになる。
元データのCSVファイル
元データのCSVファイルは以下のようになる。先頭行にあるコメントを変える事で、編集内容を確認できる。