.rute1/

キャリア/心理学/読書/Pythonなどなど

Amazon Linux2 で運用しているMinecraft Serverのバージョンアップ

久しぶりにMinecraftを遊ぼうと思ったらバージョンが上がってたので、サーバー側のバージョンも上げることにしました。

環境はAmazon Linux 2で systemctlでサーバープロセスを管理しています。

まずはサーバーを停止。

$ sudo systemctl stop minecraft_server.service
$ sudo systemctl status minecraft_server.service

無事に停止できたら、新バージョンを公式からダウンロードします。(現時点では、1.17.1が最新でした。)

www.minecraft.net

$ wget https://launcher.mojang.com/v1/objects/a16d67e5807f57fc4e550299cf20226194497dc2/server.jar

実際に動かしているファイル名はminecraft_server.jarなので、ファイル名を変更。
(本来なら元のファイル名を別のファイルに退避などをしても良いです。この環境はバックアップを取得していたので省略)

$ mv server.jar minecraft_server.jar

startしてみましたが、起動しませんでした。

$ systemctl start minecraft_server.service
$ systemctl status minecraft_server.service
 ● minecraft_server.service - Minecraft Server
    Loaded: loaded (/etc/systemd/system/minecraft_server.service; enabled; vendor preset: disabled)
    Active: failed (Result: start-limit) since Fri 2021-07-16 15:06:17 UTC; 439ms ago
   Process: 3519 ExecStart=/bin/bash /home/ec2-user/minecraft/boot (code=exited, status=1/FAILURE)
  Main PID: 3519 (code=exited, status=1/FAILURE)
 
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service: main process exited, code=exited, status=1/FAILURE
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: Unit minecraft_server.service entered failed state.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service failed.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service holdoff time over, scheduling restart.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: start request repeated too quickly for minecraft_server.service
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: Failed to start Minecraft Server.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: Unit minecraft_server.service entered failed state.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service failed.

ログを確認すると、Error: A JNI error has occurred, please check your installation and try again というエラーが見つかりました。
これは、javaのバージョンが古いというエラーです。どうやらMinecraftは1.17系からjava16が必要になったようです。

$ journalctl -ex -u minecraft_server.service
(中略)
 -- Unit minecraft_server.service has begun starting up.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: Error: A JNI error has occurred, please check your installation and try again
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: Exception in thread "main" java.lang.UnsupportedClassVersionError: net/minecraft/server/Main has been compiled by a more recent version
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.lang.ClassLoader.defineClass1(Native Method)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.security.AccessController.doPrivileged(Native Method)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal bash[3519]: at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service: main process exited, code=exited, status=1/FAILURE
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: Unit minecraft_server.service entered failed state.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service failed.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service holdoff time over, scheduling restart.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: start request repeated too quickly for minecraft_server.service
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: Failed to start Minecraft Server.
 -- Subject: Unit minecraft_server.service has failed
 -- Defined-By: systemd
 -- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
 --
 -- Unit minecraft_server.service has failed.
 --
 -- The result is failed.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: Unit minecraft_server.service entered failed state.
 Jul 16 15:06:17 ip-172-31-41-86.ap-northeast-1.compute.internal systemd[1]: minecraft_server.service failed.

元々の環境では java8でした。

$ java -version
 openjdk version "1.8.0_201"
 OpenJDK Runtime Environment (build 1.8.0_201-b09)
 OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)

そこで、javaのバージョンを上げることにします。
そういえばAmazonが管理しているOpenJDKがあったことを思い出したので、ドキュメント通りにインストールします。

docs.aws.amazon.com

$ sudo rpm --import https://yum.corretto.aws/corretto.key
$ sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo
$ sudo yum install -y java-16-amazon-corretto-devel

これだけで簡単にバージョンが上がりました。

$ java --version
 openjdk 16.0.1 2021-04-20
 OpenJDK Runtime Environment Corretto-16.0.1.9.1 (build 16.0.1+9)
 OpenJDK 64-Bit Server VM Corretto-16.0.1.9.1 (build 16.0.1+9, mixed mode, sharing)

これで無事に起動でき、バージョンアップできました!

$ sudo systemctl start minecraft_server.service
$ systemctl status minecraft_server.service
 ● minecraft_server.service - Minecraft Server
    Loaded: loaded (/etc/systemd/system/minecraft_server.service; enabled; vendor preset: disabled)
    Active: active (running) since Fri 2021-07-16 15:35:13 UTC; 7s ago
  Main PID: 23650 (bash)
    CGroup: /system.slice/minecraft_server.service
            ├─23650 /bin/bash /home/ec2-user/minecraft/boot
            └─23651 java -Xms3072M -Xmx3072M -jar /home/ec2-user/minecraft/minecraft_server.jar nogui
 (略)

『Pythonトリック』を読んだ

Pythonトリック』が良書だったので紹介します。

Pythonトリック

Pythonトリック

翔泳社のページからPDFでの購入もできます。

私は普段の業務ではRubyを使うことの方が多いのですが、Pythonにも興味があり趣味で書くこともあります。 pythonの言語の仕組みやより良いとされている書き方を勉強したかったのですがFluent Python ―Pythonicな思考とコーディング手法Effective Python 第2版 ―Pythonプログラムを改良する90項目は分厚すぎて尻込みしていたところ こちらの本はコンパクトで立ち読みしたところちょうどよく知りたいことが書かれていた感じだったので購入してみました。

目次

CHAPTER 1 はじめに
1.1 Pythonトリックとは何か
1.2 本書はどのように役立つか
1.3 本書の読み方

CHAPTER 2 よりクリーンなPythonのためのパターン
2.1 アサーションによる安全対策
2.2 無頓着なコンマの配置
2.3 コンテキストマネージャーとwith文
2.4 アンダースコアとダンダー
2.5 文字列のフォーマットに関する衝撃の事実
2.6 「The Zen of Python」の隠しコマンド

CHAPTER 3 効果的な関数
3.1 Pythonの関数はファーストクラスオブジェクト
3.2 ラムダは単一式の関数
3.3 デコレータの威力
3.4 *argsと**kwargs
3.5 引数のアンパック
3.6 ここから返すものは何もない

CHAPTER 4 クラスとオブジェクト指向プログラミング
4.1 オブジェクトの比較:”is”と”==”
4.2 文字列変換:すべてのクラスに_repr_が必要
4.3 カスタム例外クラスを定義する
4.4 趣味と実益を兼ねたクローンオブジェクトの作成
4.5 抽象基底クラスは継承に待ったをかける
4.6 名前付きタプルは何に役立つか
4.7 クラス変数とインスタンス変数の落とし穴
4.8 インスタンスメソッド、クラスメソッド、静的メソッドの謎を解く

CHAPTER 5 Pythonの一般的なデータ構造
5.1 ディクショナリ、マップ、ハッシュテーブル
5.2 配列
5.3 レコード、構造体、DTO
5.4 セットとマルチセット
5.5 スタック(LIFO)
5.6 キュー(FIFO)
5.7 優先度付きキュー

CHAPTER 6 ループとイテレーション
6.1 パイソニックなループの書き方
6.2 内包を理解する
6.3 リストのスライスとすし演算子
6.4 美しいイテレータ
6.5 ジェネレータは単純化されたイテレータ
6.6 ジェネレータ式
6.7 イテレータチェーン

CHAPTER 7 ディクショナリのトリック
7.1 ディクショナリのデフォルト値
7.2 趣味と実益を兼ねたディクショナリのソート
7.3 ディクショナリを使ってswitch/case文をエミュレートする
7.4 型破りなディクショナリ式
7.5 ディクショナリのいろいろなマージ法
7.6 ディクショナリの出力を整える

CHAPTER 8 パイソニックな生産性向上テクニック
8.1 Pythonのモジュールとオブジェクトを調べる
8.2 仮想環境を使ってプロジェクトの依存関係を分離する
8.3 バイトコードの裏側を覗く

CHAPTER 9 最後に
9.1 Python開発者のためのメールマガジン
9.2 PythonistaCafe:Python開発者のコミュニティ

2章は手取り早く役にたつTips集といった感じで、この章だけでも読むとこの本がどんな本かわかると思います。

3章以降は関数、クラス、イテレータ、データ構造などそれぞれのトピックを取り上げて色々なテクニックを紹介する内容になっています。 各章の内容は独立していて、どこから読んでも良いような内容になっているので、とりあえず興味のある章だけ読むという読み方をしても良いと思います。

プログラミング言語初心者でオブジェクト志向はなんとなくわかるというレベルなら4章から、すでに他の言語をある程度理解していてPythonならではの書き方や特徴を知りたいというかたは5~6章、関数型の書き方に興味があるという方は3章がおすすめです。

さらに、各節の最後には内容の簡単な要約もついているので、上級者の場合は最初に要約から読んで知らなった項目だけ読むということもできます。 (私の場合は70%くらいが初めて知る内容でした。)

with文に対応したObjectの作り方

ここからは私が特に面白いと感じた内容について簡単に紹介していきます。

with文はファイル操作などでよく見る書き方です。

with open('hello.txt', 'w') as f:
    f.write('hello, world')

この場合はwithブロックから抜けたらすぐに自動でf.close()を行うことで、安全にリソースを管理することができるようになっています。 with文に対応するオブジェクト(=コンテキストマネージャと呼ばれるらしい)は、このように必ず最初と最後に行いたい処理がある場合に便利な機能です。

実はこのようなコンテキストマネージャは自作クラスや関数でも簡単に作れるようです。
私が試しに考えてみた、環境変数を一時的に変更できる関数を紹介します。

import contextlib
import copy
from os import environ

@contextlib.contextmanager
def env(env: str, value):
    original = copy.copy(environ[env]) # 元の環境変数を保存
    environ[env] = value # 環境変数を上書き
    yield
    environ[env] = original # 元あった値に戻す

# 使い方
print(environ["USER"]) # tarou

with env("USER", "honako"):
    print(environ["USER"]) # hanako

print(environ["USER"]) # tarou

詳しい仕組みはぜひ書籍を読んでいただきたいですが、ざっくりと解説すると、@contextlib.contextmanagerというデコレータを使うことで、 自作のenv関数をwth文に対応させています。
関数の中のyieldまでがwith文の最初に処理される部分、yield以降がwithブロックから抜けた後に実行される処理です。
ここでは最初に既存の環境変数を別の変数に保存し、最後に再び元の環境変数にセットし直すことで一時的に環境変数を変更するということができました。

これ以外にも最初と最後になんらかの処理を実行したいという時にはwith文に対応した関数を使えば書くほうも読む方も楽になりそうなので、便利に使えそうです。

また、この本では関数ではなくオブジェクト(クラス)をwith文に対応させる方法も書かれています。 本の中で宿題として「time.time関数を使ってコードブロックの実行時間を計測するコンテキストマネージャーを実装してみてください」と書かれていたので、 自分なりに考えて実装してみました。

Python stop watch · GitHub

こちらはクラスベースの実装になっていますが、ネタバレになってしまう可能性もあるので心配な方はぜひ書籍を読んでから見てみてください。

Cloud9から既存EC2(tokyo)に初回設定のSSHができない問題を解決

Cloud9から既存EC2(tokyo)に初回設定のSSHができない

Tokyoリージョンに立てた開発用のEC2にCloud9(シンガポールリージョン)からの初期設定のSSHができなくて困りました。
以下のことを行うことで設定できました。

  • nodejsがEC2に入っていることを確認する
    • node -v
  • /home/user/.ssh/authorized_keys にコンソールからコピーした公開鍵を貼る
  • セキュリティーグループのTCP,PORT 22を一旦0.0.0.0/0に解放する
  • Environment pathはデフォルトで/home/ec2-userを見に行くため、ubuntucentosの場合やユーザー名を変更している場合は個別で設定する。
    • /home/ubuntu

セキュリティグループ をよりセキュアに設定したい

セキュリティグループ が0.0.0.0/0に解放したままでは危険のため、セキュリティグループ の設定をAWSシンガポールリージョンのCloud9のIPのみに設定します。
AWSのIP範囲はこちらから確認できます。
AWS IP アドレスの範囲 - アマゾン ウェブ サービス
このドキュメントの通りに、jsonからIPを抜き出します。

# 一旦jsonをファイルに保存
curl https://ip-ranges.amazonaws.com/ip-ranges.json > ip-ranges.json

# Linux等、jqが使える場合
jq -r '.prefixes[] | select(.region=="ap-southeast-1") | select(.service=="CLOUD9") | .ip_prefix' < ip-ranges.json

>>> 13.250.186.128/27
>>> 13.250.186.160/27

これで、13.250.186.128/27と13.250.186.160/27に対してセキュリティグループ のTCP, Port 22を解放すればOKです。
Cloud9のリージョンがシンガポール以外の場合は.region==""の部分を適宜変更してください。

AWS クロスアカウント権限管理 「リソースポリシーからrootに許可するパターン」編

クロスアカウントで色々いじっていて、IAMの権限管理についてのパターンがなんとなくわかってきたのでメモします。

クロスアカウントでの権限管理は色々なパターンがあるようですが、今回扱うのは「AアカウントのリソースポリシーでBアカウントのrootを許可→Bアカウントの中でポリシーアタッチ」のパターンです。

具体的に見ていきましょう。まずはよくあるS3のパターンです。

別アカウントのS3バケットにLambdaからアクセスする

ここではアカウントID:111111111111アカウント1からアカウントID:0000000000000000アカウント0のS3バケットにアクセスしたいとします。 f:id:rutei:20190105180724p:plain
アカウント0のコンソールで、S3のバケットポリシーを以下のように設定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Example permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::1111111111111:root"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::BUCKET_NAME/*"
        }
    ]
}

ここではアカウント1のrootユーザーに対してS3バケットへのアクセスを許可しています。

次に、許可をもらったアカウント1のコンソールで以下のポリシーを作成し、S3バケットにアクセスするLambdaにアタッチします。

{
    "Version": "2012-10-17",
    "Statement": [ 
            {
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::BUCKET_NAME/*",
            "Effect": "Allow"
        }
    ]
}

これで、ポリシーをアタッチされたLambdaはアカウント0のS3バケットに対してアクセスすることができるようになります。
まずはアカウントのrootユーザーに権限を与える → 権限を得たroot(admin)ユーザーがリソースに対して権限を与えるという流れでわかりやすいと思います。
もちろん、最初にバケットポリシーで許可したActionしかできないようになっているので、例えば今回の場合s3:GetObject以外の操作権限はLambda にもrootユーザーにも与えられていません。
LambdaからObjectを新たにPUTしたりするためには、再びアカウント0の管理者にバケットポリシーからs3:PutObjectを許可してもらう必要があります。

別アカウントのLambdaにLambdaからアクセスする

先ほどと同様にアカウント1のLambdaからアカウント0のLambdaにアクセスしたいとします。
f:id:rutei:20190105181018p:plain
Lambdaの場合はリソースポリシーというものがあるので、ここで、アカウント1のrootユーザーに権限を与えます。
参考: AWS Lambda でリソースベースのポリシーを使用する (Lambda 関数ポリシー) - AWS Lambda この操作は現在AWS CLIAPIでしか行えないようです。

aws lambda add-permission \
--function-name FUNC_NAME \
--statement-id Example permissions \
--action lambda:InvokeFunction \
--principal 111111111111 \
--output json 

上記のコマンドを実行すると以下のjsonが返ってきます。

{
    "Statement": "{\"Action\":[\"lambda:InvokeFunction\"],
                   \"Resource\":\"arn:aws:lambda:YOUR_REGION:000000000000:function:FUNC_NAME\",
                   \"Effect\":\"Allow\",
                   \"Principal\":{\"AWS\":\"111111111111\"},
                   \"Sid\":\"Example permissions\"}"
}

CLIなので、見ためは違いますが、やっていることとしては先ほどのバケットポリシーと対して変わりません。アカウント1のrootユーザーに対してアカウント0のLambdaへのlambda:InvokeFunctionの権限を与えています。
次に、アカウント1のコンソールから、Lambda(アクセスする方)の実行ロールにアカウント0のLambdaへアクセスする権限を与えるポリシーをアタッチします。

{
    "Version": "2012-10-17",
    "Statement": [ 
        {
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:YOUR_REGION:000000000000:function:FUNC_NAME",
            "Effect": "Allow"
        }
    ]
}

これで、アカウント1のLambdaからアカウント0のLambdaにアクセスすることができるようになります。

おまけ: kmsのキーポリシーをIAMポリシーで管理する

実はクロスアカウントの権限管理以外にも、似たような権限の与え方をするパターンがあります。
それが、KMSのキーポリシーです。
AWSでは若干複雑なことに、KMSで作ったCMKに対する権限をKMSのキーポリシーで管理するということになっています。
ただ、特にLambdaに関しては実行ロールに付与されているIAMポリシーでLambdaの権限を管理できた方がわかりやすいため、IAMポリシーで管理できた方が便利だと思います。
そういった場合に、キーポリシーをIAMポリシーで管理できるようにするキーポリシーの書き方が以下です。

{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::111111111111:root"},
  "Action": "kms:*",
  "Resource": "*"
}

これで、IAMポリシーでKMSのCMKの権限を管理できるようになります。 Lambdaの実行ロールには以下の権限を付与します。

{
    "Version": "2012-10-17",
    "Statement": [
         {
            "Action": [
                "kms:*"
            ],
            "Resource": "arn:aws:kms:YOUR_REGION:111111111111:key/xxxxxxxxxx",
            "Effect": "Allow"
        }
    ]
}

【Python】responder でHello, World ! 0.0.0.0をhostに指定する方法

responder でHello World! 0.0.0.0をhostに指定する方法

人間のためのイケてるPython WebFramework「responder」、そして作者のKenneth Reitzについて - フリーランチ食べたい

こちらの記事で話題になっているresponderを早速試して見ました。
Hello World ! するまでに2つひっかかったポイントがあったので、共有します。
ちなみにresponderどころかDjangoもFlaskも使ったことのない初心者なので、おかしな内容になっているかもしれません。ご容赦ください。
※2018年12月2日時点での情報です。

環境 * AWS EC2 Ubuntu Server 18.04 LTS (HVM), SSD Volume Type * Python 3.6.5

ModuleNotFoundError: No module named 'starlette.lifespan'

とりあえずこちらのクイックスタート通りにHello World ! を書いてみました。   Quick Start! — responder 1.1.2 documentation

import responder
api = responder.API()
@api.route("/")
def hello_world(req, resp):
    resp.text = "hello, world!"
api.run()

実行したところModuleNotFoundError: No module named 'starlette.lifespan’というエラーが出てしまいました。
これはgithubのissueにもあげられていました。
ModuleNotFoundError: No module named ‘starlette.lifespan’ · Issue #255 · kennethreitz/responder · GitHub.

problem solved by using starlette 0.8 version

とりあえず今のところはstarletteのバージョン0.8を使えば解決するようです。
pip install starlette==0.8

hostに0.0.0.0を指定したい

ubuntu@ip-172-31-43-88:~/dev/test$ python hello.py
INFO: Started server process [17400]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:5042 (Press CTRL+C to quit)

これはEC2を開発環境にしていると毎回困ることなのですが、localhost127.0.0.1にアクセスするのが困難です。
0.0.0.0を開くようにしないとローカルのブラウザから確認できません。

Responderのコードを読むと、どうやら環境変数として”PORT”が設定されている場合は0.0.0.0:PORTが開かれ、それでない場合は127.0.0.1:5042になるようです。

if "PORT" in os.environ:
    if address is None:
        address = "0.0.0.0"
        port = int(os.environ["PORT"])

if address is None:
    address = "127.0.0.1"
if port is None:
    port = 5042

ということで適用にわかりやすいPORTを設定して、

ubuntu@ip-172-31-43-88:~/dev/test$ export "PORT"=10001

EC2のセキュリティグループも変更します。 f:id:rutei:20181202020216p:plain

そしてブラウザからアクセスすると、 f:id:rutei:20181202020213p:plain

Hello World ! が無事表示されました 。

ubuntu@ip-172-31-43-88:~/dev/test$ python hello.py
INFO: Started server process [17405]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://0.0.0.0:10001 (Press CTRL+C to quit)
INFO: ('m.y.i.p', 53094) - "GET / HTTP/1.1" 200

追記: api.run()の引数にaddressとportを与えても同様のことができるようです。

api.run(address='0.0.0.0',port=10001)

【Python】requestsでボタンを擬似クリック

requestsでボタンを擬似クリック

Beautiful Soupでhrefが取得できないボタン等のクリック動作をrequestsで再現する方法を調べたので、まとめます。

hrefが取得できないボタン

例えばこのページの「CSVダウンロード」ボタン部分などはBeatiful Soupでもhrefが取得しにくいためにrequestsでのスクレイピングが困難です。
CSVダウンロード f:id:rutei:20181130205349p:plain

csvダウンロード」ボタンのソースコードはこのようになっています。

<input class="btn btn-primary" type="submit" value="CSVダウンロード">

おそらくこのボタンを押すとなんらかのscriptが動いてダウンロードが始まるものと思います。
requestsでこのボタンをクリックするという動作を再現することはできないので、seleniumを使うといった選択肢をとることが多いかもしれませんが、場合によってはrequestsだけでボタンをクリックしたのと同等の通信を再現することができます。
以下がその方法です。

crhomeデベロッパーツールでnetworkを見てみる

f:id:rutei:20181130205413p:plain

デベロッパーツールを表示した状態でボタンをクリックすると、networkタブでどのような通信が行われているか確認することができます。
今回のダウンロードもバッチリURLを確認することができました。
URLはhttps://www.itdashboard.go.jp/Csv/downloadで、ここにPOSTでdataを送れば良さそうです。
cookieなどはrequestsのsession()を使えば処理してもらえます。
POSTで送るdataもデベレッパーツールから確認できます。
f:id:rutei:20181130205424p:plain

最終的なコードはこのようになりました。

import requests

# ページにアクセス
session = requests.Session()
res = session.get('https://www.itdashboard.go.jp/DataFeeds/csv')
res.raise_for_status() # エラーならここで例外を発生させる

# dataに項目をセット
data = {
    "_method": "POST",
    "year": "",
    "modelname": "BasicInformationAll"
}

# クリック時の通信
res = session.post('https://www.itdashboard.go.jp/Csv/download', data=data)
res.raise_for_status() # エラーならここで例外を発生させる

# レスポンスで返ってきたコンテンツをcsvとして保存
contentType = res.headers['Content-Type']
contentDisposition = res.headers['Content-Disposition']
ATTRIBUTE = 'filename='
fileName = contentDisposition[contentDisposition.find(ATTRIBUTE) + len(ATTRIBUTE):]

with open(fileName, 'wb') as saveFile:
        saveFile.write(res.content)

ファイルを保存する部分はこちらの記事を参考にしました。
Pythonのrequestsを利用してファイルダウンロードする方法 - Qiita