Python学習10日目!今日は、これまで学んだGitHubの使い方を実践しながら、新しいウェブサービス開発に挑戦します。
なぜ「HTML特殊文字変換サービス」?
次に何を作ろうかと考えた時、普段から「ちょっと面倒だな」と感じていたことを思い出しました。
それは、このブログにWebサービスのHTMLコードを載せる時のことです。
Webサービスを作る際に必要なhtmlのファイル内容をそのまま記事に記述すると、ブラウザがHTMLのタグとして解釈してしまい意図通りに表示されません。
そのため、「<」「>」を「<」「>」といったように変換する必要があります。
数行程度のコードなら手作業で変換しても大した手間ではありませんが、行数が増えたり、変換するタグの数が増えてくると、これが結構な重労働になります。
もちろん、世の中にはすでにHTMLコードを変換してくれる便利なWebサービスがたくさんあるのは知っています。
でも、せっかくPythonを学んでいるのだから、この「ちょっとした面倒」を解決するサービスを自分で作ってみることにしました!
まずはGitHubにリポジトリを作成する
まずはGitHubに新しいリポジトリを作成します。
今回は最初からGitHubを活用して開発を進めるため、「html_converter」という名前でリポジトリを作りました。
前回の開発と大きく違うのは、リポジトリ作成時に「Add .gitignore」で「.gitignore template: Python」を選択した点です。
なぜ.gitignoreが必要なの?
前回はプロジェクトのディレクトリ内にあるデータをすべてGitHubにアップロードしていましたが、Pythonの仮想環境用のディレクトリ(「.venv」等)はGitHubで管理しないのが基本だということを知りました。
その理由は、仮想環境のファイルが開発者それぞれの環境に強く依存するため共有には向かないからです。
そこで役立つのが「.gitignore」ファイルです。
このファイルを用意することで、Gitがバージョン管理すべきではないファイルやディレクトリを明確に指定できます。
今回「template: Python」を選択したことで、Python開発において一般的に無視すべきファイルやディレクトリが最初から記述された「.gitignore」ファイルが、自動的にリポジトリに追加されます。
実際に「.gitignore」ファイルの中身を確認して見ると、とても多くの設定が記述されていて「.venv」も含まれていました。
これで、不要なファイルが誤ってコミットされる心配がなくなりますね。
GitHubからリポジトリをローカルにクローン
今回も普段使っているUbuntuがインストールされたノートPCで開発を進めます。
まずはGitHubで作成したリポジトリを、「git clone」コマンドを使ってローカルPCにダウンロードします。
$ cd ~/project $ git clone git@github.com:servermemo/html_converter.git
これで、GitHub上のリポジトリ内容がローカルPCに完全にコピーされ、開発作業を始める準備が整いました。
開発環境の作成
今回も前回同様、「Python」+「Pyenv」+「uv」+「Flask」を組み合わせて環境を構築します。
GitHubからクローンした「html_converter」ディレクトリに移動して、以下のコマンドを順に実行していきます。
$ cd html_converter/ $ pyenv local 3.13.3 $ uv venv $ source .venv/bin/activate (html_converter) $ uv pip install Flask (html_converter) $ uv pip install python-dotenv (html_converter) $ uv pip freeze > requirements.txt
これで、開発を行うための基盤となる環境構築は完了です。
Pythonプログラム作成!短くてもパワフルなコードの秘密
さて、いよいよPythonプログラムの作成に入ります。
Gemini先生に尋ねてみたところ、Pythonの標準ライブラリであるhtmlモジュールに、HTMLの特殊文字をエスケープするescape()関数があることを教えてもらいました。
これを使用することが最もシンプルで推奨される方法とのことでした。
さらに、Webサービスとして動かすために提案してもらったのが以下のコードになります。
最初の感想は「こんなに短いコードで本当に変換できるの?」でした。
とりあえず、教えてもらったコードを「app.py」という名前で作成していきます。
import html from flask import Flask, render_template, request app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): escaped_html = None if request.method == 'POST': original_html = request.form['html_code'] escaped_html = html.escape(original_html) return render_template('index.html', escaped_html=escaped_html) if __name__ == '__main__': app.run(debug=True)
ちなみに、今回は最初からVSCodeを使用して開発を行っています。
コードの解説
前回作った時計サービスにはなく、今回のHTML変換サービスで新たに登場したコードや概念について詳しく見ていきます。
import html
Pythonの標準ライブラリであるhtmlモジュールをインポートしています。
このモジュールには、HTMLの特殊文字をエンティティ(例: <を<に)に変換するためのescape()関数が含まれています。
from flask import Flask, render_template, request
Flaskフレームワークから、このWebサービスを構築するために必要な特定のコンポーネント(クラスや関数)をインポートしています。
request
WebブラザからのHTTPリクエスト(ユーザーがブラウザから送信する情報)を処理するためのオブジェクトで、ユーザーがフォームに入力したデータなどを、このrequestオブジェクトを使ってPythonコード内で取得します。
@app.route('/', methods=['GET', 'POST'])
これはPythonのデコレータと呼ばれる構文です。
このデコレータは、直下のindex()関数がWebサービスのどのURLパス(この場合はルートパス/)にアクセスが合った時に実行されるかをFlaskに教えています。
'/'
これは、WebサービスのルートURL(例: http://127.0.0.1:5000/))を示します。
ユーザーがこのURLにアクセスすると、index()関数が実行されます。
methods=['GET', 'POST']
これは、このルートがGETリクエスト(ページを初めて表示するときなど)と、POSTリクエスト(フォームを送信するときなど)の両方を受け付けることを指定しています。
@app.route('/') との違い
methods引数を指定していない「@app.route('/')」の場合は、そのルートはデフォルトでGETリクエストのみを受け付けます。
POSTリクエストでアクセスしようとするとエラーになります。
def index():
上記のデコレータ「@app.route('/', methods=['GET', 'POST'])」によって、「/」パスへのリクエストが来たときに実行される関数を定義していて、この関数はウェブページの表示内容を生成して返します。
escaped_html = None
変換後のHTMLを一時的に格納するための変数「escaped_html」を定義し、初期値としてNone(何も値がない状態)を設定しています。
ページが初めて表示されるとき(GETリクエストのとき)は、まだユーザーからの入力がないため、変換結果も存在しません。
このため、初期状態では「None」としておくことで、テンプレート側で「変換結果はまだない」という状態を判断できます。
if request.method == 'POST':
この条件分岐は、Webブラウザからのリクエストが、POSTメソッドで送信されたものかどうかをチェックしています。
Webページのフォームに何かを入力して「送信」ボタンを押すと、通常POSTリクエストが送信されます。
HTMLコードを入力(POSTリクエスト)して変換を要求した場合に、このブロック内の処理が実行されます。
original_html = request.form['html_code']
request.form
POSTリクエストで送信されたフォームのデータを辞書のように扱えるようにするFlaskのオブジェクトです。
['html_code']
フォーム内の「textarea」タグに「name="html_code"」という属性が設定されている場合、その入力フィールドからHTMLコードの文字列を取得します。
取得した文字列はoriginal_html変数に格納されます。
escaped_html = html.escape(original_html)
ここが、このサービスの最も重要な処理です。
html.escape()
インポートしたhtmlモジュールが提供する関数です。
「original_html」変数に格納されている、ユーザーが入力した文字列内の「<」、「>」、「&」、「"」、「'」などの記号や特殊文字を、対応するHTMLエンティティ「<」「>」「&」「"」「'」に変換しています。
変換された結果は「escaped_html」変数に格納されます。
return render_template('index.html', escaped_html=escaped_html)
この行は、Pythonで処理した結果を、Webブラウザに表示するための最終的なステップです。
Flaskのrender_template()関数を使って、指定されたHTMLテンプレートファイル(この場合はindex.html)をレンダリングし、その結果をWebブラウザに返しています。
「escaped_html=escaped_html」は、「escaped_html」変数の値を「index.html」テンプレート内で「escaped_html」という同じ名前で利用できるように渡しています。
これにより、HTMLテンプレートの中で{{ escaped_html }}のように書くことで、変換結果を表示できます。
index.htmlの作成
Flaskアプリケーションのユーザーインターフェースとなる「index.html」ファイルを作成します。
このファイルは、Flaskが自動的に探してくれるように「templates」という名前のディレクトリを作成して、その中に置きます。
こちらもGemini先生に教えてもらっています。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HTMLエスケープツール</title> <style> body { font-family: sans-serif; margin: 20px; } textarea { width: 80%; height: 200px; margin-bottom: 10px; } pre { background-color: #eee; padding: 10px; border: 1px solid #ddd; overflow-x: auto; } h2 { margin-top: 30px; } </style> </head> <body> <h1>HTML特殊文字変換ツール</h1> <form method="POST"> <label for="html_code">変換したいHTMLコードを入力してください:</label><br> <textarea id="html_code" name="html_code" placeholder="例: <div>Hello!</div>"></textarea><br> <input type="submit" value="変換"> </form> {% if escaped_html %} <h2>変換結果:</h2> <pre>{{ escaped_html }}</pre> {% endif %} </body> </html>
動作確認
「index.html」の準備ができたら、いよいよapp.pyを起動して動作を確認します。
(html_converter) $ python ./app.py * Serving Flask app 'app' * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 Press CTRL+C to quit * Restarting with stat * Debugger is active! * Debugger PIN: 100-232-979
表示されたURL(http://127.0.0.1:5000/ )にWebブラウザでアクセスをします。
期待通り、HTML変換ツールのウェブページが表示されました。
実際に、テキストエリアでHTMLのコードを入力して、実際に変換されるか確認してみます。
HTMLコードを入力したあとに「変換」ボタンをクリックすると、変換作業が行われます。
画面下部に変更後の結果が表示されました!
まだ簡単な動作テストしかしていませんが、とりあえず自分が思い描いていたサービスが完成しました。
GitHubへプッシュ
これで、HTML変換サービスの基本的な骨格が完成したので、この段階で一度GitHubのリポジトリにプッシュしておくことにします。
(html_converter) $ git add app.py templates/index.html (html_converter) $ git commit -m "新規作成" (html_converter) $ git push
これで、作成したコードはGitHubに保存され、いつでも履歴を追ったり、他のデバイスからアクセスしたりできるようになりました。
色々と使ってみて、問題が発生したり追加の機能が欲しくなったら、新しくブランチを作成して作業を行うことにします。
本日のまとめ:モジュールの力に感動!
こんな短いコードで本当に期待していたサービスを作成できました。
htmlモジュールの力を借りることで、複雑な処理を自分で一から書くことなく、あっという間に目的の機能を実装できてしました。
Pythonのモジュールってすごく便利なのですね…
いままで、シェルスクリプトで簡単なログ解析ツールや、Minecraftサーバ運用のツールを作成したことはありましたが、モジュールを使ってツールを作成することはやったことがありませんでした。
モジュールってとても便利機能を提供してくれる、とても素晴らしいものであることを実感することができました!
もしかして、Pythonで何らかのプログラムを作成する場合ってモジュールを探すことから始めたほうが良いのかな?
これからの予定
モジュールの探し方を学ぶ
今回でモジュールはとても便利なものだということが実感できたので、実現したい機能が含まれているモジュールをどうやって探せばよいのか、探し方を学びたいと思います。
Nginx + Gunicorn環境で動作させる
まずは、ローカルのPC上でNginx + Gunicorn 環境で動作できるようにしてみようと思います。
こちらは、前回の時計サービスでやっているので、たぶん問題なくできるはず。
セキュリティについて学ぶ
今回のサービスをインターネット上に公開できるようにするために、セキュリティ関連のことを学びたいと思います。
前回は、ただ時刻を表示させるだけだったので、セキュリティリスクはほとんどなかったのですが、今回はユーザーが任意のHTMLコードを入力できるため、悪意のある入力に対する脆弱性を適切に処理しないと、クロスサイトスクリプティング(XSS)などの攻撃を受ける可能性があります。
これまでWebサービスを作成したことがないので、セキュリティ周りのことについてもこれから学んでいきます。
コメント