pythonのaws-sdkのboto3を使ってライブラリのコードをちゃんと読んでみる①
boto3とは?
pythonのaws-sdkです。
他の言語のaws-sdkは大体aws-sdk
みたいな名前で公開されていることが多いのですが、なぜかpythonだけboto3
っていう名前です。
個人的にはこのドキュメントすごい読みやすくて好きなライブラリです。
awsはapiの設計がちゃんとしててわかりやすいっていうイメージがあります。
あと自分が使い慣れているというものあって、このライブラリを使ってちゃんとコードを読めるようになっていこうと思います。(今までは雰囲気でやっていた。。。)(雰囲気でやっている人も多いはず。)
ドキュメントを読む
一番シンプルそうな、EC2のclientのdescribe_instances()メソッドを例にして見てみます。
まずはドキュメントを見てみます。
response = client.describe_instances( Filters=[ { 'Name': 'string', 'Values': [ 'string', ] }, ], InstanceIds=[ 'string', ], DryRun=True|False, MaxResults=123, NextToken='string' )
この下のparamsの説明でREQUIREDなparamはないので、一旦シンプルに全取得してみます。
>>> import boto3 >>> client = boto3.client("ec2") >>> response = client.describe_instances() >>> print(response)
これでEC2の情報が取得できました。(ただし、aws config
コマンドで事前にprofileなどは設定しているものとします。)
基本このように、ドキュメントがしっかりしているライブラリはそれ道理にやればうまくいきますね。
(AWSレベルになるとソースコード読む必要性に駆られることなさそう)
ソースコードを読む
まずは、この一覧の中のboto3っていうディレクトリ配下にソースコードがあるのかな?(これも雰囲気でしかわからないレベル)
正直どこにあるのかよくわからない。
じゃあ実際にpythonから使うときはどうやって読み込まれるのか。
pipでinstallしたライブラリの保存場所とは。
Python Tips:ライブラリ・モジュールの場所を調べたい - Life with Python
これで調べられると。
>>> import boto3 >>> print(boto3.__file__) /Users/my0shym/.anyenv/envs/pyenv/versions/3.6.0/lib/python3.6/site-packages/boto3/__init__.py
そういえば、anyenvとか使ってたな。なんのパッケージ管理システムを使ってたのかもはやわからなくなっていた。
$ cd /Users/my0shym/.anyenv/envs/pyenv/versions/3.6.0/lib/python3.6/site-packages/boto3/ $ ls -l total 72 -rw-r--r-- 1 my0shym staff 3338 9 12 2018 __init__.py drwxr-xr-x 7 my0shym staff 224 9 12 2018 __pycache__ -rw-r--r-- 1 my0shym staff 1490 9 12 2018 compat.py drwxr-xr-x 12 my0shym staff 384 9 12 2018 data drwxr-xr-x 16 my0shym staff 512 9 12 2018 docs drwxr-xr-x 8 my0shym staff 256 9 12 2018 dynamodb drwxr-xr-x 6 my0shym staff 192 9 12 2018 ec2 drwxr-xr-x 4 my0shym staff 128 9 12 2018 examples -rw-r--r-- 1 my0shym staff 3993 9 12 2018 exceptions.py drwxr-xr-x 11 my0shym staff 352 9 12 2018 resources drwxr-xr-x 6 my0shym staff 192 9 12 2018 s3 -rw-r--r-- 1 my0shym staff 19614 9 12 2018 session.py -rw-r--r-- 1 my0shym staff 3095 9 12 2018 utils.py
これはboto3ディレクトリの下の構造と一緒だ。これでいいのだった。
ライブラリをimportするとまずinit.pyが読み込まれる。
init.pyは110行程度のコード。
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import logging from boto3.session import Session __author__ = 'Amazon Web Services' __version__ = '1.9.2' # The default Boto3 session; autoloaded when needed. DEFAULT_SESSION = None def setup_default_session(**kwargs): """ Set up a default session, passing through any parameters to the session constructor. There is no need to call this unless you wish to pass custom parameters, because a default session will be created for you. """ global DEFAULT_SESSION DEFAULT_SESSION = Session(**kwargs) def set_stream_logger(name='boto3', level=logging.DEBUG, format_string=None): """ Add a stream handler for the given name and level to the logging module. By default, this logs all boto3 messages to ``stdout``. >>> import boto3 >>> boto3.set_stream_logger('boto3.resources', logging.INFO) For debugging purposes a good choice is to set the stream logger to ``''`` which is equivalent to saying "log everything". .. WARNING:: Be aware that when logging anything from ``'botocore'`` the full wire trace will appear in your logs. If your payloads contain sensitive data this should not be used in production. :type name: string :param name: Log name :type level: int :param level: Logging level, e.g. ``logging.INFO`` :type format_string: str :param format_string: Log message format """ if format_string is None: format_string = "%(asctime)s %(name)s [%(levelname)s] %(message)s" logger = logging.getLogger(name) logger.setLevel(level) handler = logging.StreamHandler() handler.setLevel(level) formatter = logging.Formatter(format_string) handler.setFormatter(formatter) logger.addHandler(handler) def _get_default_session(): """ Get the default session, creating one if needed. :rtype: :py:class:`~boto3.session.Session` :return: The default session """ if DEFAULT_SESSION is None: setup_default_session() return DEFAULT_SESSION def client(*args, **kwargs): """ Create a low-level service client by name using the default session. See :py:meth:`boto3.session.Session.client`. """ return _get_default_session().client(*args, **kwargs) def resource(*args, **kwargs): """ Create a resource service client by name using the default session. See :py:meth:`boto3.session.Session.resource`. """ return _get_default_session().resource(*args, **kwargs) # Set up logging to ``/dev/null`` like a library is supposed to. # http://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library class NullHandler(logging.Handler): def emit(self, record): pass logging.getLogger('boto3').addHandler(NullHandler())
client関連の部分だけ見てみます。
import logging from boto3.session import Session __author__ = 'Amazon Web Services' __version__ = '1.9.2' # The default Boto3 session; autoloaded when needed. DEFAULT_SESSION = None
これが呼び出されます。
def client(*args, **kwargs): """ Create a low-level service client by name using the default session. See :py:meth:`boto3.session.Session.client`. """ return _get_default_session().client(*args, **kwargs)
def _get_default_session(): """ Get the default session, creating one if needed. :rtype: :py:class:`~boto3.session.Session` :return: The default session """ if DEFAULT_SESSION is None: setup_default_session() return DEFAULT_SESSION
def setup_default_session(**kwargs): """ Set up a default session, passing through any parameters to the session constructor. There is no need to call this unless you wish to pass custom parameters, because a default session will be created for you. """ global DEFAULT_SESSION DEFAULT_SESSION = Session(**kwargs)
ここまでででDEFAULT_SESSIONにSessionモジュールが読み込まれて、それに対してclient(*args, **kwargs)
メソッド
def client(self, service_name, region_name=None, api_version=None, use_ssl=True, verify=None, endpoint_url=None, aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None, config=None): return self._session.create_client( service_name, region_name=region_name, api_version=api_version, use_ssl=use_ssl, verify=verify, endpoint_url=endpoint_url, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, aws_session_token=aws_session_token, config=config)
self._session.create_client
は以下より定義されている。
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None, region_name=None, botocore_session=None, profile_name=None): if botocore_session is not None: self._session = botocore_session else: # Create a new default session self._session = botocore.session.get_session()
つまりbotocore.session.get_session()
から。
botocore.session.get_session()
のbotocoreのソースコードはどこにあるのか?
調べようと思ったところでタイムアップになってしまった。
続きは次回、あるいは更新していこうと思います。
aws-cliを使ってs3にgz圧縮ファイルをアップロードするときの注意点
aws-cliを使ってローカルからファイルをアップロードしようとしたのですが、csv.gzなファイルをアップロードするときにちょっとハマってしまったのでメモとして残しておきます。
まずはcsvファイルを用意します。
$cat sample.csv hoge,fuga foo,bar $ gzip sample.csv $ ls sample.csv.gz
gzファイルになりました。
これを普通に$ aws s3 cp
コマンドを使ってs3にコピーしようとしてみます。
$ aws s3 cp sample.csv.gz s3://my-bucket/hoge_dir/
できました。
と、これをブラウザからダウンロードしてみると、ファイルはsample.csvという名前になっています。 お、これはgzをいい感じに解凍してくれているっぽいですね。
開いてみます。
Oops、だめだ。これcsvになっていない。 文字化けしてしまいました。
手動で同じことをやってみます。 すると、ダウンロードしても勝手に解凍されない。 sample.csv.gzファイルとしてダウンロードされています。 ローカルで解凍します。 csvファイルになったのでそれを開いてみます。
$cat sample.csv hoge,fuga foo,bar
ちゃんと開けていますね。
aws-cliでアップロードしたファイルをもう一度見てます。 ファイル名をcsv→csv.gzに戻してみます。 そしてこれを解凍し、開いてみます。 すると、正常に開けましたー
ということで、s3からダウンロードするときにgzを解凍していないのに解凍している風を出していたのが問題でした。
しかしs3はこんな余計なことをやってくれるんだろうか?
GUIからアップロードしたのと、CLIでコピーしたファイルの何が違うのか調べてみました。
すると、 プロパティ > メタデータ を見てみます。 正常に動作できるGUIでアップロードした方には、 Content-Type application/octed-stream とある。 一方で、挙動がおかしい方は Content-Type text/csv とある。
application/octed-streamとは?
https://wa3.i-3-i.info/word15821.html
まぁ「application/octet-stream」って単語が出てきたら「その他のファイル(ファイルの種類は気にするな)なんだな~」と、お考えください。
とのことで、よくわからないけど、これにすると大丈夫らしい。
$ aws s3 cp sample.csv.gz s3://my-bucket/hoge_dir/ --content-type --content-type application/octed-stream
とすると、無事正しい挙動になりました。
AWSのEC2でAirflow実行環境を構築してチュートリアルを動かしてみる
今、airflowが熱いらしいです。
そこら編の解説は他の有用や記事に任せて、とりあえずチュートリアル動かしてみた備忘録を残しておきます。
AWS環境
Amazon Linux 2 セキュリティグループは sshの22番 ウェブコンソールの8080番 を開けておきます
大体チュートリアル見てやればうまくいきますが、ちょっとつまづきました。
何をどう見てこうなったかはもはや忘れましたが、ググりまくれば行けるはずです。
これがいいのかはわかりませんが、とりあえず動きます。
Quick Start — Airflow Documentation
Dockerとかもあるみたいなので、環境構築面倒くさいなってなったらそっちをやってみてもいいかもしれません。
以下、ターミナルから
$ sudo su - # とりあえず諸々インストール yum install -y sudo python3 gcc git python3-devel zlib-devel bzip2-devel tree tmux tig # 環境変数セット export SLUGIFY_USES_TEXT_UNIDECODE=yes # pipのinstall pip3 install --upgrade setuptools pip3 install apache-airflow tenacity==5.0.2 # airflow開始 airflow initdb airflow webserver -p 8080
これで一旦webコンソール画面は表示されました
ここから、チュートリアルに沿ってやってみます。
Tutorial — Airflow Documentation
チュートリアルは解説もちゃんとあって、丁寧だと思うんですが、ウェブコンソール周りの見方が最初よくわからなかったです。
それの備忘録も兼ねてやってみたいと思います。
正しいかどうか分からないですが、とりあえず初心者が動かしてみたって感じということで許してください。
/root/airflow/airflow.cfgファイルに
dags_folder = /root/airflow/dags
って書いてあるところがあります。
デフォルトではこの下にpythonスクリプトを置いておくと読み込んでくれます。
最初はディレクトリもないので作りましょう。
以下、チュートリアルのまんまです。
このスクリプトの内容はたくさん解説している記事があるので、中身は触れないことにします。
とりあえずtaskの中でBashを実行しています。
""" Code that goes along with the Airflow tutorial located at: https://github.com/apache/airflow/blob/master/airflow/example_dags/tutorial.py """ from airflow import DAG from airflow.operators.bash_operator import BashOperator from datetime import datetime, timedelta default_args = { 'owner': 'airflow', 'depends_on_past': False, 'start_date': datetime(2015, 6, 1), 'email': ['airflow@example.com'], 'email_on_failure': False, 'email_on_retry': False, 'retries': 1, 'retry_delay': timedelta(minutes=5), # 'queue': 'bash_queue', # 'pool': 'backfill', # 'priority_weight': 10, # 'end_date': datetime(2016, 1, 1), } dag = DAG('tutorial', default_args=default_args, schedule_interval=timedelta(days=1)) # t1, t2 and t3 are examples of tasks created by instantiating operators t1 = BashOperator( task_id='print_date', bash_command='date', dag=dag) t2 = BashOperator( task_id='sleep', bash_command='sleep 5', retries=3, dag=dag) templated_command = """ {% for i in range(5) %} echo "{{ ds }}" echo "{{ macros.ds_add(ds, 7)}}" echo "{{ params.my_param }}" {% endfor %} """ t3 = BashOperator( task_id='templated', bash_command=templated_command, params={'my_param': 'Parameter I passed in'}, dag=dag) t2.set_upstream(t1) t3.set_upstream(t1)
これの中の
dag = DAG('tutorial', default_args=default_args, schedule_interval=timedelta(days=1))
という記述のtutorial
がウェブコンソールでの一つのDAGになっています。
このままだと、すでにtutorialという名前のDAGが存在しているので、確認のためちょっと変更します。
以下、変更点
dag = DAG('tutorial', default_args=default_args, schedule_interval=timedelta(days=1)) ↓ dag = DAG('tutorial_man', default_args=default_args, schedule_interval=timedelta(minutes=1))
実行を早く確認したいので、days=1からminutes=1としました。
これを
/root/airflow/dags/tutorial_man.py
に保存
これでウェブコンソールを確認してみます。
。。。
。。。。。
と、全然追加されません。
どうやら以下も実行しておかないとだめだったっぽいです。
$ airflow scheduler
これはスケジューラーを登録する用のやつっぽい。
これを実行しないでも、ウェブコンソールがちゃんと表示されているので動いているっぽいけど、新規に追加されないという罠がありました。
さて、どうでしょう。
追加されていました!!!
あとは、offとなっているところをonにポチッとします。
すると数分後、無事タスクがsuccessとなっていましたー
あとはログを見てみるなり、GUIの利点を最大限に活かして色々ポチポチして遊んでみるのも良いでしょう。
公式チュートリアルではBashOperatorを使ってBashを実行しましたが、次回PythonOperatorを使ってpythonの関数を実行していきたいと思います。 (呼び出し方に微妙に罠がありました。。。)
いい加減DockerとECSを整理する①
Dockerの概念が腑に落ちないこと早1年。
そろそろこれじゃだめだって思い始めて、復習する万。
install方法は割愛しておくことにする。
というか忘れてしまった。
Mac High Sierra 10.13.5
$ docker -v Docker version 17.12.0-ce, build c97c6d6
docker-composeとやらも入っておる。
$ docker-compose -v docker-compose version 1.18.0, build 8dd22a9
まずはOSを起動してみる centosを使ってみる。 と、その前に、現在の状況を確認
現在起動しているコンテナを確認する。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hogee_web latest 13cc204c812e 9 days ago 881MB ruby 2.3.6 7ab6e81790b8 3 months ago 722MB myapp_web latest 1b99a62bd7f1 3 months ago 841MB <none> <none> 33139e4ab9e2 3 months ago 808MB mysql 5.7 5d4d51c57ea8 4 months ago 374MB ruby 2.3.0 7ca70eb2dfea 2 years ago 725MB
imageにはこんなものたちがある。
ここにはcentosはない。
続いて現在のdockerのプロセスを確認
すべてのコンテナを確認。
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 67c4c9131574 hogee_web "bundle exec rails s…" 8 days ago Exited (1) 8 days ago hogee_web_run_6 60fc65437813 hogee_web "bundle exec rails s…" 8 days ago Exited (1) 8 days ago hogee_web_run_5 0fb85edc2110 hogee_web "bundle exec rails s…" 8 days ago Exited (1) 8 days ago hogee_web_run_4 2c498cd732f6 hogee_web "bundle exec rake db…" 9 days ago Exited (0) 9 days ago hogee_web_run_3 e97a1e89c87e hogee_web "bundle exec rake db…" 9 days ago Exited (0) 9 days ago hogee_web_run_2 126811cf559f hogee_web "bundle exec rails s…" 9 days ago Exited (1) 8 days ago hogee_web_1 e78c4418f446 hogee_web "rails new . --force…" 9 days ago Exited (1) 9 days ago hogee_web_run_1 faed05fdd12c mysql:5.7 "docker-entrypoint.s…" 9 days ago Exited (255) 6 days ago 3306/tcp hogee_db_1 5a079a80a50c myapp_web "rails db:migrate" 3 months ago Exited (0) 3 months ago myapp_web_run_5 fa4cd6b00acc myapp_web "rails g scaffold us…" 3 months ago Exited (0) 3 months ago myapp_web_run_4 13c8f6ce670c myapp_web "rails db:create" 3 months ago Exited (0) 3 months ago myapp_web_run_3 2b96f67a9f7a myapp_web "bundle exec rails s…" 3 months ago Exited (255) 3 months ago 0.0.0.0:3000->3000/tcp myapp_web_1 858bc9dfd0c2 myapp_web "rails db:create" 3 months ago Exited (1) 3 months ago myapp_web_run_2 038519291a53 33139e4ab9e2 "rails new . --force…" 3 months ago Exited (0) 3 months ago myapp_web_run_1 91592f781321 mysql:5.7 "docker-entrypoint.s…" 3 months ago Exited (255) 3 months ago 3306/tcp myapp_db_1
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
現在動いているdockerのプロセスはない。
$ docker run -it centos:latest bash Unable to find image 'centos:latest' locally latest: Pulling from library/centos 7dc0dca2b151: Pull complete Digest: sha256:b67d21dfe609ddacf404589e04631d90a342921e81c40aeaf3391f6717fa5322 Status: Downloaded newer image for centos:latest [root@de5bdc2e3f95 /]#
処理終了までに数分かかりました。
docker run
はホスト名をつけて起動するコマンド。
この場合は、centosというイメージからdockerを実行せよ。
:latestは、タグで、ここでcentosのバージョンとかを指定できる。
-iは--interactive, Keep STDIN open even if not attached
インタラクティブなシェルとかで使えますと。
-tは--tty, Allocate a pseudo-TTY
pseudo-tty( text-terminal ) = 疑似端末 pseudo = 擬似 tty = 標準入出力となっている端末デバイス(制御端末、controlling terminal)の名前を表示するunixコマンドである。元来ttyとはteletypewriter(テレタイプライター)のことを指す。
`pseudo-tty` とは? - kz-engineer -SCRAP-
だいたいココらへんは$docker run --help
でオプションの意味が出てくるので、都度調べる。-it
は頻出。
bashはシェルの指定。
本来、bash以外のシェルでもログインできるか試してみたいところだが、あいにくbashとzshしか知らない。。。
zshはなんかinstallしなきゃいけない感じだった気がするので、こうするとbash以外に試せるものがない。(調べたら出てきたけど、サクッとはできなさそうで、そっちで詰まるのは本筋ではないので、割愛.)
一応zshでも試みてみるも、
$ docker run -it centos:latest zsh docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"zsh\": executable file not found in $PATH": unknown.
そらそうやな。 ほかのとりあえず名前知っているシェルを手当たり次第試してみる。
$ docker run -it centos:latest tcsh docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"tcsh\": executable file not found in $PATH": unknown. ERRO[0000] error waiting for container: context canceled
だめ。
$ docker run -it centos:latest tcsh docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"tcsh\": executable file not found in $PATH": unknown. ERRO[0000] error waiting for container: context canceled
だめ。
$ docker run -it centos:latest sh sh-4.2# sh-4.2# echo $SHELL /bin/bash
やった!入れた!と思ってシェルを確認してみると、、、
これただのbashやんけ!!
と、そもそものbashとshの違いを調べてみると、
shはbashにシンボリックリンクを貼っているとのこと。
$ docker run -it centos:latest bash # ll /bin/ | grep sh -rwxr-xr-x 1 root root 964544 Apr 11 00:53 bash lrwxrwxrwx 1 root root 10 May 31 18:01 bashbug -> bashbug-64 -rwxr-xr-x 1 root root 6964 Apr 11 00:52 bashbug-64 -rws--x--x 1 root root 23960 Apr 11 06:50 chsh -rwxr-xr-x 1 root root 15864 Apr 12 18:44 lchsh lrwxrwxrwx 1 root root 19 May 31 18:02 setup-nsssysinit -> setup-nsssysinit.sh -rwxr-xr-x 1 root root 1539 May 16 15:20 setup-nsssysinit.sh lrwxrwxrwx 1 root root 4 May 31 18:01 sh -> bash -rwxr-xr-x 1 root root 37448 Apr 11 04:35 sha1sum -rwxr-xr-x 1 root root 41600 Apr 11 04:35 sha224sum -rwxr-xr-x 1 root root 41600 Apr 11 04:35 sha256sum -rwxr-xr-x 1 root root 41592 Apr 11 04:35 sha384sum -rwxr-xr-x 1 root root 41592 Apr 11 04:35 sha512sum -rwxr-xr-x 1 root root 10371 Apr 11 04:16 show-changed-rco -rwxr-xr-x 1 root root 16572 Apr 11 04:16 show-installed -rwxr-xr-x 1 root root 54216 Apr 11 04:35 shred -rwxr-xr-x 1 root root 50320 Apr 11 04:35 shuf -rwxr-xr-x 1 root root 15904 Apr 11 06:50 unshare
確かにシンボリックってる。
それはさておき、基本的にはbashとshはおんなじような動きをするらしい。
posixうんたらが違いだとか。
/bin/sh と /bin/bash の違い - 双六工場日誌
脱線したが、戻します。 dockerのcentosにログイン後、centosで有ることを確認しておきます。
[root@75ff963973c6 /]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core)
いいですね。
コンテナを確認
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES de5bdc2e3f95 centos:latest "bash" 6 minutes ago Up 6 minutes vigilant_hoover
$ d ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES de5bdc2e3f95 centos:latest "bash" 7 minutes ago Up 7 minutes vigilant_hoover 67c4c9131574 hogee_web "bundle exec rails s…" 8 days ago Exited (1) 8 days ago hogee_web_run_6 . . .
STATUSがUPになっておりまする。
dockerのimageはどこに保存されるのか
調べてみると、docker for macのアプリののPreferenceから確認できると。
ネットにあった情報と微妙に違うが頑張って探した。
$ cd /Users/yoshii/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/ $ ls -lh total 13994832 -rw-r--r--@ 1 yoshii staff 64G 7 4 01:50 Docker.raw -rw-r--r-- 1 yoshii staff 64K 7 3 21:53 console-ring -rw-r--r-- 1 yoshii staff 64K 7 3 21:53 database.iso -rw-r--r-- 1 yoshii staff 3.3K 7 3 21:53 hyperkit.json -rw-r--r-- 1 yoshii staff 3B 7 3 21:53 hyperkit.pid -rw-r--r-- 1 yoshii staff 0B 2 9 18:10 lock drwxr-xr-x 2 yoshii staff 64B 2 9 18:10 log -rw-r--r-- 1 yoshii staff 36B 2 9 18:10 nic1.uuid -rw-r--r-- 1 yoshii staff 2B 7 3 21:53 pid -rw-r--r-- 1 yoshii staff 2.2K 7 3 21:53 syslog lrwxr-xr-x 1 yoshii staff 12B 7 3 21:53 tty -> /dev/ttys000
おいおい知らぬ間に64Gも使ってたのかよコイツ。。。
なんの情報を持ってんだが。。。
と思って、よくよく先程のDocker for Macの情報を見てみると、
Virtual disk image size: 64.0 GB (allocated: 7.2GB)
とな。
多分自分で最初の設定のときにこのくらいいったれーって64GBを用意しておいたけど、実際には7.2GBしか使われていないってことな気がしてきた。
確認します。
ll total 13994832 -rw-r--r--@ 1 yoshii staff 68719476736 7 4 01:50 Docker.raw -rw-r--r-- 1 yoshii staff 65536 7 3 21:53 console-ring -rw-r--r-- 1 yoshii staff 65536 7 3 21:53 database.iso -rw-r--r-- 1 yoshii staff 3419 7 3 21:53 hyperkit.json -rw-r--r-- 1 yoshii staff 3 7 3 21:53 hyperkit.pid -rw-r--r-- 1 yoshii staff 0 2 9 18:10 lock drwxr-xr-x 2 yoshii staff 64 2 9 18:10 log -rw-r--r-- 1 yoshii staff 36 2 9 18:10 nic1.uuid -rw-r--r-- 1 yoshii staff 2 7 3 21:53 pid -rw-r--r-- 1 yoshii staff 2256 7 3 21:53 syslog lrwxr-xr-x 1 yoshii staff 12 7 3 21:53 tty -> /dev/ttys000
68,719,476,736B
おっとそもそも64GBじゃないんかーいと思ったが、これは
>>> 64 * 1024 * 1024 * 1024 68719476736
でしたな。失敬。
$ docker run -it ubuntu:latest bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 6b98dfc16071: Pull complete 4001a1209541: Pull complete 6319fc68c576: Pull complete b24603670dc3: Pull complete 97f170c87c6f: Pull complete Digest: sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d Status: Downloaded newer image for ubuntu:latest root@f0e98565c20b:/# root@f0e98565c20b:/# cat /etc/os-release NAME="Ubuntu" VERSION="18.04 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic
はい、ubuntuが入りました。
docker images REPOSITORY TAG IMAGE ID CREATED SIZE hogee_web latest 13cc204c812e 9 days ago 881MB ubuntu latest 113a43faa138 3 weeks ago 81.2MB centos latest 49f7960eb7e4 4 weeks ago 200MB ruby 2.3.6 7ab6e81790b8 3 months ago 722MB myapp_web latest 1b99a62bd7f1 3 months ago 841MB <none> <none> 33139e4ab9e2 3 months ago 808MB mysql 5.7 5d4d51c57ea8 4 months ago 374MB ruby 2.3.0 7ca70eb2dfea 2 years ago 725MB
docker images
によると、ubuntuは81.2MBらしいです。
先程のファイルサイズが増加しているか確認しようと思ったけど、
これは$ ls -l /Users/yoshii/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/
する必要もないですね。
そもそも/Users/yoshii/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.raw
のファイル容量がきりの良すぎる数字の時点でおかしかった。
これはDocker.rawファイルに全部情報がいい感じに入っているようだ。僕らからは確認のしようがない。
linuxだと、/var/lib/docker配下にできるらしいな。 一応やってみます。
linuxでdockerを起動
こういうときはEC2のt2.microインスタンスをサクッと立ち上げる。富豪だ。 ネットワークもこっちのほうが早いから、すぐAWS使っちゃう。富豪だ。
sudo su - yum install -y docker service docker start docker run -it centos:latest bash
とりあえずサクッとcentosがinstallできた。 一旦exitして、実態の在り処を探す。
# cd /var/lib/docker ll total 48 drwx------ 2 root root 4096 Jul 3 17:36 builder drwx--x--x 3 root root 4096 Jul 3 17:36 containerd drwx------ 3 root root 4096 Jul 3 17:36 containers drwx------ 3 root root 4096 Jul 3 17:36 image drwxr-x--- 3 root root 4096 Jul 3 17:36 network drwx------ 6 root root 4096 Jul 3 17:36 overlay2 drwx------ 4 root root 4096 Jul 3 17:36 plugins drwx------ 2 root root 4096 Jul 3 17:36 runtimes drwx------ 2 root root 4096 Jul 3 17:36 swarm drwx------ 2 root root 4096 Jul 3 17:36 tmp drwx------ 2 root root 4096 Jul 3 17:36 trust drwx------ 2 root root 4096 Jul 3 17:36 volumes # # # du -sh ./* 20K ./builder 116K ./containerd 48K ./containers 736K ./image 52K ./network 211M ./overlay2 20K ./plugins 4.0K ./runtimes 4.0K ./swarm 4.0K ./tmp 4.0K ./trust 28K ./volumes
怪しいのはoverlay2
ですね。
# ll overlay2/ total 16 drwx------ 4 root root 4096 Jul 3 17:38 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8 drwx------ 4 root root 4096 Jul 3 17:36 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8-init drwx------ 3 root root 4096 Jul 3 17:36 eb694a3cfd827ea603a03347037a4f50e537151e18aff3c8ab80482d274ebca0 drwx------ 2 root root 4096 Jul 3 17:36 l # # [root@ip-172-31-44-122 docker]# du -sh overlay2/* 32K overlay2/2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8 40K overlay2/2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8-init 211M overlay2/eb694a3cfd827ea603a03347037a4f50e537151e18aff3c8ab80482d274ebca0 16K overlay2/l
find /var/lib/docker/overlay2/. |wc 10705 10705 1268385
どうやらたくさんのファイル群が入っているようだ。
あまり踏み込まないようにしよう。
docker run -it ubuntu:latest bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 6b98dfc16071: Pull complete 4001a1209541: Pull complete 6319fc68c576: Pull complete b24603670dc3: Pull complete 97f170c87c6f: Pull complete Digest: sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d Status: Downloaded newer image for ubuntu:latest root@67da5f8e8b2c:/#
容量を確認する。
[root@ip-172-31-44-122 docker]# du -sh ./* 20K ./builder 116K ./containerd 92K ./containers 1.1M ./image 52K ./network 297M ./overlay2 20K ./plugins 4.0K ./runtimes 4.0K ./swarm 4.0K ./tmp 4.0K ./trust 28K ./volumes
詳細も確認する。
[root@ip-172-31-44-122 overlay2]# ll /var/lib/docker/overlay2 total 44 drwx------ 3 root root 4096 Jul 3 17:45 06142c28a84ecc2757409fad89ad24c6e8d5e0f787937245c0c7ef34a8157db1 drwx------ 4 root root 4096 Jul 3 17:45 17cd521c63cb54de57f1918706cb950920b2664c0b93390f3c8ef60d55cef536 drwx------ 4 root root 4096 Jul 3 17:45 2d85d5672c17ce9441e77328e210bdcaad440cfadbbce39f32a46870aa51c6fc drwx------ 4 root root 4096 Jul 3 17:45 2d85d5672c17ce9441e77328e210bdcaad440cfadbbce39f32a46870aa51c6fc-init drwx------ 4 root root 4096 Jul 3 17:38 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8 drwx------ 4 root root 4096 Jul 3 17:36 2f040d74f70b4d498e644d0c04af7ee9a137881795cc377912871588dd4142b8-init drwx------ 4 root root 4096 Jul 3 17:45 4c69f1a2eea8fba87437187838a942c75fea48b50744718288c6f7e72529827e drwx------ 4 root root 4096 Jul 3 17:45 5e834467fed8e25a3ee8ce8c39b565171dd97bf7d4de5d9d86772144e18cea22 drwx------ 3 root root 4096 Jul 3 17:36 eb694a3cfd827ea603a03347037a4f50e537151e18aff3c8ab80482d274ebca0 drwx------ 4 root root 4096 Jul 3 17:45 f42660ffc61032bf307beaab41da57fe93acff57a59f8cc028a6baf1866c8b06 drwx------ 2 root root 4096 Jul 3 17:45 l
ubuntuのものと思われるものが増えている。
なるほど。
なるほど。
Dockerのファイル保存場所とか整理できたので、一旦ここまでにする。
顔認識・画像認識ライブラリAPIを使ってみる
経緯
個人的な趣味で、顔認識のAPIを使いたいなと思い立ちました。
OpenCVで自前でできないかなーとも思ったのですが、なかなか難しいっぽく、諦めてAPIを使うという選択になった次第です。
どこがAPIを提供しているのか、以下のリンクがとても参考になりました。
qiita.com
このリンクをもとに、それぞれ調べてみました。
調べる際の観点は「口の開閉が判別できるか」「その精度は確かか」という2点で調査をしました。
なお、調査したのは2017年の年末頃なので、この記事を書いている時点ですでに仕様が更新されている可能性があります。
目次
detectFace()
detectFace(); - 顔検出Webサービス → 2018.04.04にサービス提供終了になりました。
何より簡単に使えて、しかも無料というのがいい。
こんな感じのページ。
このAPIでは50個の特徴量を返してくれるみたいだ。
その結果、笑ってるか怒ってるかとかを判定したいと思ったらこっちで別途実装しなきゃいけないみたいだ。
それはそれで問題はないけども。
丁寧にサンプルページがあるので、実装する前に雰囲気確かめられる。
早速でもページから画像を投げてみる。
帰ってくるxmlはこんな感じ
<?xml version='1.0'?> <faces> <face id='0'> <bounds x='328' y='550' width='100' height='100'/> <right-eye x='365' y='591'/> </face> <face id='1'> <bounds x='292' y='549' width='106' height='106'/> </face> <face id='2'> <bounds x='706' y='256' width='709' height='709'/> <right-eye x='907' y='538'/> <left-eye x='1196' y='527'/> <features s-avg='0.83' s-min='0.66' s-max='0.95'> <point id='PR' x='921' y='536' s='0.80'/> <point id='PL' x='1192' y='525' s='0.94'/> <point id='BR2' x='1004' y='447' s='0.67'/> <point id='BL2' x='1111' y='431' s='0.82'/> <point id='N1' x='1057' y='531' s='0.78'/> <point id='N5' x='1064' y='689' s='0.86'/> <point id='M1' x='1068' y='796' s='0.85'/> <point id='M5' x='1071' y='895' s='0.74'/> <point id='M3' x='965' y='831' s='0.74'/> <point id='M7' x='1176' y='830' s='0.82'/> <point id='EL4' x='1257' y='534' s='0.93'/> <point id='EL1' x='1139' y='545' s='0.92'/> <point id='ER1' x='969' y='556' s='0.82'/> <point id='ER4' x='871' y='538' s='0.84'/> <point id='M2' x='1011' y='797' s='0.87'/> <point id='M8' x='1109' y='793' s='0.91'/> <point id='M4' x='1014' y='882' s='0.71'/> <point id='M6' x='1145' y='872' s='0.75'/> <point id='BR3' x='948' y='428' s='0.70'/> <point id='BL3' x='1187' y='409' s='0.84'/> <point id='N2' x='965' y='706' s='0.87'/> <point id='N4' x='1149' y='703' s='0.92'/> <point id='F1' x='1043' y='196' s='0.70'/> <point id='F2' x='842' y='260' s='0.89'/> <point id='F10' x='1252' y='286' s='0.69'/> <point id='ER2' x='949' y='525' s='0.82'/> <point id='ER3' x='891' y='522' s='0.81'/> <point id='ER5' x='897' y='560' s='0.83'/> <point id='ER6' x='936' y='567' s='0.81'/> <point id='EL2' x='1164' y='508' s='0.92'/> <point id='EL3' x='1231' y='508' s='0.93'/> <point id='EL5' x='1229' y='547' s='0.94'/> <point id='EL6' x='1170' y='551' s='0.92'/> <point id='BR1' x='994' y='489' s='0.72'/> <point id='BR4' x='890' y='436' s='0.79'/> <point id='BR5' x='841' y='492' s='0.82'/> <point id='BR6' x='919' y='449' s='0.77'/> <point id='BL1' x='1111' y='468' s='0.81'/> <point id='BL4' x='1240' y='414' s='0.83'/> <point id='BL5' x='1298' y='450' s='0.90'/> <point id='BL6' x='1226' y='437' s='0.85'/> <point id='N3' x='1068' y='737' s='0.92'/> <point id='M9' x='1068' y='834' s='0.83'/> <point id='F3' x='779' y='558' s='0.87'/> <point id='F9' x='1346' y='535' s='0.95'/> <point id='F4' x='817' y='741' s='0.81'/> <point id='F8' x='1323' y='720' s='0.90'/> <point id='F6' x='1041' y='1041' s='0.66'/> <point id='F5' x='849' y='880' s='0.78'/> <point id='F7' x='1307' y='879' s='0.83'/> </features> </face> </faces>
自分の使いたい用途に合わせるとしたら、どこか口以外の基準点から標準化するポイントを複数見つけておいて、画像のサイズを標準化してから、口の周りの特徴点の距離を調べるという方法だろうか。
AWS Rekognito
画像認識に関連したいろいろなサービスがある感じ。
これが人工知能というくくりのサービスなのはちょっと変な感じするが。
デモページもあった。
ただ、これを見ると、でもページの画像でさえ口開いてるのに閉じてるって判定されてたので(しかも信頼度72%)、精度はそこまで良くないのかもしれない。
試しに自分で取った写真をアップロードしてみても、ちょくちょく誤判定されてしまっている。
JSONも返してくれるが、特徴量が少ない気がする。特に口周り。
{ "FaceDetails": [ { "BoundingBox": { "Width": 0.2618750035762787, "Height": 0.3930581510066986, "Left": 0.14937500655651093, "Top": 0.13414634764194489 }, "AgeRange": { "Low": 26, "High": 43 }, "Smile": { "Value": true, "Confidence": 99.0987319946289 }, "Eyeglasses": { "Value": true, "Confidence": 99.99999237060547 }, "Sunglasses": { "Value": true, "Confidence": 95.7325439453125 }, "Gender": { "Value": "Female", "Confidence": 100 }, "Beard": { "Value": false, "Confidence": 99.90460205078125 }, "Mustache": { "Value": false, "Confidence": 92.63785552978516 }, "EyesOpen": { "Value": true, "Confidence": 85.72091674804688 }, "MouthOpen": { "Value": false, "Confidence": 72.2423095703125 }, "Emotions": [ { "Type": "HAPPY", "Confidence": 99.69184112548828 }, { "Type": "CALM", "Confidence": 1.0618865489959717 }, { "Type": "ANGRY", "Confidence": 0.44966936111450195 } ], "Landmarks": [ { "Type": "eyeLeft", "X": 0.2459116131067276, "Y": 0.2963947355747223 }, { "Type": "eyeRight", "X": 0.32498690485954285, "Y": 0.28320804238319397 }, { "Type": "nose", "X": 0.2972606122493744, "Y": 0.338502436876297 }, { "Type": "mouthLeft", "X": 0.24349376559257507, "Y": 0.43834927678108215 }, { "Type": "mouthRight", "X": 0.32762306928634644, "Y": 0.4251043200492859 }, { "Type": "leftPupil", "X": 0.2514769434928894, "Y": 0.2966562509536743 }, { "Type": "rightPupil", "X": 0.3339778780937195, "Y": 0.28375154733657837 }, { "Type": "leftEyeBrowLeft", "X": 0.2145199030637741, "Y": 0.2463952600955963 }, { "Type": "leftEyeBrowUp", "X": 0.2372017651796341, "Y": 0.22838076949119568 }, { "Type": "leftEyeBrowRight", "X": 0.26641586422920227, "Y": 0.2389357089996338 }, { "Type": "rightEyeBrowLeft", "X": 0.2992517352104187, "Y": 0.23876728117465973 }, { "Type": "rightEyeBrowUp", "X": 0.3239707052707672, "Y": 0.22483669221401215 }, { "Type": "rightEyeBrowRight", "X": 0.3491824269294739, "Y": 0.23105363547801971 }, { "Type": "leftEyeLeft", "X": 0.22946621477603912, "Y": 0.3019390404224396 }, { "Type": "leftEyeRight", "X": 0.2616650462150574, "Y": 0.29498758912086487 }, { "Type": "leftEyeUp", "X": 0.24515913426876068, "Y": 0.2873624563217163 }, { "Type": "leftEyeDown", "X": 0.24701011180877686, "Y": 0.3033584654331207 }, { "Type": "rightEyeLeft", "X": 0.30755841732025146, "Y": 0.2871529757976532 }, { "Type": "rightEyeRight", "X": 0.3417114317417145, "Y": 0.2818377912044525 }, { "Type": "rightEyeUp", "X": 0.3245350122451782, "Y": 0.27441540360450745 }, { "Type": "rightEyeDown", "X": 0.3257909119129181, "Y": 0.2907133102416992 }, { "Type": "noseLeft", "X": 0.2787094712257385, "Y": 0.38133224844932556 }, { "Type": "noseRight", "X": 0.308928519487381, "Y": 0.37534570693969727 }, { "Type": "mouthUp", "X": 0.2901398241519928, "Y": 0.4091244339942932 }, { "Type": "mouthDown", "X": 0.29399216175079346, "Y": 0.4643388092517853 } ], "Pose": { "Roll": -8.045867919921875, "Yaw": 17.916576385498047, "Pitch": 13.156755447387695 }, "Quality": { "Brightness": 40.16537857055664, "Sharpness": 99.9980239868164 }, "Confidence": 99.82343292236328 } ] }
Google Cloud Vision
顔認識もできるし、いろいろな情報も併せてくれる。
こんな感じでJSONを返してくれたりもするみたいだ。
dandaIMGL5048_TP_V.jpg { "faceAnnotations": [ { "boundingPoly": { "vertices": [ { "x": 583, "y": 49 }, { "x": 1029, "y": 49 }, { "x": 1029, "y": 567 }, { "x": 583, "y": 567 } ] }, "fdBoundingPoly": { "vertices": [ { "x": 649, "y": 161 }, { "x": 975, "y": 161 }, { "x": 975, "y": 487 }, { "x": 649, "y": 487 } ] }, "landmarks": [ { "type": "LEFT_EYE", "position": { "x": 719.5262, "y": 253.72295, "z": 0.000031506435 } }, { "type": "RIGHT_EYE", "position": { "x": 849.03937, "y": 267.75647, "z": -36.321598 } }, { "type": "LEFT_OF_LEFT_EYEBROW", "position": { "x": 686.8778, "y": 221.65237, "z": 27.712809 } }, { "type": "RIGHT_OF_LEFT_EYEBROW", "position": { "x": 754.03094, "y": 225.36835, "z": -30.87542 } }, { "type": "LEFT_OF_RIGHT_EYEBROW", "position": { "x": 816.0152, "y": 231.25845, "z": -48.467567 } }, { "type": "RIGHT_OF_RIGHT_EYEBROW", "position": { "x": 902.0392, "y": 247.53323, "z": -33.053932 } }, { "type": "MIDPOINT_BETWEEN_EYES", "position": { "x": 780.317, "y": 254.34734, "z": -45.376167 } }, { "type": "NOSE_TIP", "position": { "x": 760.82666, "y": 325.50342, "z": -93.64924 } }, { "type": "UPPER_LIP", "position": { "x": 758.0111, "y": 379.47757, "z": -70.47521 } }, { "type": "LOWER_LIP", "position": { "x": 750.76904, "y": 417.12973, "z": -67.296265 } }, { "type": "MOUTH_LEFT", "position": { "x": 709.657, "y": 398.31903, "z": -23.435837 } }, { "type": "MOUTH_RIGHT", "position": { "x": 818.4644, "y": 414.50378, "z": -52.383175 } }, { "type": "MOUTH_CENTER", "position": { "x": 756.7259, "y": 397.63107, "z": -63.729393 } }, { "type": "NOSE_BOTTOM_RIGHT", "position": { "x": 805.1426, "y": 346.799, "z": -55.52658 } }, { "type": "NOSE_BOTTOM_LEFT", "position": { "x": 733.29297, "y": 340.7746, "z": -35.59469 } }, { "type": "NOSE_BOTTOM_CENTER", "position": { "x": 763.22516, "y": 350.47217, "z": -67.04768 } }, { "type": "LEFT_EYE_TOP_BOUNDARY", "position": { "x": 721.1245, "y": 244.94334, "z": -8.15667 } }, { "type": "LEFT_EYE_RIGHT_CORNER", "position": { "x": 747.6377, "y": 259.79794, "z": -7.71021 } }, { "type": "LEFT_EYE_BOTTOM_BOUNDARY", "position": { "x": 718.8527, "y": 263.2192, "z": -2.942062 } }, { "type": "LEFT_EYE_LEFT_CORNER", "position": { "x": 699.73505, "y": 254.79391, "z": 18.149515 } }, { "type": "LEFT_EYE_PUPIL", "position": { "x": 719.1574, "y": 254.79868, "z": -3.427162 } }, { "type": "RIGHT_EYE_TOP_BOUNDARY", "position": { "x": 849.89404, "y": 260.44193, "z": -44.4995 } }, { "type": "RIGHT_EYE_RIGHT_CORNER", "position": { "x": 883.94965, "y": 276.54755, "z": -32.000946 } }, { "type": "RIGHT_EYE_BOTTOM_BOUNDARY", "position": { "x": 849.20135, "y": 277.8363, "z": -39.35131 } }, { "type": "RIGHT_EYE_LEFT_CORNER", "position": { "x": 823.90643, "y": 269.1588, "z": -29.444563 } }, { "type": "RIGHT_EYE_PUPIL", "position": { "x": 851.3009, "y": 270.67163, "z": -40.867477 } }, { "type": "LEFT_EYEBROW_UPPER_MIDPOINT", "position": { "x": 720.2593, "y": 205.07675, "z": -7.762258 } }, { "type": "RIGHT_EYEBROW_UPPER_MIDPOINT", "position": { "x": 859.88257, "y": 221.919, "z": -47.19713 } }, { "type": "LEFT_EAR_TRAGION", "position": { "x": 670.9434, "y": 339.6975, "z": 171.91898 } }, { "type": "RIGHT_EAR_TRAGION", "position": { "x": 963.58167, "y": 374.80206, "z": 89.242195 } }, { "type": "FOREHEAD_GLABELLA", "position": { "x": 783.9658, "y": 224.99454, "z": -44.277527 } }, { "type": "CHIN_GNATHION", "position": { "x": 748.6527, "y": 486.20532, "z": -57.60541 } }, { "type": "CHIN_LEFT_GONION", "position": { "x": 658.22473, "y": 415.8591, "z": 103.73505 } }, { "type": "CHIN_RIGHT_GONION", "position": { "x": 922.45325, "y": 447.898, "z": 29.103714 } } ], "rollAngle": 9.122541, "panAngle": -15.808093, "tiltAngle": 7.937726, "detectionConfidence": 0.8733366, "landmarkingConfidence": 0.5498749, "joyLikelihood": "VERY_UNLIKELY", "sorrowLikelihood": "VERY_UNLIKELY", "angerLikelihood": "VERY_UNLIKELY", "surpriseLikelihood": "VERY_UNLIKELY", "underExposedLikelihood": "VERY_UNLIKELY", "blurredLikelihood": "VERY_UNLIKELY", "headwearLikelihood": "VERY_UNLIKELY" } ], "labelAnnotations": [ { "mid": "/m/01xyhv", "description": "suit", "score": 0.855479 }, { "mid": "/m/01qkbx", "description": "professional", "score": 0.84650135 }, { "mid": "/m/0n5szg6", "description": "business executive", "score": 0.81015223 }, { "mid": "/m/012t_z", "description": "businessperson", "score": 0.7535335 }, { "mid": "/m/01kq3x", "description": "white collar worker", "score": 0.67039 }, { "mid": "/m/0289fz", "description": "executive officer", "score": 0.6614492 }, { "mid": "/m/09x_r", "description": "entrepreneur", "score": 0.638664 }, { "mid": "/m/09s1f", "description": "business", "score": 0.5803496 }, { "mid": "/m/05s9tm", "description": "talent manager", "score": 0.51430815 }, { "mid": "/m/019p5q", "description": "gentleman", "score": 0.50934017 } ], "safeSearchAnnotation": { "adult": "VERY_UNLIKELY", "spoof": "VERY_UNLIKELY", "medical": "VERY_UNLIKELY", "violence": "VERY_UNLIKELY" }, "imagePropertiesAnnotation": { "dominantColors": { "colors": [ { "color": { "red": 230, "green": 230, "blue": 237 }, "score": 0.5333071, "pixelFraction": 0.5140667 }, { "color": { "red": 26, "green": 26, "blue": 28 }, "score": 0.21879509, "pixelFraction": 0.26526666 }, { "color": { "red": 205, "green": 203, "blue": 213 }, "score": 0.1390053, "pixelFraction": 0.11186667 }, { "color": { "red": 45, "green": 43, "blue": 47 }, "score": 0.029963521, "pixelFraction": 0.0282 }, { "color": { "red": 225, "green": 190, "blue": 189 }, "score": 0.01627379, "pixelFraction": 0.012933333 }, { "color": { "red": 164, "green": 159, "blue": 165 }, "score": 0.014597795, "pixelFraction": 0.0132 }, { "color": { "red": 226, "green": 183, "blue": 174 }, "score": 0.007874987, "pixelFraction": 0.014666666 }, { "color": { "red": 123, "green": 118, "blue": 121 }, "score": 0.0060422462, "pixelFraction": 0.0058 }, { "color": { "red": 246, "green": 214, "blue": 214 }, "score": 0.005779971, "pixelFraction": 0.0045333332 }, { "color": { "red": 86, "green": 83, "blue": 85 }, "score": 0.005673399, "pixelFraction": 0.0055333334 } ] } }, "cropHintsAnnotation": { "cropHints": [ { "boundingPoly": { "vertices": [ { "x": 351 }, { "x": 1215 }, { "x": 1215, "y": 1065 }, { "x": 351, "y": 1065 } ] }, "confidence": 1, "importanceFraction": 0.95 }, { "boundingPoly": { "vertices": [ { "x": 255 }, { "x": 1343 }, { "x": 1343, "y": 1065 }, { "x": 255, "y": 1065 } ] }, "confidence": 1, "importanceFraction": 0.98999995 }, { "boundingPoly": { "vertices": [ { "x": 127 }, { "x": 1423 }, { "x": 1423, "y": 1065 }, { "x": 127, "y": 1065 } ] }, "confidence": 1, "importanceFraction": 0.98999995 } ] }, "webDetection": { "webEntities": [ { "entityId": "/g/12bprj7th", "score": 1.1322 }, { "entityId": "/g/1tgwpw14", "score": 1.0244 }, { "entityId": "/m/09s1f", "score": 0.7011, "description": "Business" }, { "entityId": "/m/01m2bt", "score": 0.6569, "description": "Venture capital" }, { "entityId": "/m/03bxgrp", "score": 0.6054, "description": "Company" }, { "entityId": "/m/018s5w", "score": 0.5943, "description": "Capital" }, { "entityId": "/m/02_7t", "score": 0.5428, "description": "Finance" }, { "entityId": "/g/1203l_bt9", "score": 0.5194 }, { "entityId": "/m/02nwq", "score": 0.5148, "description": "Entrepreneurship" }, { "entityId": "/m/03m3ym9", "score": 0.5101, "description": "Angel investor" }, { "entityId": "/m/023k2", "score": 0.4496, "description": "Corporation" }, { "entityId": "/m/03jzl9", "score": 0.4287, "description": "Share" }, { "entityId": "/m/0dlrgy", "score": 0.4206, "description": "All China Lawyers Association" }, { "entityId": "/m/0h3z7", "score": 0.4193, "description": "Initial public offering" }, { "entityId": "/m/0lbmv", "score": 0.20668001, "description": "Shenzhen" } ], "fullMatchingImages": [ { "url": "http://timscholten.com/wp-content/uploads/2017/05/TimScholten-5.jpg" }, { "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048.jpg" }, { "url": "http://stat.profile.ameba.jp/profile_images/20130317/19/1e/9f/j/o180025201363516460689.jpg" }, { "url": "http://www.gdszlvshi.com/UploadFiles/201662015263786.jpg" }, { "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V.jpg" }, { "url": "https://image.jimcdn.com/app/cms/image/transf/none/path/s4684c34cfcc8a80d/image/i400bb0104d9c3726/version/1454214695/image.jpg" }, { "url": "http://felvidek.ma/wp-content/uploads/2017/10/Becse-Norbert-fot%C3%B3-MKP.jpg" }, { "url": "http://www.wscom.com.br/arqs/arquivos/arquivos/201303081003240000008330.jpg" }, { "url": "http://upload-images.jianshu.io/upload_images/2548788-875d2c3997d23c47.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" }, { "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V4.jpg" }, { "url": "http://www.prachachat.net/online/2016/10/14775681281477568341l.jpg" }, { "url": "http://www.yemacaijing.com/kindeditor/attached/image/20160212/20160212155959_73709.jpg" }, { "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V1.jpg" }, { "url": "http://stat.profile.ameba.jp/profile_images/20160930/02/f3/Ot/j/o045204531475169331139.jpg" }, { "url": "https://image.jimcdn.com/app/cms/image/transf/dimension=210x1024:format=jpg/path/s4684c34cfcc8a80d/image/i400bb0104d9c3726/version/1454214695/image.jpg" }, { "url": "http://happymakeproject.com/wp-content/uploads/2016/02/image.jpg" }, { "url": "https://media.licdn.com/mpr/mpr/shrinknp_200_200/AAEAAQAAAAAAAATyAAAAJDEyMmM3MWY3LTQwZmYtNGI1Yi05ZjA1LTIxNzJhOGM1NjU4Ng.jpg" }, { "url": "https://media.licdn.com/mpr/mpr/shrinknp_200_200/AAEAAQAAAAAAAA3VAAAAJGY0ZThlMDg3LTdlMTYtNDAxMC05NzZhLTJiYWRmNjA5OTM0Yw.jpg" }, { "url": "http://img2.cyzone.cn/uploadfile/2015/0710/fa4005041f412e73d576693097584382.jpg" }, { "url": "https://media.licdn.com/mpr/mpr/shrink_100_100/AAEAAQAAAAAAAAxMAAAAJDliODg1ZWJmLTM5OTctNDk1Yi1iMTg0LTc5MWQ4YjAxZGQ3Yw.jpg" } ], "partialMatchingImages": [ { "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V2.jpg" } ], "pagesWithMatchingImages": [ { "url": "https://www.linkedin.com/in/andrewbwillis" }, { "url": "http://timscholten.com/media-sheet/images/" }, { "url": "https://sg.linkedin.com/in/amy-aw-1804433a" }, { "url": "https://holaconnect.com/profile/margaux-lefort-email-phone-93fee623" }, { "url": "http://felvidek.ma/2017/10/becse-norbert-mindent-megtesz-a-komaromi-jaras-fellenditese-erdekeben/" }, { "url": "http://www.yemacaijing.com/index/view/id/84.html" }, { "url": "http://www.yemacaijing.com/Index/view/id/84.html" }, { "url": "http://www.wscom.com.br/noticias/economia/JOAO+PESSOA+GANHA+CONCESSIONARIA+AUDI+-145150" }, { "url": "https://www.pakutaso.com/person/" }, { "url": "http://www.cyzone.cn/d/20150601/470.html" }, { "url": "http://happymakeproject.com/8678/" }, { "url": "https://dandashokai.com/8667" }, { "url": "http://www.cyzone.cn/f/20150710/1664.html" }, { "url": "https://www.prachachat.net/news_detail.php?newsid=1477568128" }, { "url": "https://www.pakutaso.com/person/man/" }, { "url": "http://www.jianshu.com/p/3580429d0a50" }, { "url": "http://m.prachachat.net/news_detail.php?newsid=1477568128" }, { "url": "http://profile.ameba.jp/dramamaster/" }, { "url": "http://profile.ameba.jp/panicgekitai" }, { "url": "http://www.gdszlvshi.com/showteam_1.html" }, { "url": "http://www.gdszlvshi.com/team.html" }, { "url": "https://panickaiketsu.jimdo.com/%E4%B8%89%E6%9C%A8%E3%83%92%E3%83%AD%E3%82%B7%E3%81%AE%E3%83%97%E3%83%AD%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB/" }, { "url": "http://hyperacy.site/%E0%B9%82%E0%B8%A1%E0%B8%A1%E0%B8%B9%E0%B8%AB%E0%B8%B0-%E0%B8%A3%E0%B8%B2%E0%B8%84%E0%B8%B2/" }, { "url": "http://www.10800.com/investor/details/id/14309" }, { "url": "https://holaconnect.com/profile/maman-bureau-email-phone-acf2ca62" } ], "visuallySimilarImages": [ { "url": "http://www.realestateforachangingworld.co.uk/wp-content/uploads/2017/10/andy_martin-500x500.jpg" }, { "url": "https://www.irep.co.jp/global/wp-content/uploads/2016/03/Samuel3-e1485150192823.png" }, { "url": "http://www.bu.ac.th/wp-content/uploads/2016/07/%E0%B8%84%E0%B8%B8%E0%B8%93%E0%B8%A8%E0%B8%B8%E0%B8%A0%E0%B8%8A%E0%B8%B1%E0%B8%A2.jpg" }, { "url": "https://ireward.superghs.com/resource/htoo/page/Mr-Manish-Gupta.jpg" }, { "url": "http://www.recruit-rgf.com/csr/img/csr_common_president01.jpg" }, { "url": "https://www.capitalfirst.com/front-end/img/board-members/bm2.jpg" }, { "url": "https://www.dialog.lk/dialogdocroot/content/images/pr/supun.jpg" }, { "url": "https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=1116763798456374" }, { "url": "https://8iholdings.com/wp-content/uploads/2017/02/Profile-Joshua-440x440.jpg" } ] } }
Microsoft Face API
返ってくるJSONはこんな感じらしい。
Response: [ { "faceAttributes": { "accessories": [], "age": 22.9, "blur": { "blurLevel": "low", "value": 0.06 }, "emotion": { "anger": 0.0, "contempt": 0.0, "disgust": 0.0, "fear": 0.0, "happiness": 0.0, "neutral": 0.986, "sadness": 0.009, "surprise": 0.005 }, "exposure": { "exposureLevel": "goodExposure", "value": 0.67 }, "facialHair": { "beard": 0.0, "moustache": 0.0, "sideburns": 0.0 }, "gender": "female", "glasses": "NoGlasses", "hair": { "bald": 0.0, "hairColor": [ { "color": "brown", "confidence": 1.0 }, { "color": "black", "confidence": 0.87 }, { "color": "other", "confidence": 0.51 }, { "color": "blond", "confidence": 0.08 }, { "color": "red", "confidence": 0.08 }, { "color": "gray", "confidence": 0.02 } ], "invisible": false }, "headPose": { "pitch": 0.0, "roll": 0.1, "yaw": -32.9 }, "makeup": { "eyeMakeup": true, "lipMakeup": true }, "noise": { "noiseLevel": "low", "value": 0.0 }, "occlusion": { "eyeOccluded": false, "foreheadOccluded": false, "mouthOccluded": false }, "smile": 0.0 }, "faceId": "49d55c17-e018-4a42-ba7b-8cbbdfae7c6f", "faceRectangle": { "height": 162, "left": 177, "top": 131, "width": 162 } } ]
あまり詳細な表情の情報は渡してくれないみたいだ。
もうこっちがやっとくから、君たちは結果だけ見てればいいの。感が強い。ヤダ。
Face++
Face Landmark SDK - Face++ Cognitive Services
なんと顔の特徴料が106個あると。
しかも無料だと。
なんやかんやこれが一番良さそうだ。
ただ、使ってみるまでがちょっと面倒くさいです。
まずアカウントを登録して(メールが届くのが5分くらいかかった!)、携帯電話番号とかも登録しなくちゃAPIKeyが取得できません。
これで使い物にならなかったら腹立ちますね。
中国のサービスなので、電話番号とか悪用されないかなと心配してたのですが、この会社割りと有名な会社だったみたいですね。恥ずかしい。
中国の顔認証スタートアップFace++が2500万米ドルを調達、Jack Ma(馬雲)氏も顔認証決済を導入へ - THE BRIDGE(ザ・ブリッジ)
{ "image_id": "5XkklHlGoc1WTKaw25w5ww==", "request_id": "1511624338,e08986b0-891b-46fb-9b1a-2c77a83dccbb", "time_used": 427, "faces": [ { "landmark": { "mouth_upper_lip_left_contour2": { "y": 384, "x": 719 }, "mouth_upper_lip_left_contour3": { "y": 396, "x": 728 }, "mouth_lower_lip_right_contour3": { "y": 415, "x": 772 }, "mouth_upper_lip_left_contour1": { "y": 372, "x": 743 }, "left_eye_upper_left_quarter": { "y": 248, "x": 706 }, "left_eyebrow_lower_middle": { "y": 220, "x": 716 }, "contour_chin": { "y": 511, "x": 750 }, "left_eyebrow_lower_left_quarter": { "y": 219, "x": 699 }, "right_eyebrow_lower_left_quarter": { "y": 235, "x": 832 }, "mouth_lower_lip_right_contour1": { "y": 402, "x": 783 }, "mouth_lower_lip_left_contour2": { "y": 408, "x": 717 }, "left_eye_bottom": { "y": 260, "x": 717 }, "mouth_lower_lip_bottom": { "y": 412, "x": 753 }, "contour_left9": { "y": 497, "x": 717 }, "mouth_lower_lip_top": { "y": 394, "x": 755 }, "right_eyebrow_upper_middle": { "y": 217, "x": 859 }, "right_eyebrow_left_corner": { "y": 232, "x": 806 }, "right_eye_bottom": { "y": 279, "x": 849 }, "contour_left7": { "y": 448, "x": 680 }, "contour_left6": { "y": 416, "x": 673 }, "contour_left5": { "y": 382, "x": 671 }, "contour_left4": { "y": 349, "x": 671 }, "contour_left3": { "y": 317, "x": 672 }, "contour_left2": { "y": 287, "x": 677 }, "contour_left1": { "y": 257, "x": 684 }, "left_eye_lower_left_quarter": { "y": 258, "x": 705 }, "mouth_upper_lip_top": { "y": 376, "x": 757 }, "contour_right3": { "y": 371, "x": 992 }, "contour_right2": { "y": 328, "x": 997 }, "mouth_left_corner": { "y": 405, "x": 701 }, "contour_right4": { "y": 414, "x": 982 }, "contour_right7": { "y": 504, "x": 893 }, "left_eyebrow_left_corner": { "y": 219, "x": 683 }, "nose_right": { "y": 346, "x": 814 }, "right_eye_upper_right_quarter": { "y": 266, "x": 867 }, "nose_tip": { "y": 321, "x": 757 }, "contour_right5": { "y": 453, "x": 963 }, "nose_contour_lower_middle": { "y": 354, "x": 762 }, "right_eye_top": { "y": 261, "x": 850 }, "mouth_lower_lip_left_contour3": { "y": 410, "x": 735 }, "right_eye_right_corner": { "y": 275, "x": 881 }, "right_eye_lower_right_quarter": { "y": 278, "x": 866 }, "mouth_upper_lip_right_contour2": { "y": 393, "x": 794 }, "right_eyebrow_lower_right_quarter": { "y": 239, "x": 883 }, "left_eye_left_corner": { "y": 253, "x": 695 }, "mouth_right_corner": { "y": 417, "x": 811 }, "mouth_upper_lip_right_contour3": { "y": 402, "x": 784 }, "right_eye_lower_left_quarter": { "y": 276, "x": 834 }, "left_eyebrow_right_corner": { "y": 224, "x": 752 }, "left_eyebrow_lower_right_quarter": { "y": 223, "x": 734 }, "right_eye_center": { "y": 272, "x": 850 }, "left_eye_upper_right_quarter": { "y": 251, "x": 731 }, "mouth_lower_lip_left_contour1": { "y": 396, "x": 728 }, "contour_left8": { "y": 475, "x": 695 }, "nose_left": { "y": 336, "x": 726 }, "right_eyebrow_lower_middle": { "y": 236, "x": 858 }, "left_eye_top": { "y": 247, "x": 718 }, "left_eye_center": { "y": 255, "x": 718 }, "left_eye_lower_right_quarter": { "y": 260, "x": 729 }, "nose_contour_right1": { "y": 269, "x": 805 }, "contour_right9": { "y": 516, "x": 798 }, "right_eye_left_corner": { "y": 272, "x": 819 }, "left_eyebrow_upper_left_quarter": { "y": 207, "x": 698 }, "left_eye_pupil": { "y": 253, "x": 718 }, "right_eyebrow_upper_left_quarter": { "y": 219, "x": 831 }, "contour_right8": { "y": 514, "x": 846 }, "right_eyebrow_right_corner": { "y": 243, "x": 906 }, "right_eye_upper_left_quarter": { "y": 264, "x": 833 }, "left_eyebrow_upper_middle": { "y": 206, "x": 718 }, "right_eyebrow_upper_right_quarter": { "y": 223, "x": 886 }, "nose_contour_left1": { "y": 265, "x": 758 }, "nose_contour_left2": { "y": 312, "x": 738 }, "mouth_upper_lip_right_contour1": { "y": 375, "x": 771 }, "contour_right1": { "y": 285, "x": 999 }, "nose_contour_right2": { "y": 320, "x": 806 }, "mouth_lower_lip_right_contour2": { "y": 417, "x": 792 }, "contour_right6": { "y": 484, "x": 933 }, "nose_contour_right3": { "y": 352, "x": 788 }, "nose_contour_left3": { "y": 346, "x": 742 }, "left_eye_right_corner": { "y": 258, "x": 740 }, "left_eyebrow_upper_right_quarter": { "y": 211, "x": 737 }, "right_eye_pupil": { "y": 269, "x": 849 }, "mouth_upper_lip_bottom": { "y": 395, "x": 755 } }, "attributes": { "emotion": { "sadness": 0.044, "neutral": 31.453, "disgust": 21.391, "anger": 47.017, "surprise": 0.032, "fear": 0.032, "happiness": 0.032 }, "gender": { "value": "Male" }, "age": { "value": 37 }, "eyestatus": { "left_eye_status": { "normal_glass_eye_open": 1.785, "no_glass_eye_close": 0, "occlusion": 0.355, "no_glass_eye_open": 97.712, "normal_glass_eye_close": 0.003, "dark_glasses": 0.145 }, "right_eye_status": { "normal_glass_eye_open": 0.004, "no_glass_eye_close": 0, "occlusion": 0.005, "no_glass_eye_open": 99.991, "normal_glass_eye_close": 0, "dark_glasses": 0 } }, "glass": { "value": "None" }, "headpose": { "yaw_angle": 22.35016, "pitch_angle": -8.229241, "roll_angle": 9.0831785 }, "blur": { "blurness": { "threshold": 50, "value": 0.378 }, "motionblur": { "threshold": 50, "value": 0.378 }, "gaussianblur": { "threshold": 50, "value": 0.378 } }, "smile": { "threshold": 30.1, "value": 3.299 }, "facequality": { "threshold": 70.1, "value": 6.392 }, "ethnicity": { "value": "Asian" } }, "face_rectangle": { "width": 316, "top": 201, "left": 657, "height": 316 }, "face_token": "06ae41e4d9d9fe3f614ea82bfbdeb289" } ]
まずはDetect APIで顔を検出してから、その顔のIDを渡すことでAnalyze APIが使えるようです。
IBM
デモページはこんな感じ
Sweetheart(恋人)かどうか判定できるってのはすごいな〜
男女二人組を恋人なのかどうか見分けられるってすごいな〜
でも特徴量とかはくれないみたいだ。
JSONはこんな感じ。
結果とスコアしかくれない。
{ "classes": [ { "class": "person", "score": 0.851 }, { "class": "sweetheart", "score": 0.581, "type_hierarchy": "/person/sweetheart" }, { "class": "people", "score": 0.597, "type_hierarchy": "/person/people" }, { "class": "couple", "score": 0.518, "type_hierarchy": "/person/couple" }, { "class": "woman", "score": 0.556, "type_hierarchy": "/person/female/woman" }, { "class": "female", "score": 0.5 }, { "class": "Indian red color", "score": 0.988 } ], "classifier_id": "default", "name": "default" }
結論
結論は、Face++を使ってみることにしました。
理由としては、特徴量が一番多く、精度も高そうだったためです。
(と、まとめるにあたってもう一度見渡してみたら、無料だしdetectFaceでも良かったのではないかな…と思っています。
と思って改めて見てみたら2018/0404にサービス終了していました。そういえば、これを採用しなかった理由は、提供元があまり聞いたことない会社だったのとで、いつサービス終了するかわからなかったのと、http通信なので、自分の顔写真を送るのには抵抗があったからでした。あの時の自分は正しかった!)
あとGoogle Cloud APIも悪くなかったと思うのですが、若干Face++よりもお高かったので、お見送りになりました。
次回は実際にFace++を用いて自己満サービスを作りたいと思います。
Amazon linuxで/etc/cron.d/配下または/etc/cron.hourly/配下にcronを設定する.
cronの設定方法はいくつかあって、どこにどう設定すればいいのかの、ベストプラクティスはまだよく分かっていません。
いったんpart1として/etc/cron.d/と/etc/cron.hourly/はいかに設定する方法をまとめておきます。
環境
まずはcron化したり作ると作成します。
僕の常套手段として、適当なpythonスクリプトを作ってslackに通知させます。
ログを出力してtail -f でもいいんですが、ログを出力するために画面を2つ出さなきゃいけないのがちょっと面倒くさいので、slackで受動的に確認します。
cron.d配下
cron化させるslack通知pythonスクリプト
import requests import json webhook_url = "https://XXXXXXXXXXXXXX" requests.post(webhook_url, data = json.dumps({ 'text': u'Test', # 投稿するテキスト 'username': u'me', # 投稿のユーザー名 'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字 'link_names': 1, # メンションを有効にする }))
これは/home/ec2-user/slack.py
においておきます。
cronがrootユーザーとして実行させることにするので、実行権限を付与しておきます。
$ sudo chmod 755 /home/ec2-user/slack.py
cron設定
続いて$ sudo vi /etc/cron.d/slack.py
SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root HOME=/ # For details see man 4 crontabs # Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * user-name command to be executed * * * * * root /usr/bin/python36 /home/ec2-user/slack.py
これだでけOKです。
特にcronのrestartとかいらないです。
これで1分ごとにslack通知が流れてきました~
cron.hourly配下
cron化させるslack通知pythonスクリプト
こちらは同じものを使っていきます。
配置場所も同じく/home/ec2-user/slack.py
です。
import requests import json webhook_url = "https://XXXXXXXXXXXXXX" requests.post(webhook_url, data = json.dumps({ 'text': u'Test', # 投稿するテキスト 'username': u'me', # 投稿のユーザー名 'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字 'link_names': 1, # メンションを有効にする }))
cron設定
$ sudo vi /etc/cron.hourly/slack
を作成します。
ファイルの中身は以下です。
#!/bin/sh /usr/bin/python36 /home/ec2-user/slack.py
ここで罠なのですが、このファイル自体にも実行権限を与えないと動かないみたいです。
$ sudo chmod 755 /etc/cron.hourly/slack
hourlyなので、1時間に1回なのですが、確認のために最大1時間も待つなんてとんでもないです。
以下より、一時的に設定を変更します。
$ sudo vi /etc/cron.d/0hourly
SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root HOME=/ 30 * * * * root run-parts /etc/cron.hourly
今回は**:30のときに実行されるように変更しました。
実際通知が流れてきました。
成功です。
python36で別のホストのmysqlに接続する。
環境
接続元
接続先
準備
MySQLサーバ
事前にDBとテーブルを作っておきます。 ユーザー、パスワードとかは特に作成せず、起動したてのままでOKです。 SQLは適当に作成します。
mysql > CREATE DATABASE testdb; mysql > USE testdb; mysql > CREATE TABLE employee(name varchar(20), age int, job varchar(20), salary int); mysql > INSERT INTO employee (name, age, job, salary) VALUES ('tanaka',20,'engineer',400); mysql > GRANT ALL PRIVIEGES ON *.* TO root@aaa.aaa.aaa.aaa;
AWS
セキュリティグループをMySQLのport3306を開けておいてください。
python
sudo pip-3.6 install pymysql
が必要です。
実装コード
import pymysql db = pymysql.connect( host='xxx.xxxx.xxx.xxx', user='root', password='', db='testdb', charset='utf8', cursorclass=pymysql.cursors.DictCursor, ) cur = db.cursor() sql = "select * from employee" cur.execute(sql) employees = cur.fetchall() print(employees)
[{'name': 'tanaka', 'age': 20, 'job': 'engineer', 'salary': 400}]
と、listになって返ってくるので、あとは煮るなり焼くなり好きにしてです。