奇人凡人の雑記帳

趣味とか投資とか、twitterに書きにくいことをこちらに書きます。毎週金曜更新目標()

Flaskで作ったwebAPIからエクセルでデータを取得する

h05torです。

社内のプロジェクトで機械学習のアプリをpythonで作成していることを、以前にも書いておりましたが、いくつか問題が生じておりました。

・(デスクトップアプリの場合)PCにpythonをインストールする必要がある。

・元々エクセルVBAで作られていた部分があり、これを全部pythonに翻訳するのは効率が悪い

・かといってpython側からエクセル動かすのも色々問題がある(特にwebアプリの場合だとサーバーがLinuxなのでエクセルが動かない)

・(webアプリの場合)コードがブラックボックスで私しか分からない

 ⇒もし私が転職したりFIRE成功したらプロジェクト破綻します

 

というわけで社内機械学習アプリの作成をエクセルベースで進めることになったのですが、AI部分はpythonでないとできないのでここをどうしたら良いのかが課題でした。

要するにpythonで出力されたデータをどうやってエクセルに持ってこれるかということです。

上で述べたようにデスクトップアプリの場合PCにpythonをインストールする必要があるのが一般論ですが、IT企業でないただの中小メーカーで社内PCにわざわざpython入れるというのは普通は考えられません。(そもそも部署によっては許可されるのか)

 

そこで私が思いついたのが、だったらwebから引っ張ってくればいいんじゃね?と。

そう、webAPIを作ってエクセルでデータを取得すればいいのでは?と。

pythonanywhereにFlaskで作ったwebサイトで機械学習アプリのweb版をデプロイしていたので、それをwebAPIに転用することにしました。

 

pythonanywhereでExcelとどこまで連携できるか? - 奇人凡人の雑記帳 (hatenablog.com)から方向性が変わったのであしからず。

 

まず、pythonanywhereにアップしているflask_app.pyのコードを以下のように記述しました。

from flask import Flask,render_template,Response,request

app = Flask(__name__)

@app.route('/')#webページを生成する部分(webAPIとは無関係)
def index():
    return render_template("index.html")

@app.route("/XML")#WEBAPIを生成する部分(今回メインの箇所)
def XML():
    param = request.args.to_dict()
    try:
        x = param["x"]
        y = param["y"]
        z = x+y
        xml = '<content><python>language: パイソン</python> <first>{}</first></content>'.format(z)
        return Response(xml, mimetype='text/xml')
    except:
        xml = '<content><python>language: パイソン</python></content>'
        return Response(xml, mimetype='text/xml')  

"""
以下はローカルホストでのコードです。
pythonanywhereにデプロイしているものなのでこの部分は省略しております    
if __name__ == "__main__":
    app.debug = True
    app.run(host = "localhost")
"""

 

一方、APIからデータを取得するエクセル側は以下のように記述しました。

 

Flask側でWEBAPIとして設定している関数が@app.route(/XML)としていることから、エクセルからリクエストを送信する先のベースとなるURLは、

https://(webコンテンツのURL)/XMLとなります。

ちなみに/XMLとしたのは、現状エクセルにデフォルトで入っている関数を使用してでwebから受信できるデータはXML形式のみであるため、あえてそう名付けました。

JSON形式で受信する方法もあるらしいですが、追加設定が必要なうえ、複雑なので今回は触れません。

Flask側で処理を行うにあたって、webサーバーにURLに乗っけて送信するパラメーターですが、先ほどの「https://(webコンテンツのURL)/XMLをベースURLとして、それに「?」を付けることでURLのそれ以降の部分をパラメータと定義することができます

上記ではパラメーター名として「x」と「y」の2つのパラメーターがあり、それぞれのパラメーターの値として「柊」、「まいん」を設定しました。

そしてURLの?以降は「x=柊&y=まいん」とすることでURLでのパラメータの搭載ができました。複数のパラメータを入れる際は、「&」で区切ります。

エクセル関数でwebにリクエストを飛ばす方法ですが、WEBSERVICE関数というものがありまして、「=WEBSERVICE(URL)」とすることでwebにリクエストを飛ばすことができ、帰ってきたレスポンスの内容がセルに表示されます。

このWEBSERVICE関数ですが、現状ですとXML形式のレスポンスしか対応していないため(メジャーであるJSON形式は未対応)、FlaskのWEBAPIの方はXML形式で返すよう設定します。

Flask側ではエクセルから受けたリクエストを「param = request.args.to_dict()」でパラメーターを辞書型でparamに収納することができ、ここから各パラメータを

x = param["x"]、y=param["y"]

としてそれぞれ受け取ることができます。

ここでは受け取ったパラメータを処理する例としてパラメータxに入っている文字列と

パラメータyに入っている文字列をつなぎ合わせてエクセルに返す処理をしております。

XML形式で返すには、返す内容を変数xmlに入れて置き、

「return Response(xml, mimetype='text/xml')」とします。

これによりエクセル側で「=WEBSERVICE(URL)」と入力したセルにレスポンスが返ってきます。

ここでレスポンスの中身が「<first>柊まいん</first>」のようにhtmlタグのようなもので囲われていると思いますが、このようにタグで囲うことでエクセル関数のFILTERXML関数を使用することでタグの中身を抜き出すことができます。

ちなみに上記ではlanguage、firstの各タグの外側にcontentタグがあり入れ子構造にしておりますが、抜き出したいタグを何らかのタグの中に入れる2重以上の構造でないとFILTERXML関数が認識しないらしくエラーになってしまうのでこのようにしています。

XMLについてはあまり詳しくないので、こうしたらこうなったというぐらいの話です。htmlとの違いもあまり分かっていません)

 

今後何かに使えたらいいなと思ったので備忘録記事としました。以上です。