はじめに
この記事では、Streamlitの概要を説明し、Streamlitを使ってデータ分析Webアプリを実際に開発する中でStreamlitの機能をいろいろと紹介していきます。最終的に以下のようなApacheやTomcatなどのアクセスログを解析するWebアプリをつくります。
Streamlitとは
Streamlitは、Pythonで実装されたオープンソースのWebアプリケーションのフレームワークであり、機械学習やデータサイエンス向けのグラフィカルなWebアプリを簡単に作成して全世界に公開(クラウドサービスにデプロイ)できます。
主な特徴
- Pythonのみで実装可能(HTMLやCSS、JavaScriptなどフロントエンドのコードを書かなくていい)
 - 豊富なウィジェットが利用可能
 - Google ColabやJupyter Notebookで作成したPythonのコードがほぼそのまま利用できる
 - 開発したアプリをStreamlit Cloudで全世界に簡単に公開可能
 - コードの修正が即座に反映され、確認できる
 
どのようなものがつくれるのか
Streamlitの公式サイトのギャラリーを見ると具体的なイメージができるのではないかと思います。
インストールとデモの実行
以下のコマンドでインストールします。
$ pip install streamlit
まずは、簡単なデモを見てみましょう。以下のコマンドでアプリケーションが起動します。
$ streamlit hello
シンプルなコードで、以下のようにグラフィカルなWebアプリケーションが実装できます。

主な機能
以下に特徴的な機能をいくつか紹介します。
マジックコマンド
マジックコマンドとは、表示したいもの(マークダウン、データ、グラフ)の変数名を記述するだけで、アプリに表示させる機能です。例えば、変数名だけをソースコードに書くと、
x = 10
x
画面上に変数の値が表示されます。

PandasのDataFrameを格納した変数だけを記述すると、
import pandas as pd
df = pd.DataFrame({'col1': [1,2,3]})
df
画面上にテーブルを表示できます。

matplotlibを使えば、
import matplotlib.pyplot as plt
import numpy as np
arr = np.random.normal(1, 1, size=100)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)
fig
簡単なコードでのウィジェットの追加
ウィジェットを追加することは、変数を宣言することと同じです。 バックエンドを作成したり、ルートを定義したり、HTTPリクエストを処理したり、フロントエンドに接続したり、HTML、CSS、JavaScriptなどを作成したりする必要はありません。
例えば、ラジオボックスを表示したければ、streamlitをインポートして、
import streamlit as st
次の1行を記述すればOKです。
st.radio("好きなマイケルは?", ('ジャクソン', 'ジョーダン', 'ホフマン'))
そして、戻り値に選択した値がセットされます。
簡単なデプロイ
Streamlitで開発したアプリを自身のGitHubにコミットし、Streamlit社が提供するStreamlit Cloudというクラウドサービスと連携すると、自動的にアプリをデプロイし、インターネット上に公開することができます。デプロイしたアプリは、以下のような画面で管理できます。
オートリロード
Streamlitで開発したアプリへのソースコードの修正は即座に反映されます。GitHubに修正をコミットすれば、Streamlit Cloud上のアプリにすぐに修正が反映されます。ローカル環境で開発している場合は、ソースコードの修正後に画面右上の「Rerun」をクリックすることで、その修正が反映されたアプリを動かすことができます。
応用編 〜 データ分析Webアプリの開発
では、実際にデータ分析Webアプリを開発してみましょう。ここでは、Apacheなどのアクセスログを解析するアプリをつくり、Streamlit Cloudにデプロイしてインターネットにアプリを公開します。
簡単なWebアプリの作成
まずは画面に簡単な文字列を表示するアプリケーションを作成して、GitHubにコミットしましょう。GitHubにアクセスし、適当な名前でアプリケーション用のリポジトリーを作成して下さい。
リポジトリーを作成したら、適当なファイル名でpythonファイルを作成します。公式ドキュメントにあるとおり、streamlit_app.pyというファイル名にすると、URLにファイル名を含めずにアクセスできます(少しURLを短縮できます)。「Add new file」ボタンをクリックして、つくってしまいましょう。
マジックコマンドで以下のコードを記述すれば、画面上にx: 10と表示することができます。
x = 10
'x: ', x 
「Commit new file」ボタンをクリックして、保存します。Webアプリの作成は、これで完了です。
Streamlit Cloudアカウントの登録
次に、 https://streamlit.io/cloud にアクセスします。以下のような画面が表示されるので、「Get Started」ボタンをクリックします。

GoogleまたはGitHubの既存アカウントを使用するか、メールアドレスを入力してStreamlit Cloudアカウントを作成します。

以降はGitHubの既存アカウントを使用します。よくあるOAuthのフローで、Streamlit CloudがGitHubアカウントの情報にアクセスすることを許可し、

確認のためのパスワードを入力します。

アカウントの情報などを入力したら、

「New app」ボタンをクリックして、アプリケーションのプロファイルを作成します。

ここで再度OAuthの同意画面が表示されるので、同意しましょう。

Streamlit Cloudで公開するアプリの情報を入力します。ここでのアプリの情報とは、先ほど作成したアプリのGitHubリポジトリ名やパスなどです。

「Deploy!」ボタンをクリックすると、Streamlit Cloud上でアプリが起動します。

正常に起動できたら、以下のように画面にx: 10と表示されます。

データ分析Webアプリの開発
簡単なWebアプリケーションができあがったので、これに機能を追加して、アクセスログを解析するWebアプリケーションにしていきましょう。
アップローダーの追加
まずは、アクセスログをアップロードする機能(アップローダー)がなければ始まりません。アップローダーは、以下のように追加できます。
import streamlit as st
uploaded_file = st.file_uploader("アクセスログをアップロードしてください。")
アップロードしたアクセスログファイルの読み込み
ファイルがアップロードできたら、st.file_uploader()の戻り値にファイルオブジェクトが格納されるので、これを使って処理を開始します。Apacheのアクセスログであれば、以下のようにpd.read_csv()で読み込めばOKです。
import pandas as pd
if uploaded_file is not None:
    df = pd.read_csv(
        uploaded_file,
        sep=r'\s(?=(?:[^"]*"[^"]*")*[^"]*$)(?![^\[]*\])',
        engine='python',
        na_values='-',
        header=None)
アクセスログファイルを読み込んだら、先頭5行を表示してみましょう。
    st.markdown('### アクセスログ(先頭5件)')
    st.write(df.head(5))
ここまでできたら、あとは変数dfに格納されたPandasのDataFrame型のアクセスログデータをいろいろな観点で解析できるでしょう。Apacheのアクセスログの解析においては、以前書いたこの記事とともにつくったノートブックがほぼそのまま使えそうだったので、これを利用することにしました。前述しましたが、Jupyter NotebookやGoogle Colabでつくったノートブックのコードがほぼそのまま活用できるのが、Streamlitのメリットと言えます。
このようにしてつくったアクセスログ解析用のWebアプリケーションがこれです。
このページの中にある「Open in Streamlit」ボタンをクリックすると、Streamlit Cloud上で動作するこのアプリケーションを試してみることができます。上記、GitHubリポジトリー内にサンプルのアクセスログがありますので、それをアップロードしてください。
顧客環境のアクセスログをStreamlit Cloudにアップロードしないように注意して下さい(Streamlit社に利用されるとは思いませんが...)。機密情報を含むアクセスログを解析したい場合は、以下の手順でローカルでこのアプリケーションを起動して下さい。
$ git clone https://github.com/k-tamura/mossala.git
$ cd mossala/
$ pip install -r requirements.txt
$ streamlit run streamlit_app.py
アクセスログ解析用のノートブックをアクセスログ解析用のWebアプリケーションに変えるために使ったStreamlitの機能を、以下にいくつか解説します。
ページタイトルとページアイコン
ページタイトルとページアイコンは、以下のように設定できます。この場合、icon.pngというファイル名でアイコンの画像ファイルをルートディレクトリーにおいておく必要があります。
st.set_page_config(page_title="メインページ", page_icon='icon.png')
st.title("Multiple OSS Access Log Analyzer")
これにより、ブラウザーのタブの部分が以下のように表示されます。

列選択のためのウィジェット
何番目の列を解析に使用するかを選択させるための、複数選択ができる入力ボックスは、以下のように実装します。第2引数は選択可能な値のリストで、第3引数はデフォルトで選択される値のリストです。
    usecols = st.multiselect(
        '何番目の列を解析の対象にしますか?',
        [0, 3, 4, 5, 6],
        [0, 3, 4, 5, 6])
エラーメッセージの表示
st.error()でエラーメッセージを表示することができます。入力チェックはこれで実装します。
if len(usecols) == 0 or len(names) == 0:
    st.error('解析対象の列が指定されていません。')
ヘルプテキスト
画面の項目にヘルプテキストを付けることもできます。
    help_txt = '''
        以下のフォーマット文字列を解析可能です。詳細については、[公式ページ](https://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html)を参照して下さい。
        | 列名 | フォーマット文字列 | 説明 | 
        |:-----|:-----:|:-----|
        | Remote Host | `%h` | リモートホスト |
        | Time | `%t` | リクエストを受付けた時刻 | 
        | Request | `\"%r\"` | リクエストの最初の行 | 
        | Status | `%>s` | ステータス | 
        | Size | `%b` | レスポンスのバイト数 | 
        | User Agent | `\"%{User-agent}i\"` | リクエストのUser-agentヘッダの内容 | 
        | Response Time | `%D` または `%T` | リクエストを処理するのにかかった時間 |         
        '''
    names = st.multiselect(
        'これらの列を何を意味しますか?',
        ['Remote Host', 'Time', 'Request', 'Status', 'Size', 'User Agent', 'Response Time'],
        default_names, help=help_txt)
ウィジェット用の関数の引数helpにマークダウン書式で書いた値を渡せば、次のようなヘルプテキストが表示されます。
世界地図の表示
st.map()を使用すると、地図上の指定した場所にプロットすることもできます。引数には、緯度と経度を意味するlat列とlon列を含むDataFrameを渡します。この機能とIPアドレスから場所と緯度・経度を取得するライブラリーを利用して、アクセスログのリモートホスト(アクセス元のクライアント)の地域を地図上にプロットします。
st.map(df)
プログレスバー
時間がかかる処理に対してはプログレスバーを表示さた方がよいでしょう。アクセスログも件数が増えると解析には時間がかかるので、以下のように大雑把な進捗状況がわかるプログレスバーを導入しています。
my_bar = st.progress(0)
# ・・・何らかの処理
my_bar.progress(10)
# ・・・何らかの処理
my_bar.progress(20)
# ・・・何らかの処理
# ・・・
my_bar.progress(100)
ログ出力
一般的なWebアプリケーションにおいてログ出力は重要です。Streamlitはログ出力機能を提供していないので、loggingモジュールを使って実現します。
import logging
logging.info('This message should go to the log file')
以下のコマンドで起動すれば、app.logファイルに出力できます。
$ streamlit run app.py  --logger.level=info 2>>app.log
出力された内容は、以下のようになります。
2022-06-22 16:06:01.608 This message should go to the log file
複数ページの作成
複数のページを作成したい場合は、以下のようにpagesディレクトリ配下にpythonファイルを格納しておくことで実現できます。

これにより、左メニューに次のようなラベルが追加され、ここをクリックするとそのページへ遷移します。ファイル名がページ名のラベルになるので注意して下さい。
デザインの変更
Streamlitは、.streamlit/config.tomlでいくつかの設定を行うことができます。デザインの設定もその一つで、[theme]セクションにprimaryColorなどのプロパティーを追加することで、変更ができます。以下の設定はすべてデフォルト値なので、これを設定しても何も変化はありませんが、例えば、primaryColor="blue"にすれば、チェックボックスやスライドバーなどの色が変わります。
[theme]
primaryColor="#F63366"
backgroundColor="#FFFFFF"
secondaryBackgroundColor="#F0F2F6"
textColor="#262730"
font="sans serif"
画像の挿入
画像は、st.image()に画像ファイルのパスを渡すことで、挿入できます。
st.image('logo.png')
せっかくなので、このアプリにもロゴをつくって、それをトップページに挿入してみることにしました。

アニメーション
Streamlitには以下のような遊び心のあるアニメーションも少しだけ用意されています。
st.balloons()
データ分析中の待ち時間を退屈させないために入れるとか、まあ、何らかの用途に使えるでしょうか。

JavaScriptで動きをつける
stc.html()を使用すれば、HTMLを書くこともできます。これを使ってAnimate.cssを読み込み、ただのメッセージに動きを付けてみます。
import streamlit.components.v1 as stc
stc.html('''
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">
<p class="animated bounceInRight">👈 左のサイドバーからアクセスログを解析したいOSSを選んで下さい。</p>
''')
グラフとテーブルを横に並べる
グラフとテーブルを横に並べるなど、画面レイアウトの調整にはst.columns()とwithを使いました。以下のように実装すれば、
st.markdown('### レスポンスのステータスコード')
status_df = DataFrame(df.groupby(['Status']).size().index)
status_df['count'] = df.groupby(['Status']).size().values
status_df['percentage'] = (df.groupby(['Status']).size() / len(df) * 100).values
col1, col2 = st.columns(2)
with col1:
    fig = plt.figure()
    labels = [str(n)+'xx' for n in list(df.groupby([df['Status'] // 100]).groups.keys())]
    plt.pie(df.groupby([df['Status'] // 100]).size(), autopct = '%1.1f%%', labels = labels, startangle = 90)
    plt.axis('equal')
    fig
with col2:
    status_df
このように表示されます。
感想
Streamlitは、高レベルのフレームワークで汎用性は低そうですが、開発したいアプリの要件を満たせれば、アプリ開発の期間を短縮する非常に良い選択肢になると思いました。逆に画面レイアウトや機能などを詳細に設計してある状況から、Streamlitでそれを実現しようとすると、かなり苦戦するのではないかと思います。まずは、Streamlitができることを大まかに把握して、アプリの要件を満たせるかどうかを検討するのがいいでしょう。
さいごに
Pythonで実装しているので、今回開発したアプリでこんなことも簡単にできそうですね。
- 機械学習によるアクセスログのカラムの自動判別
 - 多言語対応
 - PyPI登録
 



















