MySQLのテーブルにレコード作成時刻や更新時刻を自動で記録する列を作る

たまに新しいテーブルを作るたびに調べてるのでメモです。
(タイトルにMySQLと書いてますが、実際はそれと互換のAuroraでやってます。)

DBのテーブルに新しい行を追加したり、更新したりするときにその時刻を記録する列を用意しておくと言うのは結構一般的なことだと思います。
phpMyAdmin からGUIでポチポチ設定してもいいのですが、SQLでもできた方が楽です。

さて、その方法ですが、MySQLのドキュメントにそのまま書いてあります。
11.3.5 TIMESTAMP および DATETIME の自動初期化および更新機能

DEFAULT CURRENT_TIMESTAMP で、行作成時にタイムスタンプが記録され、
ON UPDATE CURRENT_TIMESTAMP で、行更新時にタイムスタンプも更新されます。

列名として、 created_at, updated_at を使う場合は次のようにすれば良いでしょう。
例えば、タイムスタンプの他にidとnameを格納するテーブルを作るとしたら次のようになります。
(ついでに NOT NULL制約も入れました。)


CREATE TABLE
    sample_table (
        id INTEGER,
        name VARCHAR(255),
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

さて、試してみましょう。まず作成時刻から。


INSERT INTO
    sample_table(id, name)
VALUES
    (1, 'テスト1'),
    (2, 'テスト2'),
    (3, 'テスト3')
;

SELECT * FROM sample_table;
+------+------------+---------------------+---------------------+
| id   | name       | created_at          | updated_at          |
+------+------------+---------------------+---------------------+
|    1 | テスト1    | 2021-04-04 23:36:29 | 2021-04-04 23:36:29 |
|    2 | テスト2    | 2021-04-04 23:36:29 | 2021-04-04 23:36:29 |
|    3 | テスト3    | 2021-04-04 23:36:29 | 2021-04-04 23:36:29 |
+------+------------+---------------------+---------------------+
3 rows in set (0.00 sec)

時刻が勝手に入りましたね。

次に更新です。


UPDATE
    sample_table
SET
    name = 'test 2' 
WHERE
    id = 2
;

SELECT * FROM sample_table;
+------+------------+---------------------+---------------------+
| id   | name       | created_at          | updated_at          |
+------+------------+---------------------+---------------------+
|    1 | テスト1    | 2021-04-04 23:36:29 | 2021-04-04 23:36:29 |
|    2 | test 2     | 2021-04-04 23:36:29 | 2021-04-04 23:38:26 |
|    3 | テスト3    | 2021-04-04 23:36:29 | 2021-04-04 23:36:29 |
+------+------------+---------------------+---------------------+
3 rows in set (0.01 sec)

id = 2 のレコードの updated_at も更新されました。

SQLでデータベースやテーブルの文字コードや照合順序の設定を変える

前回の記事でRDSのクラスタ単位の文字コードや照合順序をutf8mb4に対応させる方法を書きましたが、
後から設定を変えても既存のデータベースやテーブルの設定はそのまま変わりません。

変更する場合はそれぞれ ALTER 文を実行して変更していく必要があります。

まず、テーブルの設定を変更する場合は、次の構文を使います。

ALTER TABLE tbl_name
CONVERT TO CHARACTER SET charset_name [COLLATE collation_name];
(参考: 13.1.7 ALTER TABLE 構文)


-- 元の設定を確認
MySQL [sample_db]> SHOW CREATE TABLE sample_table\G
*************************** 1. row ***************************
       Table: sample_table
Create Table: CREATE TABLE `sample_table` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `text` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

-- 設定を変更
MySQL [sample_db]> ALTER TABLE sample_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

-- 変更結果を確認
MySQL [sample_db]> SHOW CREATE TABLE sample_table\G
*************************** 1. row ***************************
       Table: sample_table
Create Table: CREATE TABLE `sample_table` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `text` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.01 sec)

ちなみに、 “CONVERT TO” をつけ忘れると、テーブル全体のデフォルト設定だけ変更されて、列の設定が元のままになります。
(列ごとに、個別の文字コード設定を行いたい場合はこちらの方法を使うことになりそうです。)


-- 元の設定を確認
MySQL [sample_db]> SHOW CREATE TABLE sample_table2\G
*************************** 1. row ***************************
       Table: sample_table2
Create Table: CREATE TABLE `sample_table2` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `text` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

-- 設定を変更 (CONVERT TO 無し)
MySQL [sample_db]> ALTER TABLE sample_table2 CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

-- 変更結果を確認
MySQL [sample_db]> SHOW CREATE TABLE sample_table2\G
*************************** 1. row ***************************
       Table: sample_table2
Create Table: CREATE TABLE `sample_table2` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
  `text` varchar(255) CHARACTER SET latin1 DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.00 sec)

各列のCHARACTERが latin1 のままですね。

続いて、データベースのデフォルトの文字コードと照合順序の設定の変更方法です。
新規作成したテーブルはデータベースのデフォルト設定を引き継ぐので、多くのテーブルを作る場合は、最初にデータベース単位で設定しておくべきでしょう。

DATABSE単位の設定は文字コード周りくらいしかないので、 ALTER DATABASE のドキュメントもシンプルです。

参考: 13.1.1 ALTER DATABASE 構文

ALTER DATABASE sample_db CHARACTER SET utf8mb4 COLLATE utf8mb4_bin


-- 元の設定を確認
MySQL [sample_db]> SHOW CREATE DATABASE sample_db;
+-----------+----------------------------------------------------------------------+
| Database  | Create Database                                                      |
+-----------+----------------------------------------------------------------------+
| sample_db | CREATE DATABASE `sample_db` /*!40100 DEFAULT CHARACTER SET latin1 */ |
+-----------+----------------------------------------------------------------------+
1 row in set (0.00 sec)

-- 設定を変更 
MySQL [sample_db]> ALTER DATABASE sample_db CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
Query OK, 1 row affected (0.03 sec)

-- 変更結果を確認
MySQL [sample_db]> SHOW CREATE DATABASE sample_db;
+-----------+-------------------------------------------------------------------------------------------+
| Database  | Create Database                                                                           |
+-----------+-------------------------------------------------------------------------------------------+
| sample_db | CREATE DATABASE `sample_db` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */ |
+-----------+-------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

Amazon RDS(Aurora)で日本語文字列や絵文字を使えるようにする

RDSのデフォルトの設定のクラスタで、データベースとテーブルを特に文字コードを設定せずに作成した場合、
VARCHAR(文字列)型の列を作ってもそこに日本語などのマルチバイト文字を格納することはできません。

カラムか、テーブルか、データベース単位で設定してもいいのですが、パラメーターグループを使って設定する方法を紹介します。
Aurora(正確にはそれが互換性を持っているMySQL)では、文字コード(Character Set)と照合順序(Collation)をそれぞれ独立した設定として持っています。
照合順序というのはソート順やアルファベットの大文字小文字を同一視するかどうかといったルールのセットです。

文字コードは日本語を使いたいのであればUTF8を使えるように設定する必要がありますが、
歴史的な経緯により、MySQLのUTF8には、utf8とutf8mb4の2種類があります。
utf8には3バイトまでの文字しか含まれておらず、絵文字等の4バイト文字も使えるようにするためにはutf8mb4を設定する必要があります。

参考: 10.1.10.7 utf8mb4 文字セット (4 バイトの UTF-8 Unicode エンコーディング)

さて、とりあえず何も設定しなかった場合の設定を見ておきましょう。


MySQL [(none)]> SHOW VARIABLES LIKE 'char%';
+--------------------------+-------------------------------------------------------------------------+
| Variable_name            | Value                                                                   |
+--------------------------+-------------------------------------------------------------------------+
| character_set_client     | utf8                                                                    |
| character_set_connection | utf8                                                                    |
| character_set_database   | latin1                                                                  |
| character_set_filesystem | binary                                                                  |
| character_set_results    | utf8                                                                    |
| character_set_server     | latin1                                                                  |
| character_set_system     | utf8                                                                    |
| character_sets_dir       | /rdsdbbin/oscar-5.7.serverless_mysql_aurora.2.08.3.38.0/share/charsets/ |
+--------------------------+-------------------------------------------------------------------------+
8 rows in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'coll%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | utf8_general_ci   |
| collation_database   | latin1_swedish_ci |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.01 sec)

character_set_client, character_set_connection, character_set_results
はデフォルトでは utf8 になっていますが、 character_set_database と、 character_set_server が、latin1 になっています。
絵文字含む日本語文字を使うためにはこれら5つをutf8mb4に設定する必要があります。

ここからがややこしいことなのですが、RDSのパラメーターグループではこの5つをそれぞれ設定できるようになっているのに、
character_set_client, character_set_connection, character_set_results にはそれが反映されません。
そして、 character_set_database には character_set_server に設定した値が入ります。
反映されない3つは、クライアント側で設定する必要があるようです。

また、collation の方も、 collation_connection には設定した値が反映されず、
collation_server に設定した値が、collation_server と collation_database に反映されます。

これを踏まえて設定していきます。まず、RDSのパラメーターグループには次のふたつを設定します。

character_set_server: utf8mb4
collation_database: utf8mb4_bin

この段階で、設定は次のようになります。


MySQL [(none)]> SHOW VARIABLES LIKE 'char%';
+--------------------------+-------------------------------------------------------------------------+
| Variable_name            | Value                                                                   |
+--------------------------+-------------------------------------------------------------------------+
| character_set_client     | utf8                                                                    |
| character_set_connection | utf8                                                                    |
| character_set_database   | utf8mb4                                                                 |
| character_set_filesystem | binary                                                                  |
| character_set_results    | utf8                                                                    |
| character_set_server     | utf8mb4                                                                 |
| character_set_system     | utf8                                                                    |
| character_sets_dir       | /rdsdbbin/oscar-5.7.serverless_mysql_aurora.2.08.3.38.0/share/charsets/ |
+--------------------------+-------------------------------------------------------------------------+
8 rows in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'coll%';
+----------------------+-----------------+
| Variable_name        | Value           |
+----------------------+-----------------+
| collation_connection | utf8_general_ci |
| collation_database   | utf8mb4_bin     |
| collation_server     | utf8mb4_bin     |
+----------------------+-----------------+
3 rows in set (0.00 sec)

次にクライアント側の設定です。
僕は、Amazon Linux 2 で標準のRDSになった、MariaDBを利用しています。
設定ファイルは、 /etc/my.cnf なのですが、
その中を見ると、 !includedir /etc/my.cnf.d となっていて、他のファイルを読み込んでいます。

/etc/my.cnf.d/client.cnf と言うファイルの中に、 [client]と言うセクションがあるのでそこに設定します。


$ sudo vim /etc/my.cnf.d/client.cnf

[client]
# 以下の1行を追加
default-character-set = utf8mb4

MySQL [(none)]> SHOW VARIABLES LIKE 'char%';
+--------------------------+-------------------------------------------------------------------------+
| Variable_name            | Value                                                                   |
+--------------------------+-------------------------------------------------------------------------+
| character_set_client     | utf8mb4                                                                 |
| character_set_connection | utf8mb4                                                                 |
| character_set_database   | utf8mb4                                                                 |
| character_set_filesystem | binary                                                                  |
| character_set_results    | utf8mb4                                                                 |
| character_set_server     | utf8mb4                                                                 |
| character_set_system     | utf8                                                                    |
| character_sets_dir       | /rdsdbbin/oscar-5.7.serverless_mysql_aurora.2.08.3.38.0/share/charsets/ |
+--------------------------+-------------------------------------------------------------------------+
8 rows in set (0.00 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'coll%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+
3 rows in set (0.01 sec)

これで、ほぼ設定できました。
実用上これで困ることなく使うことができると思います。

後1点, collation_connection の設定が utf8mb4_bin にならずに、 utf8mb4_general_ci になってしまうのですが、
どなたか collation_connection の設定を utf8mb4_bin にする方法をご存知の方がいらしたら教えていただけないでしょうか。

そのセッションに限った設定であれば、
SET collation_connection = utf8mb4_bin;
で設定できるのですが、接続を切ると元に戻ってしまいます。

また、
SET GLOBAL collation_connection = utf8mb4_bin;
は RDS では実行できないようです。

先述の通り、パラメーターグループで設定しても反映されません。
あまり困ることもないのですが、ここだけ設定がズレていて気持ち悪いので可能であればutf8mb4_binに揃えたいです。

RDSのタイムゾーンを日本時間(Asia/Tokyo)にする

EC2の時刻設定の話を書いたついでに、RDSの時刻設定を変える方法も紹介しておきます。
(ちなみに僕が使ってるRDSは Amazon Aurora Serverless です。)

結論から言うと、RDSにはパラメーターグループと呼ばれる設定値のコンテナのようなものがあり、
それを使ってシステム環境変数を設定します。

ドキュメントはここかな。(これを読むより実際に動かした方がわかりやすい)
参考: DB パラメータグループを使用する

まず、デフォルトの設定を確認しておきましょう。
何も設定せずに、RDSのクラスタを立ち上げ、現在時刻を表示してみます。

これ実行したのは 2021-03-26 23:48:09 です。


MySQL [(none)]> SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2021-03-26 14:48:09 |
+---------------------+
1 row in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'time_%';
+---------------+-------------------+
| Variable_name | Value             |
+---------------+-------------------+
| time_format   | %H:%i:%s          |
| time_zone     | SYSTEM            |
| timestamp     | 1616770134.005551 |
+---------------+-------------------+
3 rows in set (0.01 sec)

time_zone は SYSTEM となっていますが9時間ずれていてUTCだとわかりますね。
これを修正していきます。

– RDSの管理画面の左ペインから、パラメーターグループを選択する
– パラメータグループの作成 を押下
– 以下のように項目選択
パラメータグループファミリー aurora-mysql5.7
タイプ DB Cluster Parameter Group
グループ名 任意
説明 任意
– 作成 を押下
– 作成したパラメーターグループを選択し、パラメーターグループアクションから編集を選択
– time_zone を探して、 Asia/Tokyo を選択
– 変更を保存
– データベースの一覧に戻り、設定を変更したいデータベースを選択
– 変更を押下
– DB クラスターのパラメータグループ に先ほど作ったパラメーターグループを選択し、続行を押下
– 変更を適用するタイミング は すぐに適用 を選択
– クラスターの変更 を押下

この後、セッションを切ってしばらく待った後繋ぎ直すと反映されています。


MySQL [(none)]> SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2021-03-26 23:59:23 |
+---------------------+
1 row in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'time_%';
+---------------+-------------------+
| Variable_name | Value             |
+---------------+-------------------+
| time_format   | %H:%i:%s          |
| time_zone     | Asia/Tokyo        |
| timestamp     | 1616770768.009869 |
+---------------+-------------------+
3 rows in set (0.00 sec)

Aurora Serverless と言うより RDS では、他のステム変数も同じようにパラメーターグループを使って編集できます。

EC2(Amazon Linux 2)の時刻設定を日本時間にする

初期設定ではUTCになっている、EC2の時刻設定を東京時間にします。
昔は設定ファイルを書き換えたりリンクを貼ったりと若干手間だったような覚えがあるのですが、
Amazon Linux 2 では、timedatectl という便利なコマンドがあり、これを使って設定ができるようです。

まず、
timedatectl list-timezones
で設定可能なタイムゾーンの一覧を取得できます。
東京時間があることを確認しておきましょう。


$ timedatectl list-timezones | grep Tokyo
Asia/Tokyo

次に、TimeZoneを設定し、確認します。


$ timedatectl set-timezone Asia/Tokyo
$ timedatectl status
      Local time: 木 2021-03-18 22:58:37 JST
  Universal time: 木 2021-03-18 13:58:37 UTC
        RTC time: 木 2021-03-18 13:58:37
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: n/a

これで設定できました。

あとは念の為、サーバーを再起動しておきましょう。
$ sudo reboot

EC2(Amazon Linux 2)でJupyter Notebookをサービスとして動かす

前回の記事でJupyterサーバーを構築しましたが、使うたびにいちいちログインしてJupyterを起動するのは手間です。
そこで、EC2インスタンスを起動したら自動的にJupyterも立ち上がるように設定しようと思います。

昔やったときは、Linuxが起動するときに実行されるスクリプトである、
/etc/rc.local
ファイルに、以下のJupyterの起動コマンドを書き込んでいました。
su - ec2-user jupyter notebook &

ただ、最近はrc.localを使うのではなく、Jupyter Notebookをサービスとして動かすのがトレンドのようなので今回はその方法でやってみます。
この方法だと、systemctlコマンドで管理できるようになるので便利そうです。

サービスファイルの中身についてのマニュアルはこちらのようです。
参考: systemd.service

まず、whichコマンドでjupyterのフルパスを確認しておきます。


$ which jupyter
~/.pyenv/shims/jupyter

~ は /home/ec2-user なので、実際のフルパスは
/home/ec2-user/.pyenv/shims/jupyter
ですね。

続いて、ユニットファイルを作成します。


sudo vim /etc/systemd/system/jupyter.service

# 中身は以下の通り
[Unit]
Description=Jupyter Notebook

[Service]
ExecStart=/home/ec2-user/.pyenv/shims/jupyter notebook
Restart=always
User=ec2-user
Group=ec2-user

[Install]
WantedBy=multi-user.target

他のサイトを見てると、[Service]のところに、
Type=simple
と入れてる人も多いですが、simpleはデフォルトなので省略しても良さそうです。(ExecStartを指定して、TypeとBusNameをいずれも指定しない時のデフォルトがsimple)
ExecStartに先ほど確認したJupyterのフルパスを指定します。
UserとGroupは指定しないとrootになってしまうようなので、ec2-userを指定します。
Restart=always は何らかの理由でサービスが終了したときに自動的に再起動する設定です。

ファイルを保存して閉じたら、サービスとして認識されていることを確認します。


$ systemctl list-unit-files --type=service | grep jupyter
jupyter.service                               disabled

あとは、起動することを確認します。


$ sudo systemctl start jupyter
$ sudo systemctl status jupyter

この段階で、ブラウザからもアクセスして使えることを確認しておきましょう。
ここまで上手くいったら、あとは自動的に起動するように設定して完成です。


$ sudo systemctl enable jupyter
$ systemctl list-unit-files --type=service | grep jupyter
jupyter.service                               enabled

EC2でJupyterサーバーを構築する

今となってはGoogleのColaboratoryがあるのであまりニーズがないのですが、
EC2でJupyter notebook環境を構築する方法についてまとめておきます。

前提ですが、OSはAmazon Linux 2を利用し、Pythonの仮想環境はpyenvで作ります。
また、ポートはデフォルトの8888番を使用し、アクセスにはパスワードをかけます。
また、作業ディレクトリは/var/notebookとします。

手順1. インスタンスを立てる

Amazon Linux 2 のAMIを選択し、EC2インスタンスを立てます。
このときセキュリティグループではssh接続に使用する22番ポートと、
Jupyter Notebookに接続するポート(デフォルトでは8888番)を開けておきます。

手順2. pyenv必要なモジュールのインストール

pyenv を使うためのモジュールを順番に入れていきます。
最初にyumを最新化し、git、次にpyenvのドキュメントで指定されているモジュール群を入れます。
FIXME: you may need to install xz to build some CPython version
とありますが、xzを入れておかないと新しめのバージョンのpandasをインポートしたときに、
lzma moduleがないと言う旨の警告が出るのでxzも入れておきます。


# yumを最新の状態にする
$ sudo yum update
# gitのインストール
$ sudo yum install git
# 必要なモジュールのインストール
$ sudo yum install gcc zlib-devel bzip2 bzip2-devel readline-devel
$ sudo yum install sqlite sqlite-devel openssl-devel tk-devel libffi-devel
$ sudo yum install xz xz-devel

手順3. pyenv本体のインストール

ドキュメントの手順にそってインストールします。
リポジトリをcloneし、.bash_profileに設定を入れていきます。
入れたら.bash_profileを再度読み込み、インストールの結果確認としてバージョンを表示します。


$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile

# .bash_profileを読み込む
$ source .bash_profile
# バージョンの確認
$ pyenv --version
pyenv 1.2.23-75-g80e418ec

手順4. Pythonのインストール

pyenvでインストールできるバージョンを確認し、インストールします。
今回は3.8.8を入れることにしました。
このため、notebookは別途インストールします。
ここで、Anacondaを選ぶと、次のステップのnotebookのインストールを飛ばすことができます。


# インストールできるバージョンの一覧を確認する
$ pyenv install -l
# 3.8.8 をインストールする
$ pyenv install 3.8.8
# 3.8.8 に切り替え
$ pyenv global 3.8.8
$ pyenv rehash
# pythonのバージョンを確認
$ python --version 
Python 3.8.8

手順5. Jupyter Notebookのインストール

pipを最新化し、Jupyterをインストールします。
Anacondaを使う場合はこの手順は不要です。


$ pip --version
pip 20.2.3 from /home/ec2-user/.pyenv/versions/3.8.8/lib/python3.8/site-packages/pip (python 3.8)
$ pip install --upgrade pip
$ pip --version
pip 21.0.1 from /home/ec2-user/.pyenv/versions/3.8.8/lib/python3.8/site-packages/pip (python 3.8)

# Jupyter Notebookのインストール
$ pip install notebook

手順6. パスワードトークンの取得

設定ファイルにセットするため、パスワードのハッシュを入手しておきます。
次のコードを実行すると、パスワードを確認含めて2回聞かれますのでそれぞれ入力します。
そして、出力された文字列をどこかに記録しておきます。
昔は sha1:xxxxxxx みたいな文字列でしたが、最近は argon2:xxxxxxx のようです。
アルゴリズムが変わったみたいですね。


$ python -c 'from notebook.auth import passwd;print(passwd())'
Enter password:
Verify password:
argon2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

手順7. 作業ディレクトリ作成

notebookのホームディレクトリとなる作業ディレクトリを作成します。
どこに作ってもいいと思うのですが、自分は/var配下に作るのが好みなので/var/notebookとしています。
本当は専用のユーザーを作った方がいいと思うのですが、自分はそのままec2-userで動かすので、
作ったディレクトリにはec2-userが書き込みができるように権限設定しておきます。


$ cd /var
$ sudo mkdir notebook
$ sudo chown ec2-user:ec2-user notebook

手順8 設定ファイル生成とバックアップ

Jupyter Notebookの設定ファイルを作成し、バックアップをとっておきます。
メッセージに表示されている通り、
/home/ec2-user/.jupyter/jupyter_notebook_config.py
と言うファイルが生成されます。


$ jupyter notebook --generate-config
Writing default config to: /home/ec2-user/.jupyter/jupyter_notebook_config.py
$ cd .jupyter/
$ cp jupyter_notebook_config.py jupyter_notebook_config.py.org

手順9 必要な設定を行う

vimか何かで設定ファイルを開き、必要な設定を施していきます。
vim jupyter_notebook_config.py

必要なのは以下の4行です。


c.NotebookApp.ip = '0.0.0.0'
c.NotebookApp.open_browser = False
c.NotebookApp.notebook_dir = '/var/notebook'
c.NotebookApp.password = 'argon2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

c.NotebookApp.ip の設定は ‘0.0.0.0’ の代わりに ‘*’でも構いません。
デフォルトはlocalhostになっていますが、これにより外部からのアクセスが許可されます。
c.NotebookApp.open_browser = False
は起動時にブラウザを立ち上げない設定です。
c.NotebookApp.notebook_dir はホームディレクトリの設定なので、先ほど作った/var/notebookを設定します。
c.NotebookApp.password
には先ほど保存しておいたパスワードトークンを設定してください。

手順10 起動と接続テスト

これで、準備は整いましたのでnotebookを立ち上げます。


$ jupyter notebook

あとは、
http://{ec2のパブリックIP}:8888
にアクセスすると、Jupyter Notebookのログイン画面が表示されます。
パスワードトークン生成時に入力したパスワードでログインできたら成功です。

この状態だと、ec2インスタンスを起動するたびに、sshで入ってJupyterを起動する必要があり若干手間なので、
次の記事ではインスタンス起動時に自動的にJupyterが立ち上がるように設定する方法を紹介する予定です。

PythonでURL文字列を構築する

前回の記事がURL文字列の各要素への分解だったので、今回は逆に構築です。

まずはクエリパラメーター(クエリストリング)部分についてです。
今更説明するまではないのですが、クエリパラメーターはキーと値を=(イコール)で結び、さらにそれらを&で繋いでいきます。
自分で実装すると地味に手間なのですが、これもurllibに実装があります。

それが urllib.parse.urlencode です。

試しにやってみます。


from urllib import parse

qs = parse.urlencode(
    {
        "key1": "キー1",
        "value1": "値1",
        "id": "123"
    }
)
print(qs)
# key1=%E3%82%AD%E3%83%BC1&value1=%E5%80%A41&id=123

この例で気づかれたと思いますが、このメソッド、とても気の利いてることにパーセントエンコーディングもやってくれます。
辞書の他に、(キー, 値)のタプルの配列も引数として受け取ってくれます。


qs = parse.urlencode(
    [
        ("key1", "キー1"),
        ("value1", "値1"),
        ("id", "123")
    ]
)
print(qs)
# key1=%E3%82%AD%E3%83%BC1&value1=%E5%80%A41&id=123

クエリパラメーターの文字列を構築するのに、非常に便利ですね。

もう一つ、プロトコルやホスト名、パスなどからURL全体を構築するメソッドもあります。
それが、 urllib.parse.urlunparse です。

scheme, netloc, path, parameters, query, fragment の 6要素のタプルを受け取り、
scheme://netloc/path;parameters?query#fragment の形に結合してくれます。
タプルないの要素は省略は不可で、それを含まないようなURLを作るときは空白文字列を入れないといけません。

正直、そこまで便利には感じませんでした。

一応やってみた例です。


print(
    parse.urlunparse(
        (
            "https",
            "analytics-note.xyz",
            "",
            "",
            "s=統計学",
            "",
        )
    )
)
# https://analytics-note.xyz?s=統計学

上の例で分かる通り、クエリストリングはパーセントエンコーディングやってくれません。
そのため、クエリパラメーターに日本語を含む場合は本当は事前に何らかの手段でパーセントエンコーディングしておく必要があります。

urlunparse はそこまで使うメリットがなさそうなので、単純に文字列の結合を実装しても良さそうです。

PythonでURL文字列を要素に分解する

前回に引き続きurllibの話です。
url文字列ははプロトコル、ホスト名、パス、パラメーターなど、いくつかの要素で成り立っています。
urllibを使うとURLの文字列から簡単に各要素を取り出せます。
使うのはurllib.parse.urlparseです。
ドキュメント: urllib.parse.urlparse

使うのは簡単で、URLの文字列を渡すだけです。結果のオブジェクトのプロパティとして各要素を取り出せます。
次の形で分解してくれます。
URL: scheme://netloc/path;parameters?query#fragment

ドキュメントのページのURLを例にしてやってみましょう。


from urllib import parse

url = "https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse"
result = parse.urlparse(url)

# 結果はParseResultオブジェクト
print(result)
# ParseResult(scheme='https', netloc='docs.python.org', path='/3/library/urllib.parse.html', params='', query='', fragment='urllib.parse.urlparse')

# ホスト名
print(result.netloc)
# docs.python.org

# パス
print(result.path)
# /3/library/urllib.parse.html

上の例では存在しませんが、 query でパラメーターが渡されていた場合、その値をパースしたいこともよくあります。
それには、parse_qs を使います。
ドキュメント: urllib.parse.parse_qs
もしくはparse_qslでも構いません。違いは出力が辞書かリストかです。
ドキュメント: urllib.parse.parse_qsl

パラメーターの多いyahoo ファイナンスさんのURLを拝借してやってみましょう。


url = "https://info.finance.yahoo.co.jp/history/?code=998407.O&sy=2021&sm=1&sd=12&ey=2021&em=1&ed=31&tm=d"
result = parse.urlparse(url)
# queryが取得できた
print(result.query)
# code=998407.O&sy=2021&sm=1&sd=12&ey=2021&em=1&ed=31&tm=d

# parse_qs でパースした結果
print(parse.parse_qs(result.query))
# {'code': ['998407.O'], 'sy': ['2021'], 'sm': ['1'], 'sd': ['12'], 'ey': ['2021'], 'em': ['1'], 'ed': ['31'], 'tm': ['d']}

# parse_qslでパースした結果
print(parse.parse_qsl(result.query))
# [('code', '998407.O'), ('sy', '2021'), ('sm', '1'), ('sd', '12'), ('ey', '2021'), ('em', '1'), ('ed', '31'), ('tm', 'd')]

Pythonでパーセントエンコード(URLエンコード)

パーセントエンコードの詳しい説明はWikipedia参照。
参考: パーセントエンコーディング – Wikipedia

要するに、日本語などURLの中で使えない文字列を表示する方法です。
上記のWikipediaのリンクも、「パーセントエンコーディング」に相当する部分が
%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0
になっています。

このようなエンコードと、デコードをPythonで行う方法を調べました。
結論として、url関係のライブラリである、urllibで実装できるようです。

まず、エンコード。
これは、urllib.parse.quote でできます。


from urllib.parse import quote
print(quote("パーセントエンコーディング"))
"""
%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0
"""

次に、逆変換(デコード)です。
これは、urllib.parse.unquote でできます。
パーセントエンコーディングだと長いので、サンプルを少し短くしました。


from urllib.parse import unquote
print(unquote("%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88"))
"""
パーセント
"""