Pythonが動くWebサイトを作ってみた

自分は最近，[paiza](https://paiza.jp/challenges) のプログラミング問題に取り組んでいます．
問題に取り組んでいく中で，問題を解く側だけでなく，作る側もやってみたいと考えるようになりました．
そこで，まずは簡単にPythonが動くWebサイトを作ってみました．

この記事では，Pythonが動くWebサイトの作り方を紹介します．
色々な方法があるようですが，今回は GitHub Pages だけで完結させます．
サーバーやクラウドサービスは利用せず，Node等も使いません．
[こちらの32行のhtml](https://raw.githubusercontent.com/tomtkg/Python-Web-Test/refs/heads/main/qiita/index7.html) だけで動きます．[こちらのサイト](https://tomtkg.github.io/Python-Web-Test/qiita/index7.html)で，実際にPythonを動かせます．
是非，一度試してみてください．

# 1. Hello World
まずは，`Hello World`を表示します．
最初に用意するhtmlファイルは，以下の通りです．
```html:index1.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>タイトル</title>
</head>
<body>
    <pre>Hello World</pre>
</body>
</html>
```
`pre`は，整形済みテキストを表し，その中身を[等幅フォント](https://ja.wikipedia.org/wiki/%E7%AD%89%E5%B9%85%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88)で表示します．
最終的にPythonプログラムの出力が表示される部分なので，最初から`pre`を利用します．
他の解説は，省略します．Web画面の表示は，以下になります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/fa7155c4-dc44-3cc8-5472-2fd210bf428b.png)

次は，JavaScriptで`Hello World`を表示させます．
```diff_html:index2.html
~~~略~~~
<body>
-   <pre>Hello World</pre>
+   <pre id="output"></pre>
+   <script type="text/javascript">
+       document.getElementById("output").innerText = "Hello World";
+   </script>
</body>
</html>
```
元の`Hello World`は消して，`pre`内のテキストを空にします．
代わりに，`pre`部分に`id="output"`を設定します．
そして，JavaScriptで`id="output"`を持つ要素の内部テキストを`Hello World`に変更します．
Web画面の表示は，前と同じです．

次は，ボタンをクリックした時に`Hello World`を表示します．
```diff_html:index3.html
~~~略~~~
<body>
+   <button onclick="main()">実行</button>
    <pre id="output"></pre>
    <script type="text/javascript">
+       async function main() {
            document.getElementById("output").innerText = "Hello World";
+       }
    </script>
</body>
</html>
```
`実行`ボタンをクリックすると，非同期の関数`main`が実行されます．
Web画面の表示は，以下になります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/1f621cfa-c4c5-efe2-e3f1-42384ad0f7dc.png)

最後は，Pythonを利用して`Hello World`を表示します．
```diff_html:index4.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>タイトル</title>
+   <script src="https://cdn.jsdelivr.net/pyodide/v0.27.2/full/pyodide.js"></script>
</head>
<body>
    <button onclick="main()">実行</button>
    <pre id="output"></pre>
    <script type="text/javascript">
        async function main() {
+           let pyodide = await loadPyodide();
+           let result = pyodide.runPython(`"Hello World"`);
+           document.getElementById("output").innerText = result;
-           document.getElementById("output").innerText = "Hello World";
        }
    </script>
</body>
</html>
```
WebでPythonを使うため，[pyodide](https://pyodide.org)を利用します．
上記のように数行記述するだけで，簡単にPythonプログラムが動きます．
Web画面の表示は，前と同じです．

# 2. Webサイト上で記述したPythonを動かす
`html`ファイルにべた書きしたPythonプログラムを動かせました．
次は，Webサイト上で記述したPythonプログラムを動かします．
そのプログラムは，以下になります．

```html:index5.html
~~~略~~~
<body>
    Pythonプログラム<br>
    <textarea id="code" rows="10" cols="79">"Hello World"</textarea><br>
    <button onclick="main()">実行</button>
    <pre id="output"></pre>

    <script type="text/javascript">
        async function main() {
            let code = document.getElementById("code").value;
            let pyodide = await loadPyodide();
            let result = pyodide.runPython(code);
            document.getElementById("output").innerText = result;
        }
    </script>
</body>
</html>
```
`textarea`にPythonプログラムを記述します．
`"Hello World"`は，初期表示です．
記述されたPythonプログラムを`document.getElementById("code").value;`で読み取り，それを`pyodide.runPython`で実行します．
以下は，Web画面の例です．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/19593ab0-1044-181b-acb8-b3d693bbbdf7.png)

今回の目的に適した，望ましい動作が実行されていません．
また，エラー処理を書いていないため，エラー時に反応がありません．
そこで，`main`関数を以下のように変更します．
```diff_python:index6.html
async function main() {
    let code = document.getElementById("code").value;
    let pyodide = await loadPyodide();
-   let result = pyodide.runPython(code);
    
+   let result = "";
+   try {
+       pyodide.setStdout({ batched: (msg) => { result += msg + "\n"; } });
+       pyodide.runPython(code);
+   } catch (error) {
+       result = error
+   }
    document.getElementById("output").innerText = result;
}
```
Pythonプログラムの標準出力ですが，`pyodide`の初期設定では，コンソールに出力します．
`index6.html`では，出力先を変数`result`に変更しています．（改行も追加しています）
また，エラー時のメッセージも変数`result`に渡すようにしました．
この変更で，Web画面は以下のように変わります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/527f9480-6688-786a-cefa-8c8fbfcd9816.png)

標準出力の内容が画面に表示できました．
また，複数行の表示，エラーの表示もできました．

# 3. 代表的なパッケージのサポート
Pythonが動くWebサイトはできました．
しかし，実はこのままでは，numpyやscipyが動かせません．
そこで，`main`関数を以下のように変更して，代表的なパッケージをサポートします．

```diff_python:index7.html
async function main() {
    let code = document.getElementById("code").value;
    let pyodide = await loadPyodide();
+   await pyodide.loadPackage(["matplotlib", "numpy", "pandas", "scikit-learn", "scipy"]);

+   document.querySelectorAll("canvas, img.matplotlib").forEach(el => el.remove());
    let result = "";
    try {
        pyodide.setStdout({ batched: (msg) => { result += msg + "\n"; } });
        pyodide.runPython(code);
    } catch (error) {
        result = error
    }
    document.getElementById("output").innerText = result;
}
```

この変更で，`pyodide.loadPackage`内に記述したPythonパッケージがサポートされます．
利用可能なパッケージの一覧は，[こちら](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)を確認ください．
`canvas`関連の記述は，`matplotlib`の実行に対応したものです．
実行ボタンを押した時，既に描画されている画像を消すように設定しています．
これが無いと，画像が消えず，Web画面上に画像が増え続けることになります．

Web画面で，以下のようなプログラムも実行可能になります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/7f496f2b-b490-d994-bbc2-807044fe3da0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/fb22b9e9-64f2-0185-fe78-170817eca903.png)

普通に実行するより時間がかかりますが，曲線近似，最適化，図の描画ができました．

# 4. サイト構築
プログラムに対して自由に`style`を適用したり，`script`を追加してください．
この記事では，そこには言及しません．（[次の記事](https://qiita.com/tomtkg/items/84a041a3761096450349)で言及します）

完成したファイル群をGithubリポジトリに`push`します．
そして，Githubリポジトリにアクセスし，「Settings」⇒「Pages」⇒「Pages」を選択します．
Build and deployment の Sorce で Github Actions を選択し，Github Pages Jekyll の Configure をクリックします．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/8fd5eaac-11e7-33bf-d247-e580923d5e01.png)

後は「Commit changes」ボタンを押すだけでサイト構築は完成です．
`https://<ユーザー名>.github.io/<リポジトリ名>/<ファイル名>` にアクセスするとWebサイトが見られるはずです．
あるいは，`https://github.com/<ユーザー名>/<リポジトリ名>/deployments` からアクセスするのが確実です．
ここでデプロイが成功していればURLが表示されます，
失敗の場合，Webサイトが構築できていないので，修正が必要となります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/4190ee3f-9f0e-1f87-25fb-2674357ef997.png)


# 終わりに
Pythonが動くWebサイトの作り方を紹介しました．
30行程度でサイトが作れた事に，驚かれた方も多いのではないでしょうか．
見た目や便利な機能に拘らなければ，Webサイト構築は意外と難しくありません．
次回は，このWebサイトの改善に取り組みます．

<br>

関連記事・リポジトリ：

https://qiita.com/tomtkg/items/84a041a3761096450349

https://qiita.com/tomtkg/items/31e04eb75ebc0b61e3ee

https://github.com/tomtkg/Python-Web-Test