見出し画像

StreamlitでUser-Agentなどのユーザー情報を取得する(ただし面倒なJavaScriptは書かずに)

1. streamlit_js_evalとかいう便利すぎるやつ

ちょっと作りたいものがあってStreamlit (https://streamlit.io) をいじっていたのだけれど、Python以外は長らく書いていない人間なのでフロントエンドがどうも触りづらくて困っていた(まあフロントエンドなんか触らなくていいのがstreamlitの良さなのでその辺の根本を間違えているといえばそうなのだけれど)。特にUser-Agentなんかを取得するには、これまでstreamlitのbi-directional componentあたりを使ってフロントエンド側で走らせているJavaScriptからPythonに変数を渡してあげる必要があり、これがかなり面倒だった。
実際のところstreamlitでUser-Agentを取得する専用のパッケージみたいなのもあるにはあるんだけど、古くなっていて今のバージョンやprotobufあたりとの依存関係が解決できなかったのと、どうせならもっと汎用性があったほうがいいので、今回はstreamlit_js_evalを使う。これ狂おしいほどに便利。

2. とりあえずstreamlitを起動する

変数の取得云々は置いておいて、まずはstreamlit自体を起動しないと始まらない。今回は説明が面倒なのでColabでやる。streamlitとlocaltunnelという神の組み合わせさえあれば無限に遊べる。
とはいえ特別やることも正直あんまりなくて、streamlitと、のちの作業のためにstreamlit_js_evalだけpipで入れておけばいい。

!pip install -q streamlit streamlit_js_eval

立ち上げにはapp.pyをファイルにして実行してあげる必要があるので、writefileを使って以下のコードをファイルに吐き出す。

%%writefile app.py
import streamlit as st

def main():
    st.title("jniimilab")

    if 'initialized' not in st.session_state.keys():
        st.session_state['initialized'] = True
        st.session_state['page'] = 0

    st.write(f"Current page: {st.session_state.page}")

if __name__ == '__main__':
    main()

これを実行する。
ただし、普通に実行するとユーザー情報取ってるからなみたいな警告が出て鬱陶しいのでstreamlit立ち上げの段階でbrowser.gatherUsageStatsにfalseを渡してあげる(そのくせ自分はユーザー情報を取るためにこれを書いているという意味でまあカスではある)。

!streamlit run app.py --browser.gatherUsageStats false & sleep 3 && npx localtunnel --port 8501
Colabで実行している様子

*ちなみにこの立ち上げ部分のコードは『StreamlitをGoogle Colabで速攻で試そう!』( https://zenn.dev/aidemy/articles/9c2f959d08566a )を、
--browser.gatherUsageStats falseは『Streamlitは4通りで設定できる! browser.gatherUsageStatsを例に』( https://nikkie-ftnext.hatenablog.com/entry/streamlit-4-way-configuration ) を
参考にしています。ありがとうございます。

あと、これは今回に限ったことじゃないけど、streamlitではrefreshのたびにコードが回りまくるので、立ち上げの最初の一回だけ走らせたいとかいう場合には色々と工夫がいる。今回は変数を格納するdictであるsession_stateの中にinitializedというフラグが立っていないときだけ初期化のルーティーンが走るようにしてある。

実行結果としては、単にタイトルとちょろっとした情報が出る。

シンプルな実行画面

3. streamlit_js_evalを使ってみる

準備は整ったので、ここから実際にstreamlit_js_evalを使って値を取得していく。
とはいえこいつも使い方は非常に簡単で、情報を取得したいタイミングで、streamlit_js_eval.streamlit_js_eval()を走らせればいいだけ。たとえばGitHub上の例を使うなら、

st.write(f"Screen width is {streamlit_js_eval(js_expressions='screen.width', key = 'SCR')}")

つまり、streamlit_js_eval上にjs_expressionsっていう引数でフロントエンドのJavaScriptから取得したい変数を指定する。単なる取得に加えて変数variable(streamlitではstreamlit.session_state.variable)に格納したい場合には、key=variableを指定しつつwant_output=Trueしてあげればいい。

上のコードはフロントエンドに表示するためにst.writeっていう余計な部分が含まれてるけど、純粋にただユーザーエージェントをuser_agentという名前で取得したければ、

streamlit_js_eval(js_expressions='window.navigator.userAgent', want_output=True, key='user_agent')

こんな感じ。これでstreamlit.session_state.user_agentに入る。めちゃくちゃ簡単ですね。

他にもいくつかの変数をまとめて取得してみるなら、

%%writefile app.py
import streamlit as st
from streamlit_js_eval import streamlit_js_eval

info = {
    'user_agent': 'window.navigator.userAgent',
    'language': 'window.navigator.language',
    'screen_height': 'window.screen.height',
    'screen_width': 'window.screen.width',
    'origin': 'window.location.origin'
}

def main():
    st.title("jniimilab")

    if 'initialized' not in st.session_state.keys():
        st.session_state['initialized'] = True
        st.session_state['page'] = 0

        for key in info.keys():
            streamlit_js_eval(js_expressions=info[key], want_output=True, key=key)
    st.write(f"Current page: {st.session_state.page}")
    
    st.write('Obtained variables:')
    keys = list(st.session_state.keys())
    keys.sort()
    for key in keys:
        st.write(key, st.session_state[key])  

if __name__ == '__main__':
    main()

最後のところでsession_stateが持ってるキーから保持してる変数を順番に吐いてるけど、単純にpandas.DataFrameにしてst.dataframeで表示させるほうが楽ではある。

そしてこれを実行した結果として…

うん。かなりシンプルで使いやすいですね。
ただ使い勝手がいいぶん、(これは高級言語とwrapperばっか使ってる人間の宿命でもあるのだけれど)おそらくこれはJSの評価のたびにコンポーネント作ってフロントエンドとなんかやってるような感じがするので単純に最初の読み込みが遅い。
なのでそもそもの注意点として、初期化とかボタン押下とかそういうタイミングで取得しないと、おそらくrefreshごとにJSの評価が走ることになるのでとても嫌なことになるんじゃないかと思う。

4. 書いたコードまとめ

今回書いたコードは全部まとめてGistにアップしてあります。下のOpen in Colabを押せばそのままGoogle Colabで全く同じように動くものを開けるかと思います。バージョンが変わらない限りは。


この記事が気に入ったらサポートをしてみませんか?