ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【python】livereloadを使って画面を自動更新するWebページを作ってみる

 最近pythonでの簡単なサーバの作り方をよく調べています。
 その中で画面を自動更新できるライブラリである「livereload」というものを知ったので、今回はそれについての備忘録になります。

 詳しい情報は参考資料に挙げているページ制作者様のGithubなどを参照してください。


 では、始めます。


1:livereloadについて
 livereloadは対象ファイルが更新された場合に処理を実行させ、その処理が終わった後にWebページのreloadが行われるようにするためのライブラリです。

 日本語で説明すると少しごちゃごちゃしていますが、要は以下のような処理ができるようになります。

  1. 対象ファイルが更新される
  2. 更新時の処理を実行
  3. Webページ更新

 例えば「1つページしか存在しないようなWebページサーバで定期的に何かデータを受信してその値を画面に表示させる」というようなものを作りたい場合、このlivereloadを使えば比較的簡単に実装できます。


2:livereloadのインストール
 インストールは簡単で以下のpipコマンドでインストールできます。

pip install livereload


3:livereloadの使い方
 livereloadの使い方は簡単で、以下のように書きます。

from livereload import Server # livereloadライブラリ読み込み

def reload():
  # 画面更新前に行いたい処理の関数
  pass

server = Server()
# 引数filepathには監視対象のファイルパス、funcには監視対象ファイルが更新された時に実行する関数を設定
server.watch(filepath="target.txt", func=reload)
# 引数hostにはホスト名、portにはポート番号、rootには表示するHTMLファイルを設定
server.serve(host="ホスト名", port=4000, root="index.html")

 上記の内容を見ればわかるかと思いますが、livereloadのserver.watchのfilepath引数でlivereloadで監視するファイルを指定します。そしてfunc引数には画面更新前に実行させたい関数を指定します。
 その後server.serveでホスト名とポート、表示させたいHTMLファイルを指定して実行することで、livereloadのサーバを起動させることができます。


4:livereloadを使ったサンプル
 では実際にlivereloadのサンプルを作って実行してみます。

 以下の「template.html」と「page_livereload.py」のファイルを作成し、同じフォルダ内(同じ階層)に格納してください。

・template.html

<html lang="ja">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="Cache-Control" content="no-cache">
    <title>livereloadのテスト</title>
</head>
<body>
画面更新回数:$count
</body>
</html>

・page_livereload.py

# -*- coding: utf-8 -*-
import os
import platform
import socket
import threading
import time
from string import Template

from livereload import Server # livereloadライブラリ読み込み


# 画面を自動更新するクラス
class PageLiveReload:
  TEMPLATE_PATH     = "template.html"
  UPDATE_FILE_PATH  = "target.txt"
  DISPLAY_HTML_PATH = "index.html"
  PORT_NUM          = 4000

  _ipaddress         = ""
  _template          = None
  _target_path       = ""
  _display_html_path = ""
  _update_count      = 0 # 画面更新カウント数


  def __init__(self):
    # コンストラクタ(何もしない)
    pass


  def get_ipadress(self):
    # IPアドレスを取得する関数
    ipaddress = ""
    if platform.system() == "Windows":
        # Windowsの場合のIPアドレス取得
        host = socket.gethostname()
        ipaddress = socket.gethostbyname(host)
    elif platform.system() == "Linux":
        # Linuxの場合のIPアドレス取得
        connect_interface = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        connect_interface.connect(("8.8.8.8", 80))
        ipaddress = connect_interface.getsockname()[0]
        connect_interface.close()
    return ipaddress


  def setup(self):
    # 本ファイルまでのディレクトリのパスを取得
    base_dir = os.path.dirname(os.path.abspath(__file__))
    # テンプレートとなるhtmlを読み込む
    template_html_path = os.path.join(base_dir, self.TEMPLATE_PATH)
    self._template = Template(open(template_html_path, "r", encoding="utf-8").read())
    # 画面更新判断用ファイルパスの設定
    self._target_path = os.path.join(base_dir, self.UPDATE_FILE_PATH)
    # 画面表示させるHTMLファイルパスの設定
    self._display_html_path = os.path.join(base_dir, self.DISPLAY_HTML_PATH)
    # IPアドレスを取得
    self._ipaddress = self.get_ipadress()
    # 更新判断用ファイルを作成
    with open(self._target_path, mode='w', encoding="utf-8") as f:
      f.write("")
    # HTMLを初期化(作成)
    self.update_html()


  def main(self):
    server = Server()
    # 引数filepathには監視対象のファイルパス、funcには監視対象ファイルが更新された時に実行する関数を設定
    server.watch(filepath=self._target_path, func=self.reload)
    # Webページのアドレスをprintで表示
    print("http://" + self._ipaddress + ":" + str(self.PORT_NUM))
    # 引数hostにはホスト名、portにはポート番号、rootには表示するHTMLファイルを設定
    server.serve(host=self._ipaddress, port=self.PORT_NUM, root=self._display_html_path)


  def reload(self):
    # 監視対象ファイルが更新された時に実行する関数(この関数実行後にページがreloadされる)

    # カウントアップ
    self._update_count += 1
    # HTML更新
    self.update_html()


  def update_html(self):
    # テンプレートから表示させるHTMLの内容を取得
    html_content = self._template.safe_substitute(count=str(self._update_count))
    # HTMLファイルに書き込み
    with open(self._display_html_path, mode='w', encoding="utf-8") as f:
      f.write(html_content)


if __name__ == '__main__':
  # 準備
  page_reload = PageLiveReload()
  page_reload.setup()

  # マルチスレッドでlivereloadのサーバを実行
  web_reload_thread = threading.Thread(target=page_reload.main, daemon=True)
  web_reload_thread.start()

  # 無限ループ
  while True:
    time.sleep(10)

    # 10秒毎に画面を更新させる
    with open(page_reload._target_path, mode='w', encoding="utf-8") as f:
      f.write("")

 内容を少し解説すると、templare.htmlをテンプレートとして読み込み、livereloadで画面更新する際にカウントアップしたものを表示させるということをやっています。
 また画面の更新は「if __name__ == '__main__':」内に書いてあるようにおよそ10秒毎にしています。

 この2つのファイルを同じ階層に配置できたら、その階層まで移動して以下のコマンドで実行します。

python page_livereload.py

 実行するとURLがprint表示されるのでそのページをブラウザで開きます。

 ブラウザを開くと以下のように画面更新回数が表示されます。

 ブラウザを開いた状態で何もせずに待っていると、およそ10秒毎に画面が更新されて数値がカウントアップされていくようになっているはずです。


5:livereloadを使う際の注意点
 livereloadは便利ですが、注意点があります。

 それは以下のissueに書いてあるように「短い時間に監視対象ファイルが更新された場合、画面更新が無視される」というものです。

 ソースコードを見ると3秒以内に監視対象ファイルが更新された場合はlivereloadによる処理は実行されないようです。

 なので、更新タイミングが短くなることが予想される場合には今回のサンプルのように別途10秒毎に画面を更新するなどの対策が必要になります。

 また自分の環境だけかもしれませんが、Raspberry Pi OS(64bit)の環境でこのlivereloadを使おうとするとエラーが出て使えませんでした。
 Raspberry Pi OS(32bit)だと使えたので、32bitと64bitの違いで使えたり使えなかったりする可能性もあるので注意してください。

 以上がlivereloadを使って画面を自動更新する方法になります。

 限定的といえば限定的かもしれませんが、ちょっとしたWebページ用サーバであれば割と簡単にかけるこの方法でやってもよいのではないかと思います。


・参考資料