HYT MachineWorks

やったこととか思いついたことをメモしておくブログです。

Flaskで、Pythonを使ってwebアプリを作る Windows10 64bit (自然言語処理100本ノック7章 69番を解く)

Flaskとは?

http://flask.pocoo.org/

Pythonをベースに作られた軽量webフレームワークです。一から頑張らなくても比較的カンタンにwebアプリが作れます。

これを使って簡単なwebアプリを作って見たのでメモ。正確には、自然言語処理100本ノックの7章 69番のメモです。データも問題で指定されているartist.json.gzを使っているので必要に応じてダウンロードすること、

 Flaskをインストール

何をともあれFlaskのインストールです。いつものように、condaかpipでインストールしましょう。

conda install Flask

pip install Flask

 で、インストールされましたね。

実際にwebアプリを作成する

今回、作成するにあたり以下のページを参考にした。

qiita.com

フォルダを構成する

いきなりプログラムを書き始めてもいいんですが、まずはじめに、pythonで各アプリのコア以外の装飾部分に当たるCSSJavaScriptフレームワークの構成から始めましょう。とりあえず下の図のような構成を作ります。今回は、自然言語処理100本ノックの7章 69番だったので、フォルダ名は「problem_no_69」です。

[problem_no_69]
┣ [template]  # ブラウザで表示されるhtmlのテンプレート用フォルダ
┣ artist_db.py  # 今回、Flaskを使って書くwebアプリケーション本体
┗ mongo_function.py  # MongoDBから、必要な情報を検索するscript

実際の置き方は、今回の完成したアプリのgithubを参考にしてください

CSSJavaScriptの設置

次に、CSSJavaScriptを設置します。使うのは、bootstrapとjqueryとpopper.jsです。

20年前では考えられないんですが、Bootstrapの推奨方法の直接リンクを張って読み込ませます。下が、そのコード

Css head内に記載
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
JavaScript Body内に記載
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

ざっと説明すると srcは、そのJavaScriptのコード integrityは、そのハッシュ値とそのアルゴリズム、crossoriginはダウンロードする際に認証情報を使用するか(今回は使わないanonymous匿名)」です。

なぜ、こんなことをするかというと、悪い人がJavaScriptのコードを入れ替えた場合に無条件で読み込まさせず、ハッシュ値で同じものか確認するという仕組みをとってるみたいです。いわゆるXSS対策ということでしょう。多分、

テンプレートを用意する

アクセスされたときに書き出すHTMLをいちいち全部pythonのコード内に書いていたら大変なので、Flaskには元から用意されたjinja2というテンプレートエンジンを使ってテンプレートの指定した場所だけ書き換えさせるようなことをさせることが出来ます。

大本は、今回は、Bootstrapのチュートリアルから持ってきました。

 

とりあえず、そのコピー

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <title>Hello, world!</title>
  </head>
  <body>
    <h1>Hello, world!</h1>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
  </body>
</html>

 ここから、今回は、赤字になっているtitleの文字列とBody内の文字列を変化させたいのでjinjaのタグをそこに記載したのが下になります。

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css" integrity="sha384-Smlep5jCw/wG7hdkwQ/Z5nLIefveQRIY9nfy6xoR1uRYBtpZgI6339F5dgvm/e9B" crossorigin="anonymous">

{% if title %}
<title>{{ title }}</title>
{% else %}

<title>No title</title>
{% endif %}


</head>
<body>
{% block content %}{% endblock %}

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js" integrity="sha384-o+RDsa0aLu++PJvFqy8fFScvbHFLtbvScb8AjopnFD+iEQ7wo/CG0xlczd+2O/em" crossorigin="anonymous"></script>
</body>
</html>

ピンク色で書き込んだ部分がjinjaのタグになります。Flaskから呼び出されたときに、contentという値が渡されればBody部分を書き換えます。また、title部分のように、ifを使うことも出来、titleという値が渡されるか否かで条件分岐させることも出来ます。詳しくは、Jinja2のドキュメントを見てください。結構柔軟にいろいろできるみたいです。

出来たファイルをlayout.htmlという名前でtempleteフォルダに保存してください。

[problem_no_69]
┣ [template]  # ここに保存
┣ artist_db.py
┗ mongo_function.py

次に、実際に呼び出すindex.htmlを作成します。

{% extends "layout.html" %}
{% block content %}
<!-- Form
================================================== -->

<div class="container">

<h1 class="form-group">
{{ message }}
</h1>

<div class="container text-right">
<a href="../">initialize</a>
</div>

<form action="/" method="post" class="form-control">

<div class="form-group">
<label for="category">Select category</label>
<select id="category" class="form-control" name="category">
<option value="name" selected>Artist name</option>
<option value="area">Work area</option>
<option value="begin">Begin year</option>
<option value="end">End year</option>
<option value="tags">tags</option>
</select>
</div>

<div class="form-group">
<label for="keyword">Input keyword</label>
<input type="text" class="form-control" id="keyword" name="keyword" placeholder="Name">
</div>

<button type="submit" class="btn btn-default">Search!</button>
</form>

{% if results %}
<table class="table table-hover">
<TR>
<TD>Artist Name</TD>
<TD>Work area</TD>
<TD>Begin year</TD>
<TD>End year</TD>
<TD>tags</TD>
<TD>Rating Count</TD>
</TR>

{% for result in results %}

<TR>
<TD>{{result.name}}</TD>
<TD>{{result.area}}</TD>
<TD>{{result.begin}}</TD>
<TD>{{result.end}}</TD>
<TD>{{result.tags}}</TD>
<TD>{{result.rating_count}}</TD>
</TR>
{% endfor %}

</table>
{% endif %}

{% if not_found %}
<div class="container">
<p>None item find ...</p>
</div>
{% endif %}

<div class="container text-right">
<a href="../">initialize</a>
</div>

</div>

{% endblock %}

このindex.htmlは、一番最初の水色文字の部分で、

{% extends "layout.html" %}
{% block content %}

と書いていますが、これは、layout.htmlを拡張するという宣言で、その後のblock contentは、layout.htmlの中の{% block content %}をindex.html内の{% block content %}で書き換えるという意味になります。

ざっくり、やってることの説明すると、常に検索のフォームは表示していて、検索結果が返されているときのみ結果の作画を行うという処理です。

また、中盤の紫色の文字で囲まれた

{% for result in results %}
 <TR>
  <TD>{{result.name}}</TD>
  <TD>{{result.area}}</TD>
  <TD>{{result.begin}}</TD>
  <TD>{{result.end}}</TD>
  <TD>{{result.tags}}</TD>
  <TD>{{result.rating_count}}</TD>
 </TR>
{% endfor %}

の部分は、resultsという辞書型のオブジェクトのリストを渡すと要素がある分だけ繰り返し処理をしてくれるJinja2のコマンドです。

このindex.htmlもtemplateのフォルダに保存します。

[problem_no_69]
┣ [template]  # ここに保存
┣ artist_db.py
┗ mongo_function.py

Flaskを使ってwebアプリを作る。

最後に、webアプリの本体を作る。以下が今回作ったartist_db.pyだ。

[problem_no_69]
┣ [template] 
┣ artist_db.py  # これの説明
┗ mongo_function.py

from flask import Flask, render_template, request, redirect, url_for

from mongo_function import search_items


app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def index():
""" driven at accessing to index.html

:return: rendering index.html
"""

if request.method == "GET":
title = "Artist DB"
message = "Search Artist DB Data"

return render_template("index.html",
title=title,
message=message)

elif request.method == "POST":
category = request.form["category"]
keyword = request.form["keyword"]

title = "Search result : " + category + " " + keyword
message = "Search result : " + category + " " + keyword

results = search_items(category, keyword)

return render_template("index.html",
title=title,
message=message,
results=results,
not_found=True if not results else False)
else:
return redirect(url_for("index"))


if __name__ == "__main__":
app.run()

以上が、今回作ったアプリになる。一番始めの

@app.route("/", methods=["GET", "POST"])
def index():

ここの部分で、どこにアクセスした時、とアクセスの方式を指定できる。

今回は、index.htmlつまり、"/"のみにしたいので上記の指定を行っている。

また、今回はGETメソッドで、URLからデータを受け取ることを想定していないのでGETの場合は強制的に、検索画面のみ、POSTの場合は結果に応じた検索結果表示にするため、その条件分岐の為に両方指定している。

動作としては、はじめにappというflaskインスタンスが作られ、app.run()でwebアプリが実行されるという流れ。

動作は、コマンドプロンプトなり、PowerShell

python artist_db.py

で動作します。アクセスは、

http://127.0.0.1:5000/

PyCharmとかで動かすと停止出来なくて困るから注意ね。

なにか、忘れてないか?

[problem_no_69]
┣ [template] 
┣ artist_db.py
┗ mongo_function.py  # コレ

mongoDBから検索した結果を返すmongo_function.pyを使っているが、DBの検索の方法の話だけなのでmongoDBの使い方は、以下の記事を参照。実際のコードは、mongo_function.pyを参照してください。

上記のコードは、検索するだけなのでデータの導入は、problem_no_64.pyを参照してください

hytmachineworks.hatenablog.com
また、今回のコードは、githubに保存しています。参照ください。

NLP_100/codes/chapter_07/problem_no_69 at master · hytmachineworks/NLP_100 · GitHub