20211123のPythonに関する記事は30件です。

Django 初期設定

はじめに djangoのプロジェクトを作成するたびにドキュメント読みに行っている気がしたので、 知識の定着も兼ねて記事を書きます。 Pythonのバージョン:3.8.2 djangoのバージョン:3.2.9 gitのバージョン:2.30.2 目次 1.git clone 2.startproject 3.startapp 4.settingsの情報修正 5.シークレット情報の設定 git clone gitを利用していないならスキップ djangoのアプリを作成するディレクトリに移動 gitにてリポジトリを作成してURLをコピー git cloneしていきましょう。 git clone https://github.com/repository.git startproject djangoのアプリを作成するディレクトリに移動してコマンドを実行 django-admin startproject projectname . ディレクトリの状態 . ├── projectname │ ├── init.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py startapp python manage.py startapp appname ディレクトリの状態 . ├── appname │ ├── init.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── init.py │ ├── models.py │ ├── tests.py │ └── views.py ├── projectname │ ├── init.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py settingsの情報修正 作成中 シークレット情報の設定 シークレットの設定は下記を参考に https://qiita.com/Hirakawa123/items/92cdfcf1db6b175b7639 設定は以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1MacのRosettaとARM環境にpyenv + pipenvの環境構築を行う

はじめに M1macパソコンが出始めて1年とちょっと?くらい経ちまして、待望の14inchと8GB以上のメモリが付けられる新型macbookproが発売されました。僕は今までintel製のmacbookを持っていたのですが、電池消費量や性能などの観点から買い替えを決意。一ヶ月経ってパソコンが届いたのですが、待っていたのは周りの人たちがたちが苦しんでいたM1macのpython環境構築でした。 さて、このM1macパソコン。pyenvが対応していなかったことからcondaを選択する人も多かったとお思います。しかし、調べてみるとpyenvって3.9系以上は対応しているのですね。でも3.9系ではなくそれより前のバージョンを使用したい人も多いはずです。機械学習を目的としてる人は特に。 この記事はたくさんあるサイトを確認して、それをまとめたものになります。 注意 ARM環境ではPythonは 3.9系以上 を入れることができます。 Rosetta環境では3.9系未満も入れることができます。 ARM環境で3.9系未満を入れる記事ではありません。ご了承ください この記事は Rosetta環境では3.9系やそれ未満をを、ARM系では3.9系を入れる記事です。 動作環境 & 使用するシステム チップ:M1 Pro メモリ:16GB バージョン:macOS Monterey 12.0.1 使用するシステム パッケージ管理:Homebrew pythonバージョン管理:pyenv ライブラリ管理:pipenv テキストエディタ:好きなものをお使いください。 Homebrewのインストール まずはHomebrewをインストールします。 Homebrewとは、パッケージ管理システムです。インストールからアンインストール・バージョンアップやバージョンダウンまで一元管理してくれるシステムです。 また、RosettaとARMでインストールするとき保存場所が違うのでARM版とRosetta版で二つインストールしてください。 インストールするときは下のコードをコピペしてください。 ARM64(Rosettaを使用していない)にインストール $ uname -m arm64 $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ---- $ which brew opt/homebrew/bin/brew Rosettaにインストール 1. ターミナルを右クリック→ 1. ロゼッタを使用して開くのチェックボックスにチェックを入れる。(初めての時はインストールが始まる)→ 1. ターミナルを開く。  $ uname -m x86_64 $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ---- $ which brew /usr/local/bin/brew PATHを通すため、.zshrcを編集 この時点で、このパソコンにはHomebrewが二つ存在している状態です。 この状態では、PATHが混乱して、ARMなのにRosetta版のHomebrewを用いたり逆が行われたりする可能性があるので しっかりと対策をしていかないといけません。そのために.zshrcを編集します。 以下を張り付けてください if [ "$(uname -m)" = "arm64" ]; then eval "$(/opt/homebrew/bin/brew shellenv)" export PATH="/opt/homebrew/bin:$PATH" else eval "$(/usr/local/bin/brew shellenv)" fi pyenvをインストール 続いてpyenvをインストールします。 $ brew install pyenv こちらもARMとRosetta両方インストールしてください。 PATHを通すため、.zshrcを編集 通常PyenvでPATHを通すときは3行くらい書けば問題ないのですが、Homebrewと同様ARMとRosettaでは保存場所が違うので、条件式を用いてインタラクティブに変更する必要があります。 以下を.zshrcに張り付けてください。 if [ "$(uname -m)" = "arm64" ]; then # arm64 export PYENV_ROOT="$HOME/.pyenv_arm64" export PATH="$HOME/.pyenv_arm64/bin:$PATH" eval "$(pyenv init -)" else # x86_64 export PYENV_ROOT="$HOME/.pyenv_x64" export PATH="$HOME/.pyenv_x64/bin:$PATH" eval "$(pyenv init -)" fi Pythonをインストールする ここまでくればあと少しです。 RosettaもARMも3.9系以上を入れる場合は、 $ pyenv install 3.9.1 この一行で済んでしまいます。(3.9.1の部分を変えればそのバージョンをインストールできます。) しかし、Rosettaで3.8系などをインストールするとき、先ほどのコードを入れると、 BUILD FAILED (OS X 12.0.1 using python-build 20180424) Inspect or clean up the working tree at /var/folders/63/b9w9wr5d1j1_00j80p_047jc0000gn/T/python-build.20211123225241.27371 Results logged to /var/folders/63/b9w9wr5d1j1_00j80p_047jc0000gn/T/python-build.20211123225241.27371.log Last 10 log lines: ret = sendfile(in, out, offset, &sbytes, &sf, flags); ^ ./Modules/posixmodule.c:10432:5: warning: code will never be executed [-Wunreachable-code] Py_FatalError("abort() called from Python code didn't abort!"); このようになってしまいます。そこで色々調べてみると、下のコマンドを発見しました。このコードでインストールすることができました。 3.8.2をインストール $ CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" pyenv install --patch 3.8.2 < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\?full_index\=1) 上記コードに pyenv install --patch 3.8.2 があるのですが、ここの3.8.2を入れたいバージョンに変えてください。 これで python-build: use openssl@1.1 from homebrew python-build: use readline from homebrew Downloading Python-3.8.2.tar.xz... -> https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tar.xz Installing Python-3.8.2... patching file Misc/NEWS.d/next/macOS/2020-06-24-13-51-57.bpo-41100.mcHdc5.rst patching file configure Hunk #1 succeeded at 3382 (offset -44 lines). patching file configure.ac Hunk #1 succeeded at 498 (offset -12 lines). python-build: use readline from homebrew python-build: use zlib from xcode sdk Installed Python-3.8.2 to /Users/username/.pyenv_x64/versions/3.8.2 と出ていれば成功です! pipenvでディレクトリごとに環境を作成 ここからはarm環境でもRosetta環境でも好きな方をお使いください pipenvを使ってディレクトリごとに環境を作っていきます。これを行うことによって、Pythonのバージョンに合わせたライブラリを入れることができます。 3.8.2用の環境を作成 #Rosettaで開き、環境構築(ARMでもpipenvはinstallしてください) $ uname -m x86_64 $ brew install pipenv #新しくディレクトリを作成し、その中に環境を作成 $ mkdir test $ cd test $ pipenv --python 3.8.2 --- #仮想環境に入る $ pipenv shell --- # ライブラリをインストール $ pipenv install numpy pandas matplotlib --- これで仮想環境作成完了です。あとはvsCodeを使うなり、jupyterを使うなり好きなようにPythonを楽しんでください! RosettaとARMをコマンドで変更する。 毎度毎度閉じてロゼッタを使用して開くのチェックボックスを付けて起動したり、外して起動したり・・・面倒じゃないですか? これをコマンドで解決してしまいましょう。 .zshrcを編集します。 下のコードを書いてください。 alias x86='arch -x86_64 zsh' alias arm='arch -arm64e zsh' これで終了です。 $ x86 $ uname -m x86_64 $ pyenv versions *system 3.9.1 $ arm $ uname -m arm $ pyenv versions system 3.9.1 3.8.2 ・・・ このようにarmやx86と打つだけでシステムを切り替えることができます。 まとめ この記事では、RosettaとARM環境の両方にpythonを使えるように環境構築を施しました。 少々面倒な場所も多かったと思いますが、次開くときはとても楽にpythonを楽しめると思います。 良きPythonライフを~ノシ 参考にさせていただいたサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OCI上のDjangoアプリをNginx+uWSGI+SSL (Let’s Encrypt)で公開

はじめに Django + Nginx + uWSGI + SSLのネタは三番煎じかもしれませんが、実際にトライしてみるとつまずく箇所がいくつかあったので、その備忘録として本記事を書きました(なので、特に問題の起きなかったOCIとDjangoのセットアップの説明は割愛しています)。 今回使用するサーバー(Oracle Cloud Infrastructure)、ドメイン、証明書の発行はすべて無料です。 環境 環境はOracle Cloud Infrastructure(OCI)の仮想マシンの無料枠で作成しました。イメージはOracle-Linux-7.9-2021.10.20-0、シェイプはVM.Standard.E2.1.Microです。 $ cat /etc/oracle-release Oracle Linux Server release 7.9 システムの構成 NginxはフリーかつオープンソースなWebサーバで、サーバー上の静的コンテンツを配信します。 DjangoはPythonで実装されたWebアプリケーションフレームワークです。 uWSGIとはPythonでWebサービスを動かすためのアプリケーションサーバです。PythonのWebアプリケーションとWebサーバー間とのやり取りの規約であるWSGI (Web Server Gateway Interface)に準拠したアプリケーション(Djangoなど)であれば使うことができます。DjangoのアプリケーションとNginx(Webサーバー)は直接対話できないので、uWSGIがその間を取り持ちます。 無料のドメインの取得 最終的にSSL化まで行うため、はじめにドメインを取得しておきます。Freenomから5種類のドメイン(.tk/.ml/.ga/.cf/.gq)が無料で取得できます。2021/11/13現在、ドメインの有効期限は取得日から三か月でした。以降、example.tkというドメインを取得した前提で話を進めます。 Nginxのインストール Yumのリポジトリファイルを作成します。 /etc/yum.repos.d/nginx.repo [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/7/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true [nginx-mainline] name=nginx mainline repo baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true インストールします。 sudo yum install -y nginx バージョンを確認します。 $ nginx -v nginx version: nginx/1.20.1 Firewallの設定 外部からサーバーのポートにアクセスするためにfirewall-cmdで必要なポートを登録しておきます。今回登録するのはhttpとhttpsと8000/tcp(uWSGIの検証用)です。OCIの方でもセキュリティリストからポートを開放しておく必要があります。 sudo firewall-cmd --zone=public --add-service=http --permanent sudo firewall-cmd --zone=public --add-service=https --permanent sudo firewall-cmd --permanent --add-port=8000/tcp sudo firewall-cmd --reload uWSGIでDjangoのアプリケーションを起動 今回はdjango_projectという名前のDjangoプロジェクトがあらかじめ作成されていることとします。 はじめに、Djangoのアプリケーションが正しく起動するか検証します。runserverを使って、8000番ポートで起動してみます。 python manage.py runserver 0.0.0.0:8000 ここで以下のようなSQLiteのエラーが出たら django.core.exceptions.ImproperlyConfigured: SQLite 3.9.0 or later is required (found 3.7.17). 「Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応」の記事を参考にして、解決してください。最終的にhttp://example.tk:8000からDjangoのアプリケーションにアクセスできれば成功です。 続いて、uWSGIでDjangoのアプリケーションを起動します。 pipでuWSGIをインストールします。 pip install uwsgi Djangoのプロジェクト下のuwsgi_paramsを作成します。 https://github.com/nginx/nginx/blob/master/conf/uwsgi_params uwsgi_param QUERY_STRING $query_string; uwsgi_param REQUEST_METHOD $request_method; uwsgi_param CONTENT_TYPE $content_type; uwsgi_param CONTENT_LENGTH $content_length; uwsgi_param REQUEST_URI $request_uri; uwsgi_param PATH_INFO $document_uri; uwsgi_param DOCUMENT_ROOT $document_root; uwsgi_param SERVER_PROTOCOL $server_protocol; uwsgi_param REQUEST_SCHEME $scheme; uwsgi_param HTTPS $https if_not_empty; uwsgi_param REMOTE_ADDR $remote_addr; uwsgi_param REMOTE_PORT $remote_port; uwsgi_param SERVER_PORT $server_port; uwsgi_param SERVER_NAME $server_name; 以下のコマンドで起動します。ここではブラウザなどを通じてデバッグするために--httpを使っていますが、Nginxと通信するときはUnix socket(--socket)を使用します。 uwsgi --http :8000 --module /path/to/your/django_project.wsgi certbotを用いた証明書の取得 今回はLet's Encryptという認証局に証明書を発行してもらいます。その際にLet's Encryptとのやりとりや検証用ファイルの用意をしてくれるのがCertbotです。 普通であれば、はじめにcertbotをインストールするのに必要なEPELリポジトリを準備します。しかし、Oracle Linux 7用のol7_developer_EPELは限りなくオリジナルのEPELに近いため、ほとんどの場合EPELの追加インストールは不要とのことです。そこでol7_developer_EPELが有効になっているか確認します。 $ sudo yum repolist all | grep -i epel ol7_developer_EPEL/x86_64 Oracle Linux 7Server EPE disabled 無効になっていなければ、有効にします。 sudo yum-config-manager --enable ol7_developer_EPEL 再び有効になっているか確認します。 $ sudo yum repolist all | grep -i epel ol7_developer_EPEL/x86_64 Oracle Linux 7Server EPE enabled: 40,406 EPELの準備ができたら、certbotとpython2-certbot-nginx(Nginx向けの追加機能)のインストールを行います。 $ sudo yum -y install certbot python2-certbot-nginx はじめに、Nginxの設定ファイルである/etc/nginx/conf.d/default.conf内のドメイン名をlocalhostからexample.tkに変更しておきます。 sudo sed -i 's/localhost/example.tk/g' /etc/nginx/conf.d/default.conf 以下のコマンドを実行すると対話形式で証明書の発行が行えます。さらに、--nginxオプションをつけることで/etc/nginx/conf.d/default.confがよしなに自動編集されます。 sudo certbot --nginx -d example.tk uWSGIとNginxを連携 はじめに、Djangoの静的ファイルを準備します。 静的ファイルを収集するディレクトリをdjango_project/static/に指定する設定をsetttings.pyに記述します。 django_project/settings.py STATIC_ROOT = os.path.join(BASE_DIR, "static/") 静的ファイルをstaticディレクトリに集めます。 python manage.py collectstatic 次にNginxの設定を行います。 念のため、default.confを編集する前にオリジナルの設定ファイルのバックアップを作っておきます。 sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.copy /etc/nginx/conf.d/default.confを編集します。 /etc/nginx/conf.d/default.conf(変更前) server { server_name example.tk; #access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/example.tk/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/example.tk/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = example.tk) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name example.tk; return 404; # managed by Certbot } 主な変更点は以下の通りです。 uWSGIとUnix socketを用いて通信する設定を追記 Djangoのstaticパスを指定 location /をDjangoのプロジェクトに変更 /path/to/your/とexample.tkとdjang_projectは自分の環境に合わせて変更してください。 /etc/nginx/conf.d/default.conf(変更後) upstream django { server unix:///path/to/your/django_project.sock; # for a file socket } # configuration of the server server { server_name example.tk; # substitute your machine's IP address or FQDN charset utf-8; error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # Django media location /media { alias /path/to/your/media; # your Django project's media files - amend as required } location /static { alias /path/to/your/static; # your Django project's static files - amend as required } # Finally, send all non-media requests to the Django server. location / { uwsgi_pass django; include /path/to/your/uwsgi_params; # the uwsgi_params file you installed } listen 443 ssl; ssl_certificate /etc/letsencrypt/live/example.tk/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/example.tk/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = example.tk) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name example.tk; return 404; # managed by Certbot } /etc/nginx/conf.d/default.confを/etc/nginx/nginx.conf内でインクルードするように設定します。 /etc/nginx/nginx.conf http { ... include /etc/nginx/conf.d/default.conf; ... } Nginxを(再)起動します。 sudo systemctl start nginx uWSGIでDjangoのアプリケーションを起動します。 uwsgi --socket django_project.sock --module django_project.wsgi --chmod-socket=664 https://example.tkからDjangoのアプリケーションにアクセスできたら成功です。 iniファイルから起動する場合は、以下の通りです。 uwsgi.ini [uwsgi] # Django-related settings # the base directory (full path) chdir = /path/to/your/django_project # Django's wsgi file module = django_project.wsgi # process-related settings # master master = true # maximum number of worker processes processes = 10 # the socket (use the full path to be safe socket = /path/to/your/django_project/django_project.sock # ... with appropriate permissions - may be needed chmod-socket = 664 # clear environment on exit #vacuum = true uwsgi --ini uwsgi.ini 参考 仮想マシン - Oracle Cloud Infrastructure OCI Always Free Compute (Oracle Linux・CentOS 7) に nginx をインストールする 無料のドメインを取得する CentOS7にnginxとcertbotを導入してHTTPS環境をさくっと作るの巻 あらためてEPELリポジトリの使い方をまとめてみた Setting up Django and your web server with uWSGI and nginx Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django Gitアップ前のシークレット情報設定

はじめに Gitへアップする前のシークレット情報の設定方法を手順化しました。 Pythonのバージョン:3.8.2 djangoのバージョン:3.2.9 gitのバージョン:2.30.2 目次 1.django-environのインストール 2.「.env」の作成 3.settings.pyの修正 4.「.gitignore」の確認 インストール django-environをインストールします。 pip install django-environ フォルダとファイルの作成 BASE_DIR(manage.pyがあるディレクトリ)にsecretsフォルダを作成します。 作成後、secretsフォルダ内に「.env」を作成します。 .envにproject/settings.pyの下記の情報を記載します。 ・SECRET_KEY ・DEBUG ・ALLOWED_HOSTS secrets/.env SECRET_KEY=secret_key #ここにsecret_keyを貼り付ける ※ダブルクォーテーションは取り除く DEBUG=True ALLOWED_HOSTS=* DATABASE_URL=sqlite:///db.sqlite3 settings.pyの修正 projectのsettings.pyの中を修正していく project/settings.py #追記--- import environ env = environ.Env() root = environ.Path(BASE_DIR / "secrets") env.read_env(root(".env")) #修正--- SECRET_KEY = env.str("SECRET_KEY") DEBUG = env.bool("DEBUG") ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") DATABASES = { 'default':env.db(), } } .gitignoreの確認 .gitignoreに.envの記載があることを確認 ない場合は追記 .gitignore # Environments .env 設定は以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder C - Back and Forth 解法メモ

2回目の行きの際にはL、Dは必ず一回発生し、U、Rは1回目よりも値が差分+1大きくなる。 同様に2回目帰りの際にはR,Uが必ず一回発生し、D、Rは1回目よりも値が差分+1大きくなる。 上記には気がつけたが、コーディングに落とし込む能力がなかった。 sx,sy,tx,ty = list(map(int, input().split())) x = tx - sx # x座標間の差分を出す y = ty - sy # y座標間の差分を出す ans = "R"*x + "U"*y + "L"*x + "D"*y # 1回目の行きと帰り ans += "D" + "R"*(x+1) + "U"*(y+1) + "L" + "U" + "L"*(x+1) + "D"*(y+1) + "R" # 2回目の行きと帰り print(ans) 参考 https://atcoder.jp/contests/abc051/submissions/27297543
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebAPIをたたく with Python

PythonでWebAPIを叩く方法を簡潔に紹介します。 使用するAPI ここではNews APIを扱います。News APIでは世界中のニュースを取得することができます。また、個人で利用する分には十分無料で使えます。 今回はAPIを叩くことだけが目的なので、News APIの使い方には言及していません。 実際のコード import requests # News APIに登録した際に発行される自分用のAPI KEY API_KEY = 'Your API KEY' # APIを叩く!! res = requests.get(f'https://newsapi.org/v2/everything?q=bitcoin&apiKey={API_KEY}') res_obj = res.json() # json形式に変換 print(res.status_code) # 200が返ればOK print(res_obj) エンドポイント 特定のリソースに対して与えられた固有の一意なURLのことです。今回の例でいうと、以下に示すように ? の手前までのURLです。 https://newsapi.org/v2/everything パラメータ ほしい情報を取得するための条件のようなもののことです。今回の例でいうと、2つのパラメータ(q, apiKey)が与えられています。パラメータどうしは & でつなげます。 q=bitcoin&apiKey={API_KEY} 出力例 以下のような出力が得られれば大丈夫です。 200 { "status": "ok", "totalResults": 8069, "articles": [ { "source": { "id": "the-verge", "name": "The Verge" }, "author": "Richard Lawler", "title": "A fake press release claiming Kroger accepts crypto reached the retailer’s own webpage", "description": "A crypto hoax claimed Kroger is accepting Bitcoin Cash. The fake press release was similar to one targeting Walmart earlier this year. The retailer quickly confirmed it’s fake, but not before the cryptocurrency’s price spiked by $30.", "url": "https://www.theverge.com/2021/11/5/22765098/kroger-bitcoin-cash-cryptocurrency-hoax-pump-dump", "urlToImage": "https://cdn.vox-cdn.com/thumbor/CKp0YjnwF88--mWg1kfPmspvfzY=/0x358:5000x2976/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/22988084/1234440443.jpg", "publishedAt": "2021-11-05T13:32:14Z", "content": "A similar hoax earlier this year tied Walmart to Litecoin\r\nIf you buy something from a Verge link, Vox Media may earn a commission. See our ethics statement.\r\nPhoto Illustration by Thiago Prudencio/S… [+1900 chars]" }, { "source": { # 省略 さいごに 以上がWebAPIをPythonで叩く方法になります。 APIに関してはもっと深く掘り下げて説明しようとすると、HTTP、ステータスコード、REST APIなど取り上げるべき話がたくさんあります。ただし今回はあくまでもWebAPIをどうやって叩くのかだけに注力したかった(ダラダラと長い記事を書きたくない)のでここで締めます。 それではよい1日を!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINEのテキストファイルを分析しやすそうに整形する

背景 データ分析のインターン先で、個人タスクとしてFlaskでWebアプリケーションを作成したのですが、LINEのトークスクリプトが結構汚かったので、自分の整形方法を書いてみようと思います。 ちなみに今回の続編記事も書いておりますので、よかったらそちらもご覧ください! お願い かなり泥臭くデータ整形していると思います。 そんなことをしなくても「こうすれば良くない?」的なご指摘コメント等いただけたら幸いです。 また、LINEのトークを利用します。 他人とのトークは個人情報の宝庫なので、利用する際はちゃんと相手の許可を取り、扱いには十分気をつけましょう。 データの取り込み LINEはtxt形式でトークスクリプトをダウンロードすることができます。 LINEアプリから取得したテキストファイルを、分析環境に持ってきます。 ちなみに僕はローカルのanacondaで分析します。 [LINE] {{user名}}とのトーク.txt こんな感じのtxtファイルを触っていきます。python側では、 import pandas df = pandas.read_csv("[LINE] {{user}}とのトーク.txt", sep='\n') とすれば読み込むことができます。タブ形式で読み込むとかなり汚いデータフレームが出来上がるので、行ごとに読み込みます。 データの整形 ここからは皆さんが取得したテキストファイルの中身にもよりますが、LINEのテキストファイルの中身はこんな感じになっていますね。 [LINE] {{user}}とのトーク履歴 保存日時:yyyy/mm/dd hh:mm 2021/02/22(月) 11:19 me こんにちは! 2021/02/23(火) 15:00 user "ごめんなさい! 全然気づきませんでした!笑" 15:00 user [スタンプ] me:筆者 user:話者 で会話を一部抜粋した形で掲載してみました。 この生データを見る限り、最初の3行を除けば、 行の形式種類は4パターンありそうです。 yyyy/mm/dd(day) hh:mm 話者 context context 空白 分析し辛いったらありゃしないですが、これが生データです。笑 あるあるですが、コンペ用のデータではないのでしょうがないですねー。 上記の4パターン別で、データフレームを1行ずつ作成していこうと思います。 日付の取得 行によっては日付がない行もあるので、各行に日付を追加していきます。 アルゴリズムはコードをみていただけるとありがたいです。。。 import re # 正規表現でトークから日付を取得し新しいカラムとして結合する date_list = [] date_pattern = '(\d+)/(\d+)/\d+\(.?\)' for talk in df['talks']: result = re.match(date_pattern, talk) if result: date_t = result.group() date_list.append(date_t) else: date_list.append(date_t) df['date'] = date_list # talksにおいて日付のカラムはもう必要ないので、そこがTrueのものをisin関数で消す flag = df['talks'].isin(df['date']) df = df[~flag] df.dropna(inplace=True) トークスクリプトの分割 「時間 話者 context」が1行でミックスされているので分解していきます。 # talks内を、時間、話者、トーク内容で分ける time_l = [] user_l = [] talk_l = [] date_l = [] count = 0 for date, talk in zip(df['date'], df['talks']): # もし正規表現で時間が取れたらスプリットして3つに分ける if(re.match('(\d+):(\d+)', talk)): try: if(len(talk.split('\t')[0]) == 5): date_l.append(date) time_l.append(talk.split('\t')[0]) user_l.append(talk.split('\t')[1]) talk_l.append(talk.split('\t')[2]) count = count + 1 else: continue except: talk_l.append("メッセージの送信取り消し") count = count + 1 else: talk_l[count-1] = talk_l[count-1] + talk df = pandas.DataFrame({"date" : date_l, "time" : time_l, "user" : user_l, "talk" : talk_l}) コードはこんな感じで、データフレームの上の方を見てみるとこんな感じになります。 曜日データは今回使わないので、消してしまいます。 date_l = [] for date in df['date']: date_l.append(date[:-3]) df['date'] = date_l 最後に これでデータ分析の準備は終了です! ここから自然言語処理をかけていくわけですね。 個人情報の流出等は十分に気をつけてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

都道府県別「直近1週間の人口10万人あたりの感染者数」をクラスター分析

◎概要 教師なし機械学習のクラスター分析を用いて、都道府県ごとの「直近1週間の人口10万人あたりの感染者数」を5つのクラスターに分類して(期間ごと)、その分類結果を考察する。 ◎目的 教師なし機械学習の勉強のため。 ちょっとしたデータをすぐにクラスター分析できるようになっておきたい。 ○ Python実行環境 Colaboratory ○ ソースコード GoogleDriveをmount 画像保存先をGoogleDriveに from google.colab import drive drive.mount('/content/drive') %cd /content/drive/My Drive/Colab Notebooks Package Install 必要なパッケージをインストール !pip install japanize_matplotlib import pandas as pd import math import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler import datetime データ取得&整形 NHKのHPからCSVをDLしてきて、使いやすいようにデータを整形 """ データ取得 """ def data_get (start, end): df = pd.read_csv('nhk_news_covid19_prefectures_daily_data.csv') df.columns.values rows = [] date_list = df.query('都道府県コード == "01"')['日付'].values.tolist() start_date = date_list.index(start) end_date = date_list.index(end) + 1 labels = date_list[start_date:end_date] labels.insert(0, '都道府県名') for pref_code in range(1,48): cols = [] data1 = df.query(f'都道府県コード == "{pref_code}"')['都道府県名'].values.tolist()[0] cols.insert(0,data1)[start_date:end_date] data2 = df.query(f'都道府県コード == "{pref_code}"')['各地の直近1週間の人口10万人あたりの感染者数'].values.tolist()[start_date:end_date] cols.extend(0.0 if math.isnan(d) else d for d in data2) rows.append(cols) df_result = pd.DataFrame(rows, columns=labels) x_data = df_result.drop(labels=['都道府県名'], axis=1).values x_labels = df_result.drop(labels=['都道府県名'], axis=1).columns.values return df_result, x_data, x_labels クラスタリング 教師なし学習で分類 """ K-平均法 """ def k_means(df, x_data, n_clusters): scaler = StandardScaler() x_scaled = scaler.fit_transform(x_data) kmeans = KMeans(n_clusters=n_clusters, random_state=0) kmeans.fit(x_scaled) cluster = kmeans.predict(x_scaled) df_cluster = df.copy() df_cluster['cluster'] = cluster return cluster, df_cluster グラフ表示&画像保存 クラスタリングした結果をグラフにプロットして、結果を画像として保存 """ グラフ表示 """ def plot_view(cluster, df_cluster, graph_title, x_labels): data_labels = [datetime.datetime.strptime(l, '%Y/%m/%d') for l in x_labels] deta_length = len(np.unique(cluster)) cols_index = 2 rows_index = math.ceil(deta_length / cols_index) data_count = 0 scale = 0.65 fig = plt.figure(figsize=(cols_index*16*scale, rows_index*9*scale)) plt.suptitle(graph_title, size='30') for row_i in range(rows_index): for col_i in range(cols_index): if data_count < deta_length: ax = fig.add_subplot(rows_index, cols_index, data_count+1) for v in df_cluster.query(f'cluster == {data_count}').values.tolist(): ax.plot(data_labels, v[1:len(v)-1], label=v[0]) ax.set_title(f'cluster{data_count}') ax.legend(bbox_to_anchor=(0, 1), loc='upper left', ncol=3, borderaxespad=0.5) else: break data_count += 1 fig.savefig(f'{graph_title}.png') ○ 実行 2020/1/18 - 2021/11/18の間のデータを半年ごとに5つに分類して、各結果を画像として保存 df, x_data, x_labels = data_get('2020/1/18', '2020/6/30') cluster, df_cluster = k_means(df=df, x_data=x_data, n_clusters=5) plot_view(cluster=cluster, df_cluster=df_cluster, graph_title='2020 01-06', x_labels=x_labels) df, x_data, x_labels = data_get('2020/7/1', '2020/12/31') cluster, df_cluster = k_means(df=df, x_data=x_data, n_clusters=5) plot_view(cluster=cluster, df_cluster=df_cluster, graph_title='2020 07-12', x_labels=x_labels) df, x_data, x_labels = data_get('2021/1/1', '2021/6/30') cluster, df_cluster = k_means(df=df, x_data=x_data, n_clusters=5) plot_view(cluster=cluster, df_cluster=df_cluster, graph_title='2021 01-06', x_labels=x_labels) df, x_data, x_labels = data_get('2021/7/1', '2021/11/18') cluster, df_cluster = k_means(df=df, x_data=x_data, n_clusters=5) plot_view(cluster=cluster, df_cluster=df_cluster, graph_title='2021 07-', x_labels=x_labels) ○ 結果 ➀第1波 4-5月 ➁第2波 8月 ➂第3波 1月・第4波 4-5月 ➃第5波 8-9月 結果から考察 東京・北海道・大阪・沖縄の4つの都道府県は、他の都道府県に比べて少し特殊な分類になることが多い。 2021~ 1都3県が同じクラスタに分かれている。足並みをそろえた結果か? ➂の分類結果が顕著だが、比較的近い地域でクラスタ分類されている。(cluster0が関東・cluster3が関西) ➂の分類から、  cluster0(関東)は第3波>第4波  cluster4(関西)は第3波<第4波 となっていて、cluster4グループに比べてcluster0グループは、第4波の感染者の増減を抑えられている。 最近に近づくにつれて、グループごとの波の特色がなくなってきている。 ○ 参考 https://www.youtube.com/watch?v=okpRV08-svw https://qiita.com/HidKamiya/items/c3cc11438d1f67f655cb ○ データの利用 「新型コロナ関連の情報提供:NHK」 https://www3.nhk.or.jp/news/special/coronavirus/data/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】東証上場銘柄の株価データを取得する(その2)

はじめに 前回の【Python】東証上場銘柄の株価データを取得する(その1)では 日本取引所グループにある東証上場銘柄の一覧ファイルを Pandas を使って銘柄抽出してみた 今回は,pandas_datareaderを使って,直近の株価データを取得してみる 自分銘柄 トヨタ,日産,ホンダ,ANA,JALを保有しているとする 下図のような Excel ファイルを my.xlsx という名前で用意した 列は,コードと銘柄名だ(日本取引所グループにある東証上場銘柄の一覧ファイルのそれと合わせた) 事前準備 前回書き忘れたが, 東証上場銘柄の一覧ファイルは data_j.xls という拡張子 xls ファイルなので, xlrd をインストール必要がある conda や pip で自分の環境に入れる必要がある conda の場合 conda install xlrd pip の場合 pip install xlrd 本論 必要なライブラリをインポートする import pandas as pd import pandas_datareader.data as pdr import datetime from dateutil.relativedelta import relativedelta 自分銘柄一覧(my.xlsx)と東証上場銘柄一覧(date_j.xls)を読み込む my_df = pd.read_excel('my.xlsx', sheet_name = 'Sheet1', dtype = str) topix_df = pd.read_excel('data_j.xls', dtype = str) 東証上場銘柄をTOPIX Core30に絞る ちゃんと30行なっている topix_core30_df = topix_df[topix_df['規模区分']=='TOPIX Core30'] len(topix_core30_df) 30 TOPIX Core30銘柄のコードと自分銘柄のコードを集合にする(重複排除される) 自分銘柄の,日産とANAがTOPIX Large70,JALがTOPIX Mid400で,トヨタとホンダはTOPIX Core30銘柄なので 3銘柄増え 30+3=33銘柄になっていれば正解 code_set = set(topix_core30_df['コード'].tolist()) | set(my_df['コード'].tolist()) len(code_set) 33 集合の中にある銘柄コードだけを,TOPIX の DataFrame から抽出する isin を使うと,リストや集合の要素に一致する行が抽出できる df = topix_df[topix_df['コード'].isin(code_set)] df 日付 コード 銘柄名 市場・商品区分 33業種コード 33業種区分 17業種コード 17業種区分 規模コード 規模区分 1025 20211029 3382 セブン&アイ・ホールディングス 市場第一部(内国株) 6100 小売業 14 小売 1 TOPIX Core30 1434 20211029 4063 信越化学工業 市場第一部(内国株) 3200 化学 4 素材・化学 1 TOPIX Core30 1645 20211029 4452 花王 市場第一部(内国株) 3200 化学 4 素材・化学 1 TOPIX Core30 以降省略 リストにしておく codeList = df['コード'].tolist() companyList = df['銘柄名'].tolist() 取得期間終了日(本日)と取得期間開始日(本日から7日前) ed = datetime.datetime.now() # 本日 st = ed - relativedelta(days = 7) # 7日前 コードに '.T' を付加し,DataReader で株価データを取得する 株価データは DataFrame で取得できる 直近のデータは DataFrame の最終行なので, 日付(Index)と終値(Close列)の最終行のデータを表示する for code, company in zip(codeList, companyList): df = pdr.DataReader(code + '.T', 'yahoo', st, ed) dates = df.index # 日付はindexになっている closes = df['Close'] print(f'[{company}] date=[{dates[-1]}] close=[{closes[-1]}]') [セブン&アイ・ホールディングス] date=[2021-11-22 00:00:00] close=[4777] [信越化学工業] date=[2021-11-22 00:00:00] close=[20070] [花王] date=[2021-11-22 00:00:00] close=[6219] 以降省略 まとめ まとめると,次のようなコードになる topix.py import pandas as pd import pandas_datareader.data as pdr import datetime from dateutil.relativedelta import relativedelta """ 事前にここから東証上場銘柄一覧ファイル(data_j.xls)をダウンロードしておきます https://www.jpx.co.jp/markets/statistics-equities/misc/01.html あと,my.xlsx を用意します """ my_df = pd.read_excel('my.xlsx', sheet_name = 'Sheet1', dtype = str) topix_df = pd.read_excel('data_j.xls', dtype = str) topix_core30_df = topix_df[topix_df['規模区分']=='TOPIX Core30'] """ TOPIX Core30 銘柄と自分銘柄の一覧を作る """ # TOPIX Core30 銘柄のコードと自分銘柄のコードを集合にする(重複排除される) code_set = set(topix_core30_df['コード'].tolist()) | set(my_df['コード'].tolist()) # 集合の中にあるコードだけを,TOPIX の DataFrame から抽出する ## isin を使うと,リストや集合の要素に一致する行が抽出できる df = topix_df[topix_df['コード'].isin(code_set)] # リストにする codeList = df['コード'].tolist() companyList = df['銘柄名'].tolist() """ 指定期間の株価データを取得し表示する """ ed = datetime.datetime.now() # 本日 st = ed - relativedelta(days = 7) # 7日前 for code, company in zip(codeList, companyList): df = pdr.DataReader(code + '.T', 'yahoo', st, ed) dates = df.index # 日付はindexになっている closes = df['Close'] print(f'[{company}] date=[{dates[-1]}] close=[{closes[-1]}]') おわりに 楽しい,便利すぎるぜ,Python 次回は,ローソク足チャートを描く予定
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Colaboratory にて for 文の進捗を可視化する

調べれば無限に出てくるよくある進捗の可視化方法についてメモする。 まずは以下のコードの通りに tqdm を import しておく。 from tqdm.notebook import tqdm あとは for 文のところに tqdm をかませると、進捗が可視化できる。 time は特に処理がないけれど処理時間の代わりに待機時間として経過してプログレスバーが進んでいく様子を観察するために使った。 import time for i in tqdm(range(100)): time.sleep(0.1)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSONと辞書型(ディクショナリー)の違い

はじめに こんにちは、よわよわエンジニアです。 本稿では、超よわよわだった当時の自分に向けて、JSONと辞書型の違いを説明します。 あくまでも "違い" にフォーカスして解説しますので、JSONと辞書型それぞれの詳細な説明は他の記事をご参照ください。 ちなみに、Pythonでは「辞書型」と言いますが、他の言語では「オブジェクト型」や「連想配列」、「ハッシュテーブル」と言ったりしますね。 ここからは「辞書型」と呼称を統一し、Pythonのコードベースで説明していきます。 JSONと辞書型の違いが理解できなかった プログラミングを学びたてだった当時、私は「辞書型」→「JSON」の順でこれらを知ることになりますが、長らくその違いを理解することができませんでした。 Pythonを学び始めてまずは「辞書型」を知り、以下のようなコードで「ふんふん…こんな感じね」と理解します。 python d = {"name": "Taro", "age": 12, "country": "Japan"} print(d) # {'name': 'Taro', 'age': 12, 'country': 'Japan'} その後、Webの勉強をし始めた私は「JSON」と出会います。 Wikipediaで以下のように説明がある通り、私が当時読んだ記事にも「JSONはデータフォーマットの一種である」と記載されていました。 JavaScript Object Notation(JSON、ジェイソン)はデータ記述言語の1つである。軽量なテキストベースのデータ交換用フォーマットでありプログラミング言語を問わず利用できる そして、JSONで表記されたデータ構造の具体例として以下のような記載があったのですが... JSON { "name": "Taro", "age": 12, "country": "Japan" } 「辞書型をprintしたものと一緒じゃねーか! 何が違うんだあああ...」 JSONと辞書型は何が違うのか 結論 JSONは「データフォーマット」であり、辞書型は「Pythonにおけるデータ型の一種」です。 JSONというフォーマットに従って記述されたデータは、そのままでは構造化された情報へのアクセスが容易に行えません。 なぜなら、ただの文字列だからです。 d["key名"]のような形でデータにアクセスできるようにするには、それぞれのプログラミング言語に応じて適切なデータ型に変換してあげる必要があります。 それがPythonでは辞書型であり、JavaScirptではオブジェクト型に該当する訳です。 JSONは「データフォーマット」 JSONは「こういうルールで書きましょうね」というただのフォーマットです。 似たものにCSVがありますが、CSVでは以下のように「カンマ区切りで書きましょう」というルールに基づいてデータを表現しますよね。 csv name, age, country Taro, 12, Japan 一方でJSONでは、「{}(波括弧)で囲みましょう」であったり「文字列は必ず""(ダブルクオーテーション)で囲みましょう」といったルールが定められており、そのルールに基づいて以下のようにデータを表現します。 JSON { "name": "Taro", "age": 12, "country": "Japan" } つまり、あえて誤解を恐れずに言うと、JSONはただの「文字列」です。 以下の例では、json_text という変数に、JSON形式の文字列を代入しています。 python json_text = '{"name": "Taro", "age": 12, "country": "Japan"}' print(type(json_text)) # <class 'str'> せっかくの構造化されたデータなので、json_text["name"] のような形で値を取得したくなりますよね。 しかし、そんなことは出来ません。なぜならjson_textはstr型のただの文字列だからです。 python print(json_text["name"]) # 以下のエラーが出力され、値は取得できない # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: string indices must be integers json_text["name"]といった形でデータを取り扱いたい場合、Pythonでは「辞書型」に変換することでこれを実現します。 辞書型は「データ型」 一方で辞書型はPythonで扱えるデータ型の一種です。 Pythonではint型、str型など様々なデータ型がありますが、その一つがdict型(辞書型)です。 辞書型はさまざまな便利機能をもっており、d["key名"]とすることで値を取得できたり、d.keys()とすることでkeyの一覧を取得できたりします。 Pythonでは、JSON形式で記述されたデータ(文字列)をこの辞書型に変換することで、辞書型のさまざまな便利機能を使ってデータをより柔軟に取り扱うことが出来るようになります。 変換に際しては、jsonというモジュールを使います。 python import json json_text = '{"name": "Taro", "age": 12, "country": "Japan"}' d = json.loads(json_text) print(type(d)) # <class 'dict'> print(d["name"]) # Taro str型である json_text を json.loads() という関数で変換することでdict型(辞書型)になり、d["name"]という形で値を取得できているのがわかります。 また当然ですが、辞書型からJSON形式の文字列に変換することも可能です。 その際は、json.dumps()に辞書型のデータを渡すことで実現できます。 まとめ 本稿のまとめです。 JSONと辞書型の違い JSONはデータフォーマット 辞書型はPythonにおけるデータの型 JSON形式のデータはただの文字列なので、それぞれのプログラミング言語で適切なデータ型に変換することで扱いが容易になる 参考文献 JavaScript Object Notation - Wikipedia
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Docs APIを使って、索引を作成する

はじめまして、記事を読んでいただいてありがとうございます。 この記事は『LITALICO Engineers Advent Calendar 2021』の4日目の記事です。 私は、2021年4月にLITALICOに入社をしまして、ドメイン知識が0の状態から、周りの方に助けていただいて、どうにかこうにかプロダクト開発に携わっています。 そんな私としまして、ドメイン知識について、効率的にinputを行いたいと常日頃考えております。 誤解がないように記載しておきますが、周囲からフォローがないという訳ではありません。むしろ、丁寧に教えてくださるのですが、誰かに教わるということは、その方の分のコストも使わせていただくわけで、自分一人で調べて、ちゃんと理解できるなら、コスト効率高いよねと考えた次第です。 (職場へのフォローを忘れない組織人の鏡) 他にも色々方法を考えたのですが、まずは、大量にあるドキュメントから、自分が必要としている情報を探すをゴールに、Google Drive上の索引を作成したいと思い至った次第です。 事前準備 APIが有効になっているGoogleCloudPlatformプロジェクト 今回は、Google Drive APIと、Google Docs APIを使用するので、2つとも有効にしてください 設定の仕方は、ちょこちょこ、GoogleCloudPlatformのUI変わったりするので、公式のプロジェクトの設定の仕方を確認してくださいませ Google APIを利用する際の認証情報 こちらも公式の認証情報の設定の仕方を参照してくださいませ また、この認証情報は他の方に教えてはいけません わかち書きエンジン この記事では、Mecabを使用しております プログラム言語の実行環境 この記事では、Python 3系を使用しております 公式の実装サンプルからちょこちょこ変えてるだけなので、nodeでも、Javaでもお好きなものを google-authを使用して OAuth 2.0 の認証を通す import os.path from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials SCOPES = [ 'https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/documents.readonly' ] CLIENT_SECRETS_FILE = 'credentials.json' TOKEN_JSON_FILE = 'token.json' def get_credentials(): credentials = None if os.path.exists(TOKEN_JSON_FILE): credentials = Credentials.from_authorized_user_file(TOKEN_JSON_FILE, SCOPES) if not credentials or not credentials.valid: if credentials and credentials.expired and credentials.refresh_token: credentials.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( CLIENT_SECRETS_FILE, SCOPES) credentials = flow.run_local_server(port=0) with open(TOKEN_JSON_FILE, 'w') as token: token.write(credentials.to_json()) return credentials 簡単な解説 SCOPES = [ 'https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/documents.readonly' ] 使用するスコープのリストを定義しておきます。 事前準備で用意していただいた認証情報のスコープに、上記で定義したものを含めるようにしてくださいませ。 CLIENT_SECRETS_FILE = 'credentials.json' TOKEN_JSON_FILE = 'token.json' 事前準備で用意していただいた認証情報は、CLIENT_SECRETS_FILEでパスを指定。 TOKEN_JSON_FILEは、2回目以降のために、認証トークンを保持します。 if os.path.exists(TOKEN_JSON_FILE): credentials = Credentials.from_authorized_user_file(TOKEN_JSON_FILE, SCOPES) TOKEN_JSON_FILEが存在する場合に、TOKEN_JSON_FILEから過去に通った認証を取得します if not credentials or not credentials.valid: 過去に通った認証が有効ならば、そのまま、認証を返します。 以下、認証が無効な場合 if credentials and credentials.expired and credentials.refresh_token: credentials.refresh(Request()) 認証は無効だけど、有効期限切れ、かつ、リフレッシュトークンがある場合、認証をリフレッシュします else: flow = InstalledAppFlow.from_client_secrets_file( CLIENT_SECRETS_FILE, SCOPES) credentials = flow.run_local_server(port=0) そもそも、認証通ってない場合、最初に認証通すとき、 認証情報を元に、承認フローのインスタンスを生成して、ローカルサーバから承認フローを実行します 一連の処理は、以下、importしているGoogleのライブラリーを見ると、処理がわかりやすいと思います。 https://github.com/googleapis/google-auth-library-python https://github.com/googleapis/google-auth-library-python-oauthlib with open(TOKEN_JSON_FILE, 'w') as token: token.write(credentials.to_json()) 再利用するために、有効な認証を、TOKEN_JSON_FILEに保存します。 Drive APIで、Docsファイルを取得する import os.environ import pymysql.cursors from googleapiclient.discovery import build DRIVE_API = { 'service_name': 'drive', 'version': 'v3' } connection = pymysql.connect(host = os.environ['DB_HOST'], user = os.environ['DB_USER'], password = os.environ['DB_PASSWORD'], db = os.environ['DB_NAME'], charset = os.environ['DB_CHARSET']) INSERT_SQL = "INSERT INTO test.docs (document_id, name) values (%s,%s)" ... def get_docs(): creds = get_credentials() drive_service = build(DRIVE_API.service_name, DRIVE_API.version, credentials=creds) while True: values = [] response = drive_service.files().list(q="mimeType='application/vnd.google-apps.document'", spaces='drive', fields='nextPageToken, files(id, name)', pageToken=page_token).execute() for file in response.get('files', []): values.append([file.get('id'), file.get('name')]) cursor = connection.cursor() cursor.executemany(INSERT_SQL, values) page_token = response.get('nextPageToken', None) if page_token is None: break 簡単な解説 drive_service = build(DRIVE_API.service_name, DRIVE_API.version, credentials=creds) 認証を元に、APIと対話するためのResourceオブジェクトを作成します。 ここも、以下、importしているGoogleのライブラリーを見ると、処理がわかりやすいと思います。 https://github.com/googleapis/google-api-python-client response = drive_service.files().list(q="mimeType='application/vnd.google-apps.document'", spaces='drive', fields='nextPageToken, files(id, name)', pageToken=page_token).execute() Drive APIを用いて、必要な情報を取得します。 今回は、Docsファイルだけ取得したいので、mimeTypeが、'application/vnd.google-apps.document'のファイルの一覧を取得。Spreadsheetの場合は、mimeTypeが、'application/vnd.google-apps.spreadsheet'。 公式だと、このページにGoogle特有のmimeTypeがまとめてあります。 page_token = response.get('nextPageToken', None) if page_token is None: break nextPageTokenが存在する場合、一覧に次ページが存在するのでループして、次ページについて、処理。 nextPageTokenがNoneの場合、最後のページまで処理が終わったので、break。 Drive APIから取得したdocumentIDで、Docs APIでドキュメントの本文取得 DOCS_API = { 'service_name': 'docs', 'version': 'v1' } ... def get_doc_content_lines(document_id): creds = get_credentials() doc_service = build(DOCS_API.service_name, DOCS_API.version, credentials=creds) document = doc_service.documents().get(documentId=document_id).execute() return document.get('body') 簡単な解説 doc_service = build(DOCS_API.service_name, DOCS_API.version, credentials=creds) Drive APIと同じように、認証を元に、APIと対話するためのResourceオブジェクトを作成します document = doc_service.documents().get(documentId=document_id).execute() return document.get('body') Docs APIでドキュメントのデータ取得。 現在のAPI仕様だと、page数などは取得できないので、本文のみ取得する Mecabで、文章中の名詞のみ取得 import MeCab tagger = MeCab.Tagger() tagger.parse('') ... def extract_nouns(lines, docs_id): nouns = {} insert_sql = "INSERT INTO test.nonus (docs_id, word, count) values (" + docs_id + ", %s, %d)" for line in lines.splitlines(): for chunk in tagger.parse(line).splitlines()[:-1]: (surface, feature) = chunk.split('\t') if feature.startswith('名詞'): nouns[surface] = nouns.get(surface, 0) + 1 cursor = connection.cursor() cursor.executemany(insert_sql, nouns.items()) 簡単な解説 用途にもよるのですが、今回は、索引なので、名詞だけ抽出しております。 pythonから辞書への追加ができないのが残念ですが、ドメインのワードを辞書追加すると、よりハッピーになれます。(pythonのライブラリにないだけで、頑張ればできそうな気もします。) for chunk in tagger.parse(line).splitlines()[:-1]: (surface, feature) = chunk.split('\t') if feature.startswith('名詞'): nouns[surface] = nouns.get(surface, 0) + 1 line = '私はLITALICO のシステムエンジニア です。' という文章に対して、mecab.parseの実行結果は、下記のようになります。 私 名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ は 助詞,係助詞,*,*,*,*,は,ハ,ワ LITALICO 名詞,固有名詞,組織,*,*,*,* の 助詞,連体化,*,*,*,*,の,ノ,ノ システムエンジニア 名詞,一般,*,*,*,*,システムエンジニア,システムエンジニア,システムエンジニア です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス 。 記号,句点,*,*,*,*,。,。,。 タブ区切りの後段に品詞の情報が出るので、それを元に、名詞か、それ以外か判定しております。 索引を作る際に、一般名詞は無視しても良い場合も多いのですが、辞書に存在しない語、辞書未登録語の場合、一般名詞として処理されます。そのため、一般名詞を除外すると、辞書未登録語を除外する可能性があるため、注意が必要です。 例) 私は、キャラメルフラペチーノが大好きです、キャラメルフラペチーノが辞書未登録語として、一般名詞となる 私 名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ は 助詞,係助詞,*,*,*,*,は,ハ,ワ キャラメルフラペチーノ 名詞,一般,*,*,*,*,* が 助詞,格助詞,一般,*,*,*,が,ガ,ガ 大好き 名詞,形容動詞語幹,*,*,*,*,大好き,ダイスキ,ダイスキ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス キャラメルフラペチーノが一般的ではないという意味ではありません。固有名詞のはずなのに、Mecabの辞書に載っていないから、カテゴリー全体をさす単語ではないのに、一般名詞になるのはおかしいよねという意味です。キャラメルフラペチーノは人類史上最も偉大な発明の一つです。そのうち、一般名詞になるかもしれませんね。 終わりに 実は、Google Docsの検索機能は、かなり良くて、単に、キーワードが含まれている資料を探すだけだと、Google Docsの検索機能を使った方が早いです。それをわかっていながら、わざわざ、索引を作ろうとしたのは、ゆくゆく文章解析をして、この文章が仕様書であるとか、議事録であるとか、そういったこともやりたいと考えているのと、うまくユビキタス言語が拾えないかなと思ったからです。また、slackや、Backlogなど、他のツールも合わせて、索引をまとめることもできそうですよね。 予告 来年は、心理学とベイズ統計のネタか、対話型インターフェースのネタを書きたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python×Sportify APIで国ごとのトレンド傾向をグラフ化する

はじめに 今回はPythonとSportify APIを使って下記のようなグラフを生成したいと思います。 自分の好きな国別のトップ200チャートから曲の特徴の平均値を取得しグラフ化したものです。 例えばこのグラフから、今日本は他国に比べてアコースティック感の強い曲が流行っているということが分かります。 余談にはなりますが、自分のよく聴く曲の特徴を可視化する方法も記事にしているので 良かったらこちらもご覧下さい。 環境 Python 3.7.5 ディレクトリ構成 ├── csv ├── images ├── main.py 予めcsvディレクトリとimagesディレクトリを作っておきます。 後述しますが、ダウンロードしたcsvファイルはcsvディレクトリに配置し、 main.pyの実行後に生成されるグラフはimagesディレクトリ配下に配置されます。 準備 Sportify APIのClient IDとシークレットキーを取得 下記の記事を参考に取得しました。 取得したい国のトップチャートcsvをダウンロードする 下記から各国のトップ200チャートをcsv形式でダウンロードできます。 日別、週間別で好きな時期のチャートを取得できます。 今回は冒頭で貼りつけたキャプチャの通り、 ブラジル、カナダ、ドイツ、エジプト、スペイン、フィンランド、日本、アメリカ、香港、南アフリカ の週間TOP200チャートをダウンロードしました。 ダウンロードしたcsvファイルは./csv配下に配置します。 必要なライブラリをインストール pandasのインストール csvの処理に使います。 $ pip install pandas matplotlibのインストール。 グラフ生成に使います。 また日本語をグラフ内に表示させる為、日本語化モジュールもインストールします。 $ pip install matplotlib $ pip install japanize-matplotlib 実装 main.py import spotipy from spotipy.oauth2 import SpotifyClientCredentials import matplotlib.pyplot as plt import japanize_matplotlib import pandas as pd import statistics import glob # 取得したClient IDとシークレットキーを入れる client_id = '****************' client_secret = '****************' client_credentials_manager = SpotifyClientCredentials(client_id, client_secret) sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) top_records = [] # ダウンロードしたcsvファイルを読み込む for csv in sorted(glob.glob("./csv/*.csv")): top_records.append(pd.read_csv(csv, header=1)) top_track_info = [] top_records_count = len(top_records) # 国ごとに曲の情報を取得する for i in range(top_records_count): top_ure_list = top_records[i]["URL"].unique() top_track_id = [top_url.split('/')[-1] for top_url in top_ure_list] top_track_info.append(pd.DataFrame()) for id in top_track_id: info = pd.DataFrame.from_dict(sp.audio_features(id)) top_track_info[i] = top_track_info[i].append(info) top_track_info[i] = top_track_info[i].reset_index(drop=True) top_track_info[i].head(10) top_track_info[i]["rank"] = top_track_info[i].index + 1 cols = top_track_info[0].columns excludes = ("analysis_url", "id", "rank", "track_href", "type", "uri", 'instrumentalness', 'key', 'mode', 'time_signature') col_nm = {"acousticness": "アコースティック感", "danceability": "ダンス感", "duration_ms": "長さ[ms]", "energy": "エナジー感", "liveness": "ライブ感", "loudness": "音圧", "speechiness": "スピーチ感", "tempo": "曲のテンポ(BPM)", "valence": "曲の明るさ"} contry = ("ブラジル", "カナダ", "ドイツ", "エジプト", "スペイン", "フィンランド", "日本", "アメリカ", "香港", "南アフリカ") # 特徴ごとにグラフを生成する for col in cols: avgs = [] if(col in excludes): continue plt.title("各国の週間TOP200ランキングの" + col_nm[col] + "(平均)") # 国ごとに平均値を取得 for i in range(top_records_count): avgs.append(statistics.mean(top_track_info[i][col])) plt.bar(contry, avgs, alpha=0.8) plt.legend(loc="upper left", fontsize=11) plt.xlabel("国名") plt.ylabel(col_nm[col]) plt.savefig("./images/" + col + ".png") plt.clf() 実行 $ python main.py imagesディレクトリ配下に、画像が生成されていく。 最後に 僕は普段はSportifyを利用する際、日本かグローバルのチャートしか聴いていませんでしたが、 こんなに多くの国のチャートが時期ごとに聴けるのは驚きでした。 普段馴染みのない国の音楽を聴くきっかけにもなり、良い体験ができました。 あなたも自分の好きな国や時期で是非試してみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで作る数当てゲーム

1~100のランダムな数字を当てるゲームです。 kazuate.py import random count = 1 answer = random.randint(1,100) while True: number = input('1~100の間で数字を入力してください >> ') if number.isdecimal(): number = int(number) else: print('間違った入力がされました。\n' 'もう一度数字の入力をお願いします。') continue if answer == number: print(f'正解です。正解は{answer}。') print(f'{count}回目のチャレンジで正解しました。') break elif answer > number: print(f'入力された数字は{number}。それより大きい数です。') count += 1 else: print(f'入力された数字は{number}。それより小さい数です。') count += 1 動作のスクリーンショットなど
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】東証上場銘柄の株価データを取得する(その1)

はじめに 【Python】Pandas で株価データを取得する(その4)ではExcelファイルに書かれた銘柄の株価データを取得した 自分の保有銘柄だけを分析したい場合は自分の保有銘柄を書いたExcelファイルを用意すればよいが, 分析対象を幅広く東証上場銘柄に広げようと思うと,自分でExcelファイルを用意するのは面倒だ 日本取引所グループには,東証上場銘柄の一覧がExcelファイルで用意されている これを使わない手はない Excel を使って東証上場銘柄一覧ファイルの確認 さっそく,日本取引所グループから東証上場銘柄一覧ファイル(data_j.xls)をダウンロードして開いてみる 東証上場銘柄一覧ファイルは,次の10列からできており, 全部で,4133銘柄(2021年10月末)ある 日付 コード 銘柄名 市場・商品区分 33業種コード 33業種区分 17業種コード 17業種区分 規模コード 規模区分 業種で絞るには「業種区分」列,株価指数で絞るには「規模区分」列がよさそうだ 「規模区分」は5つあり,次のようになっている 名称 構成銘柄 TOPIX Core30 時価総額と流動性の特に高い30銘柄 TOPIX Large70 Topix Core 30についで,時価総額と流動性の高い70銘柄 TOPIX Mid400 Topix Core30,Topix Large70 についで,時価総額と流動性の高い400銘柄 TOPIX Small 1 TOPIX Small の構成銘柄のうち TOPIX 1000 及び TOPIX Small500 の構成銘柄に該当する銘柄 TOPIX Small 2 TOPIX Small の構成銘柄のうち TOPIX 1000 及び TOPIX Small500 の構成銘柄には該当しない銘柄 Pandas を使って東証上場銘柄一覧ファイルの確認 まずは,Pandas で東証上場銘柄一覧ファイルを読み込む >>> import pandas as pd >>> df = pd.read_excel('data_j.xls', dtype = str) どんな列があるか >>> df.columns Index(['日付', 'コード', '銘柄名', '市場・商品区分', '33業種コード', '33業種区分', '17業種コード', '17業種区分', '規模コード', '規模区分'], dtype='object') 銘柄数は >>> len(df) 4133 規模区分とは,どんな区分か >>> df['規模区分'].unique() array(['TOPIX Small 2', '-', 'TOPIX Mid400', 'TOPIX Small 1', 'TOPIX Large70', 'TOPIX Core30'], dtype=object) 17業種区分とは,どんな区分か >>> df['17業種区分'].unique() array(['食品 ', '-', '商社・卸売 ', '建設・資材 ', '鉄鋼・非鉄 ', 'エネルギー資源 ', '情報通信・サービスその他 ', '機械 ', '医薬品 ', '不動産 ', '運輸・物流 ', '金融(除く銀行) ', '小売 ', '素材・化学 ', '電機・精密 ', '自動車・輸送機 ', '銀行 ', '電力・ガス '], dtype=object) Pandas を使って東証上場銘柄一覧ファイルの銘柄抽出 東証上場銘柄一覧ファイルが確認できたところで,銘柄の抽出を行ってみる 17業種区分が銀行の銘柄は >>> df[(df['17業種区分']=='銀行')] Empty DataFrame Columns: [日付, コード, 銘柄名, 市場・商品区分, 33業種コード, 33業種区分, 17業種コード, 17業種区分, 規模コード, 規模区分] Index: [] あれ空だ 17業種区分の値をよく見ると,'銀行 'のように後ろに空白が入っているので str.rstrip() を使って,末尾の空白を削除する必要があった >>> df[(df['17業種区分'].str.rstrip()=='銀行')] 81行が抽出された TOPIX Core30 の条件をつけると3メガ銀が抽出される >>> df[(df['17業種区分'].str.rstrip()=='銀行') & (df['規模区分']=='TOPIX Core30')]['銘柄名'] 3510 三菱UFJフィナンシャル・グループ 3513 三井住友フィナンシャルグループ 3554 みずほフィナンシャルグループ Name: 銘柄名, dtype: object TOPIX Core30 または TOPIX Large70 の条件にすると5大銀が抽出される >>> df[(df['17業種区分'].str.rstrip()=='銀行') & ((df['規模区分']=='TOPIX Core30') | (df['規模区分']=='TOPIX Large70'))]['銘柄名'] 3510 三菱UFJフィナンシャル・グループ 3511 りそなホールディングス 3512 三井住友トラスト・ホールディングス 3513 三井住友フィナンシャルグループ 3554 みずほフィナンシャルグループ Name: 銘柄名, dtype: object おわりに 今回はここまでで次回は,【Python】Pandas で株価データを取得する(その4)で書いた pandas_datareader を使って,直近の株価データを取得してみることにしよう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【図解解説】ARC129 B Range Point Distance

図解解説シリーズ 競技プログラミングを始めたばかりでAtCoderの解説やJOIの解説ではいまいちピンと来ない…という人向けに、図解を用いて解説を行います。 問題文 ARC129 B - Range Point Distance 図解解説 問題文を整理するために、具体的な数字を用いて、図示して考えてみます。 そこで、問題文に示されているdist(l,r,x)に具体的な数字を入れて考えます。まずl=2,r=4として、◯をl、●をrとして数直線に表したものが左上の図になります。xの値がlとrの間にあれば、答えは0(dist(2,4,2)=dist(2,4,3)=dist(2,4,4)=0)となります。また、xの値がl=2より小さければ、例えばx=0のときdist(2,4,0)=2-0=2(l>xのときdist(l,r,x)=l-x)となります。一方、xの値がr=4より大きければ、例えばx=5のときdist(2,4,5)=5-4=1(x>rのときdist(l,r,x)=x-r)となります。 従って、範囲が1つのとき、max(dist(2,4,x))を最小にするxは、2<=x<=4となる値を選べば良いので、答えは必ず0になります。 範囲が2つの場合を考えます。 【2つの範囲が重なっているとき】max(dist(1,3,x),dist(2,4,x))を最小にするxは、2つの範囲が重なっている値、つまりx=2(またはx=3)を選べば、dist(1,3,2)=0、dist(2,4,2)=0となり、答えは0になります。 【2つの範囲が重なっていないとき】max(dist(1,3,x),dist(5,7,x))を最小にするxを考えます。x=2からx=6を順次あてはめて計算を行うと、x=4のとき最小になることがわかります。つまり、左の範囲のr=3と右の範囲のl=5の中央値(x=4)が求めるxの値になり、答えは1となります。ここで、中央値が整数にならない場合を考えます。例えば、max(dist(0,2,x),dist(5,7,x))のときは、そのまま計算するとx=3.5になってしまいます。xが整数に反しますので、xの値を切り下げたx=3または、xの値を切り上げたx=4が求めるxの値となり、答えは2となります。 それでは、範囲が多くなったとき、どのように考えればよいでしょうか。 図のように、2つの場合で考えます。最初の例では、範囲が離れていますので、x=8で試しに計算を行なっています。すると、x=8を基準にして、左側にある範囲については、rの値を使ってdist(l,r,x)=x-rのように値を計算することになります。一方、右側にある範囲については、lの値を使ってdist(l,r,x)=l-xのように値を計算することになります。最終的に答えに影響する計算は、最小のrを含む範囲と、最大のlを含む範囲となります。さらによく観察すると、答えは最大のlと最小のrの距離の半分となります。答えが小数にはなりませんので、小数になる場合は求めた値を切り上げたものが答えになります。範囲が2つで離れているときの例でも同様に求めることができることを確認してみてください。 次の例では、すべての範囲が重なっています。このとき、先ほどの例で注目した最大のlと最小のrの位置関係を確認してみると、「最大のl<=最小のr」となっています。このときの答えは0ですので、最大のlと最小のrの大小関係に注意しながらプログラムを作成すればよいことがわかります。 解答例 最終的に作成したプログラムは以下の通りです。 前述の通り、すべての範囲から、最小のrと最大のlをまずは求めておきます。その後、最小のrと最大のlの大小関係から答えを求めます。 「最大のl<=最小のr」のときは、すべての範囲が重なっているので、答えは0になります。一方、「最大のl>最小のr」のときは、最大のlと最小のrの距離の半分(小数値となる場合は切り上げ)となります。 arc129b.py import math N = int(input()) mxL = -float('inf') mnR = float('inf') for i in range(N): l,r = map(int,input().split()) mxL = max(mxL,l) mnR = min(mnR,r) if mxL<=mnR: print(0) else: tmp = math.ceil(abs(mxL-mnR)/2) print(tmp) 提出したプログラム
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最も簡単なバックエンドAPIの構築方法

自分で書いたpythonソースコード(計算機能)を Webサービス として一般公開しておくと Excel google spreadsheet 自作アプリ など、どこからでも自分で書いた計算機能を利用できます。 今回は、google の Cloud Fuinction というサービスを用いて APIサーバーの構築方法を紹介します。 APIサーバーの構築 google Cloud Fuinction の登録 google Cloud Fuinction の説明はここでは省きます。他の情報を参照してください 下記のURLにアクセスしてください https://cloud.google.com/functions 無料で開始をクリック google にログインしていない場合はログインしてください Cloud Fuinction を始めて使う場合 アカウント情報の登録が必要です。 アカウント情報の登録 ステップ 1/3 アカウント情報の登録 ステップ 2/3 クレジットカードを入力します。 同じ画面の説明にあるように自動請求を有効にするまで課金されることはありません とこのあたりの説明を読んで納得できる方は先に進んでください アカウント情報の登録 ステップ 3/3 Google Cloud Functions の管理画面を表示する 左のメニューから Cloud Functions をクリックしてください バックエンドAPIとなる 関数の作成 関数の作成 ボタンをクリックします。 関数の設定 未認証の呼び出しを許可します。 関数名などの設定 関数名を任意の名前に今回は function-1 としました リージョンはサーバーの場所で 今回は asia-northeast1 としました URL を覚えておいてください. 今回は下記になりました https://asia-northeast1-hallowed-oven-333004.cloudfunctions.net/function-1 後ほど使います。 API を有効にする 外部からのアクセスを許可するために必要な設定を行います。 API を有効にするボタンをクリックする 有効にするボタンをクリックする ソースコードを用意します。 今回の例では、下記のソースコードとしました main.py import json def hello_world(request): # ここからは定形文です------------------------------------ if request.method == 'OPTIONS': headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST', 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Max-Age': '3600' } return ('', 204, headers) headers = { 'Access-Control-Allow-Origin': '*' } # ここまでは定形文です------------------------------------ request_json = request.get_json() if request_json: A = request_json['A'] B = request_json['B'] result = {"C": A + B } return (json.dumps(result), 200, headers) return f'Hello World!' 説明) 定形文--- となっている範囲は外部からのアクセスを許す場合に必要なソースです request_json = request.get_json() の部分で入力情報を受け取っています。 受け取る入力情報は A という変数と B という変数に格納されることとします。 result = {"C": A + B } ここで A と B の足し算を行い、その答えを Cという名前で格納しています。 デプロイボタンをクリックする ソースコード、関数の設定が完了しましたので関数を作成します。 くるくるを待ちます 権限を追加する 外部からのアクセスを許可するために必要な権限の設定を行います。 [権限] タブを表示します。 右上隅にある [情報パネルを表示] をクリックして、[権限] タブを表示します。 [プリンシパルを追加] をクリックします。 新しいプリンシパル フィールドで、allUsersと入力します。 役割を選択 プルダウン メニューから Cloud Functions > Cloud Functions 起動元 役割を選択します。 保存をクリック 一般公開アクセスを許可をクリック APIサーバーを使ってみる APIサーバーが完成しました!おめでとうございます。 次はこのAPIサーバーを外部から呼び出してみてテストしてみたいと思います。 覚えておいた URL にアクセスしてみます。 簡単なチェック ブラウザのアドレスバーに 覚えておいた URLを入力してください と表示されれば成功です。 これは、ソースコードの if request_json 文 に入らなかった ことを意味しています。 main.py request_json = request.get_json() if request_json: # この if 文 に入らなかった return f'Hello World!' APIサーバーに計算させてみる 今度はAPIサーバーに A と B の足し算をさせてみます。 ここに示すようなソースコードを用意します。 https://colab.research.google.com/drive/1h-GJAU2lBedkLJmzVYSEU7oADmORYv-3?usp=sharing A に 1, B に 5 を設定してサーバーに送ってみます。 APIサーバーから 6 という答えが返ってきました。 以上です あなたの 新たな Webサービス が 一般に公開されました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PyInstaller】Pythonで作成したアプリをexe化して配布する

0. はじめに 前回に引き続き連載ネタ第3弾です。 作成したGUIもPythonの環境構築している人しか使えません。技術者が何かしらのアプリを作成してもいざユーザーに使ってもらう際に、そのユーザーに難解な環境構築をさせるのもひと手間ですね。 そこで今回はPyrhon環境がないユーザーにexe化してアプリ提供する部分を書いていきます。 exe化もいくつかあります(Py2exeとかcx_freeze)が、今回はメジャーなPyInstallerを紹介します。 【第1回】Pythonで簡単に日本語OCR 【第2回】PythonでオリジナルGUIアプリを作成  ※前回記事 【第3回】Pythonで作成したアプリをexe化して配布する ←今回はこの記事 動作環境 OS : Windows10 pro Python : 3.8.3 // Miniconda 4.9.1 PyInstaller : 4.6 ※今最新は4.7になってるらしい PySimpleGUI :4.55.1 (GUI部分) Tesseract : 5.0.0 (OCR部分) pyocr : 0.8 (OCR部分) Pillow : 8.4.0 ※今回はjupyter notebookは使用しまぜん 1. PyInstallerでexe化を行う前に知っておくべきこと まずPyInstallerはpip install pyinstallerで導入可能です。 そしてexe化したい.pyファイルがある階層でコマンドプロンプトを開き、pyinstaller [exe化したいファイル名] --onefileというコマンドを実行すればいいだけです。 ただそれだけなのでこれで記事を終わってもいいんですが、せっかくなのでこの記事ではPyInstallerに関してかなり色々掘り下げていきます 1-1. コマンドオプション 上のコマンドでも「–onefile」というものがついているのがお分かりになると思いますが、PyInstallerにはいくつかオプションが存在します。全部は紹介できないものの、その中からいくつかを紹介します。 オプション 効果 --onefile プログラムを一つにまとめるためのコマンド --noconsole コマンドプロンプトを表示させないコマンド --icon exe化したときのアイコンを指定するコマンド --debug all exe実行時にデバッグするコマンド(--noconsoleは指定しないこと) --clean PyInstallerキャッシュを消去するコマンド --exclude 不要なライブラリを指定するコマンド --key 暗号化に使用されるキーを指定する(難読化)コマンド 1-2. アイコンの作成 コマンドオプションで--iconというものがあるが、これはjpgやpngを指定できず.icoファイルを指定する必要があるので、ここではすでにある画像を使用して簡単に.icoをpythonから作成する。皆さんはもっとかっこいいアイコンを設定してみてください。 ※今回のアイコンは以下(パワポで適当に作成しただけ・・・) (コマンドプロンプトでそのままpythonを起動) >>> from PIL import Image >>> filename = r'OCR.png' #適当な画像を読み込む >>> img = Image.open(filename) >>> img.save('OCR.ico') #.icoで保存する ★あとでこのアイコンをexeの画像として指定する 1-3. 仮想環境の構築 ↑のオプションはさておき、何も考えずにexe化コマンドを実行するとどうなるか・・・ 無茶苦茶重いexeファイルが出来上がる可能性があります。そりゃそうですよね、今使ってる開発環境のpythonのライブラリを全部使えるように盛り込んだexeなので、不要なライブラリとかも入ってる為です。 そこで新規でexe用の仮想環境を立ち上げて、必要なライブラリだけ導入してexeを作成することにする。※今回はcondaの仮想環境ケースを書くが、別にvenvでもなんでもOKである。 コマンドプロンプト #--pyinstallerという名前の仮想環境作成-- conda create -n pyinstaller python=3.8.3 #--作成した仮想環境に切り替える-- conda activate pyinstaller #--以下自作アプリで使用している必要ライブラリをpipで入れる-- pip install PySimpleGUI pip install pillow pip install pyocr pip install pyinstaller 2. 実際に前回までのGUIアプリをexe化する ※参考(前回記事) 2-1.プログラムのPath指定部分を変更する プログラムの中にpathを指定するような箇所がある場合は修正が必要になる。 sample.py import PySimpleGUI as sg from PIL import Image, ImageTk, ImageEnhance import io import os import pyocr #テーマカラーを設定 sg.theme('Purple') #TesseractのPath情報登録 ※ここをFULLpathから変更する """ exeが置かれるpathにTesseractを入れてもらえばOK ※配布側がそのままTesseractと同梱で配布するのも可能(同じような環境のユーザーの場合のみ) """ path = os.getcwd() #exe実行ディレクトリpathを取得するのが重要 TESSERACT_PATH = path + '/Tesseract-OCR' #今回はexeと同じディレクトリに配置させる前提とする TESSDATA_PATH = path + '/Tesseract-OCR/tessdata' #tessdataのpath os.environ["PATH"] += os.pathsep + TESSERACT_PATH os.environ["TESSDATA_PREFIX"] = TESSDATA_PATH """以下全く同じでいいので略。""" 2-2.PyInstallerでexe化 sample.pyの配置されているディレクトリへcdコマンドで移動し、exe化コマンドを打つ。 今回使用するオプションは「onefile、noconsole、clean、icon 」の4つとする。 コマンドプロンプト #--作成した仮想環境に切り替える-- conda activate pyinstaller #--sample.pyのディレクトリ移動-- cd C:\Users\・・・\sample.pyのディレクトリ #--exe化実行-- ※さっき作成した.icoを同じディレクトリに配置する pyinstaller sample.py --onefile --noconsole --clean --icon OCR.ico #--アイコンキャッシュを削除(★これしないとアイコンが反映されないので注意★)-- ie4uinit.exe -show exeは「dist」フォルダの中に出来るため、プログラム内でPath指定しているものを「dist」内に集結させる。※exeのアイコンが変わっていることも確認できる。 このdistフォルダをユーザーに配ればユーザーはpython環境が無くてもアプリを実行できるようになる。※ただし今回のアプリではpyocrを使用する為、以下2-3の対応が別途必要です 参考までに完成イメージGIFは以下のようになります(クドイようですが今回のアプリは2-3の対応しないとOCR実行時にエラーになります) 2-3.onefile+noconsoleの組み合わせ時のエラー対応(ハンドルが無効) 現時点(2021/11/23)で上で述べたexeを作成して実行するとtesseractで以下のようなエラーが発生するはずである。調べた限りだと「onefile」「noconsole」のコマンドオプションを同時に使用し、かつ「subprocessライブラリ」を併用した場合にエラーが起きる模様(以下公式のWiki) noconsoleなのでこのエラー内容を確認するには、try-except文にlogを残すような改造が必要です(本記事では省略) ※ハンドルというのは「何らかのリソースを識別・操作するための識別子」のことです エラー内容 File "pyocr\tesseract.py", line 364, in image_to_string File "pyocr\tesseract.py", line 293, in run_tesseract File "subprocess.py", line 804, in __init__ File "subprocess.py", line 1142, in _get_handles OSError: [WinError 6] ハンドルが無効です。 まずはエラーの原因になっているtesseract.pyを探す必要がある。 仮想環境に入ったままpythonを起動して以下のように確認すれば簡単。 (コマンドプロンプトでそのままpythonを起動) >>> import pyocr >>> pyocr.__file__ 'C:\\Users\\****\\envs\\pyinstaller\\lib\\site-packages\\pyocr\\__init__.py' 指定された場所に行けば以下のように問題となっている「tesseract.py」が見つかるはずである。 以下は修正前と後を記載する。 tesseract.py(修正前) 293 proc = subprocess.Popen(command, cwd=cwd, 294 startupinfo=g_subprocess_startup_info, 295 creationflags=g_creation_flags, 296 stdout=subprocess.PIPE, 297 stderr=subprocess.STDOUT) これを以下のように修正して保存する。 tesseract.py(修正後) 293 proc = subprocess.Popen(command, cwd=cwd, 294 startupinfo=g_subprocess_startup_info, 295 creationflags=g_creation_flags, 296 stdout=subprocess.PIPE, 297 stderr=subprocess.STDOUT, 298 stdin=subprocess.DEVNULL) #これを追加 この修正後にpyinstaller sample.py --onefile --noconsole --clean --icon OCR.icoとやり直せばエラーを出さずに実行可能である。 subprocess.pyのエラー箇所解説(読み飛ばしOK) そもそも以下部分で「if stdin is None:」だとエラーになるようになっており、subprocess.pyにおけるデフォルト設定(init関数)がstdin=Noneなのでエラーになるようである。そこで上述のようにstdin=subprocess.DEVNULLを追加することにより回避させている。 subprocess.py def _get_handles(self, stdin, stdout, stderr): """Construct and return tuple with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ if stdin is None and stdout is None and stderr is None: return (-1, -1, -1, -1, -1, -1) p2cread, p2cwrite = -1, -1 c2pread, c2pwrite = -1, -1 errread, errwrite = -1, -1 if stdin is None: #★★ここでNoneだとエラーになるようになってる★★ p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) if p2cread is None: p2cread, _ = _winapi.CreatePipe(None, 0) p2cread = Handle(p2cread) _winapi.CloseHandle(_) elif stdin == PIPE: p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) elif stdin == DEVNULL: #★★ここに分岐できるように修正する★★ p2cread = msvcrt.get_osfhandle(self._get_devnull()) 3. さいごに Pythonが触れる人間が社内で作ったアプリを紹介したら「それ使いたい!」と言われることも多いと思う。さらに使い勝手がいい場合、勝手に口コミで他部署にも伝番することも少なくない。 そんな時に手順書作成していちいち環境構築をやらせるわけにもいかないので、exe化を覚えておいて損はないと思う。 今回3回に渡って記事連載した流れなんて例えば「これからDX進めるぞ!!」なんて部署では割とありがちな流れを書いたつもりなのでぜひ参考にしていただければと思います。 それでは今回はここまで! 参考リンク集 ・https://github.com/pyinstaller/pyinstaller ・https://pyinstaller.readthedocs.io/en/stable/usage.html ・https://news.mynavi.jp/article/20180131-windows_icon/ ・https://stackoverflow.com/questions/69425010/pyinstaller-error-oserror-winerror-6-the-handle-is-invalid ・https://github.com/pyinstaller/pyinstaller/wiki/Recipe-subprocess ・https://github.com/pyinstaller/pyinstaller/issues/5601
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python初心者がノンプロ研でPython学んでみた vol.2

はじめに こんにちは、な~です。ノンプロ研でPythonを学び始めました。その学びについて学べる第2弾です。 今回学ぶこと 条件分岐 繰り返し 関数 の3本立てです。 条件分岐 プログラムの手続きには3種類あります。 順次:上から順番に処理を実行する 選択;条件によって、処理を選択して実行する 反復:同じ処理を繰り返して行う。条件によってその処理を抜ける ここはPythonでなくとも、他のプログラムでも学ぶところですね。 今回は2個目の分岐処理です。 分岐処理にはifを使います。 条件式は、True・Falseを返す式を書きます。式は特に括弧で囲わなくてもいいです。処理は、インデントをして書きます。GASみたいに{}で囲ったり、VBAのようにEND IFしなくて、インデントで判断するそうです。 Falseのときは、else:とインデントすることで処理ができます。また、条件をいくつかつけたいときはelif文を使います。Switchやcaseのような構文はまだ無いとのことです。 条件式の説明中に論理演算子の説明もありました。Pythonはand・or・notとのことです。 また、ブール値以外の型の判定の説明もありました。 これはデータの存在チェックをするやつ、便利なのに忘れがちです。(私比)Pythonなら使う場面が多そうなので今回こそ憶えたい。 繰り返し 次は手続きの画像の3つ目にありました「繰り返し」です。 まずは、whileによる繰り返しです。 whileの後ろの条件式がTrueの間処理を繰り返します。繰り返す処理はインデントをしておきます。 次はfor文。集合に含まれるデータを順番に取り出しながら処理をします。添字を増やしながらというところでないところがちょいと戸惑いますね。 関数 次は関数! こちらは、関数の定義です。def 関数名()で関数の定義ができます。 returnでも戻り値を指定します。 こちらは呼び出し側、関数名()で呼び出せるとのことです。 ただ、関数の呼び出しと定義は、順番があります。関数を定義した後に呼び出す必要があるそうなので注意が必要そうです。 まとめ 今回は、制御構文と関数について学びました。 Pythonのスペースの置き方に戸惑いがあったので、 pep8-ja こちらのページを読んでみたのでまとめてみました!Python学習中。PEP8って?の学びのまとめ また、いつもの通りに講座中の自分のツイートもまとめしまた! ノンプロ研 Python初心者講座第2回目 byな~
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub Actionsのstepsの重複を減らすためのComposite run steps action

GitHub Actionsを使っていて、並列目的などでジョブを細かく分けたときに依存関係の設定などで記述が重複する箇所が結構発生し、それらの重複をComposite run steps action (Composite actionなどとも表記されるようです)という機能を使って軽減したのでその際の備忘録です。 発生していた問題とやったこと 少し前にPythonの自作ライブラリのLintやテスト・デプロイなどのGitHub Actionsのジョブで依存関係の設定のキャッシュ機能やジョブ分割による並列実行などを対応しました。多くのジョブを走らせても処理時間などは良い感じに気にならない形となりました。 一方でジョブの分割によって依存関係の設定など(Pythonの設定やPythonライブラリのパッケージ設定など)の記述が各ジョブ間で重複している箇所がsteps内で目立ちました。 DRY原則的にもこの辺は重複が多いと結構気になりますし変更する際などに変更漏れなどが発生しそうであまり良くありません。 そこでComposite run steps actionという機能を使ってstepsの重複部分の記述を統一しました。 Composite run steps actionとはどんな機能なのか steps内の一部の処理群を別のYAMLファイルに記述することができます。 元々のワークフローの各ジョブのsteps内の任意の位置でその別ファイルのYAMLのstepsの処理を呼び出すことができます。 別ファイル側で実行されたstepsの処理結果は呼び出し元にも反映されます。つまり依存関係などの設定を別ファイルで設定した際には呼び出し元のジョブ側でもその後その依存関係のものを利用することができます。 別ファイルのstepsへは呼び出し元から引数のように任意のパラメーターを渡すことができます。 この記事を書いている時点では別ファイル側ではifが使えなかったり一部追加の設定などが必要になったりといった制限などがあります。 必要な対応内容の概略 ※それぞれ後の節で詳しく触れます。 切り出したいstepsの処理を記述した別ファイルのYAMLファイルを設置します。 そちらの別ファイルに受け付けるパラメーターの設定やstepsなどを記述します。 呼び出し元のジョブの任意のstepsの箇所でその別ファイルを指定して処理を呼び出します。 別ファイルのYAMLの設置方法 設置する別ファイルにはフォルダ名やファイル名にルールがあるので注意します。 まずは通常のワークフローのYAMLが設置されているリポジトリの.githubフォルダ以下にactionsというフォルダを追加します。そちらのフォルダ内に切り出したい処理の名前を付けたフォルダを更に追加します。 今回は依存関係などの設定を行うためのstepsを切り出したのでsetup_py_dependenciesという名前のフォルダにしました。<リポジトリパス>/.github/actions/setup_py_dependencies/といったようなフォルダ構成になっています。 今後別のstepsの処理を追加で切り出したい場合はsetup_py_dependenciesフォルダと同じ階層に別の名前のフォルダを追加します。 追加したsetup_py_dependenciesフォルダ以下にaction.ymlというファイルを追加します。拡張子は.yamlでも認識してくれるようです。ただしファイル名は別のものにしてしまうと認識してくれなさそう?なので注意します。結果的に今回対応したファイルのパスは<リポジトリパス>/.github/actions/setup_py_dependencies/action.ymlとなっています。 切り出したaction.ymlファイルで最低限必要な記述 最低限name, description, runsが必要になります。descriptionなどは省略するとVS Code上で引っかかっていたので基本的には書く形のようです。また、runs内にはusing: compositeという記述が必要になります。後はruns内にstepsを書いていけば対応ができます。 setup_py_dependencies/action.yml name: Setup the Python dependencies description: This composite run steps require the dependencies cache before running. runs: using: composite steps: - name: Set the Python version ... 渡すパラメーターの設定 呼び出し元からなんらか任意のパラメーターを切り出したYAMLの処理に渡したい場合にはinputsの設定を行う必要があります。以下のようなフォーマットで書きます。 inputs: <パラメーター名>: description: <パラメーターの説明> required: <パラメーターを必須とするかどうかの真偽値> 例えばpython-versionという名前のパラメーターを必須の設定で設定したい場合には以下のように書きます。 setup_py_dependencies/action.yml name: Setup the Python dependencies description: This composite run steps require the dependencies cache before running. inputs: python-version: description: Python version to use. required: true ... こうすることで、steps内で${{ inputs.<パラメーター名> }}と記述することで渡されたパラメーターを参照することができます。先ほどのpython-versionというパラメーターの場合は${{ inputs.python-version }}といったようになります。 記述例 : setup_py_dependencies/action.yml name: Setup the Python dependencies description: This composite run steps require the dependencies cache before running. inputs: python-version: description: Python version to use. required: true runs: using: composite steps: - name: Set the Python version uses: actions/setup-python@v2 with: python-version: ${{ inputs.python-version }} ... 呼び出し元のsteps内で追加したYAMLの内容を呼び出す方法 呼び出し元となるワークフローの任意のsteps内で追加した別ファイルのYAMLのものを呼び出したい場合にはusesに./.github/actions/<対象の処理のフォルダ名>と指定します。今回はsetup_py_dependenciesというフォルダを追加しているのでuses: ./.github/actions/setup_py_dependenciesといった記述になります。action.ymlといったファイル名部分は不要です。また、先頭に./の記述が無いと動かなかったりする?ようなので注意してください。 記述例 : ... RunFlake8: needs: CreateCache runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Set the Python version environment variable run: echo "python-version=3.6.15" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v2 - name: Setup the Python dependencies uses: ./.github/actions/setup_py_dependencies - name: Set the flake8 alias run: alias flake8=/opt/hostedtoolcache/Python/${{ env.python-version }}/x64/bin/flake8 - name: Run the flake8 command run: python run_flake8.py ... パラメーターを指定して呼び出しを行う 呼び出しの際にパラメーターを指定したい場合にはwithを使って以下のように書きます。 ... with: <パラメーター名>: <渡すパラメーター> ... 今回はpython-versionというパラメーターを必須として切り出したYAMLファイル側で設定しているのでそちらを指定します。 記述例 : ... - name: Setup the Python dependencies uses: ./.github/actions/setup_py_dependencies with: python-version: ${{ env.python-version }} ... runを使うときには追加でshellの指定が必要 注意点となりますが、切り出したYAML内でのrunでのコマンドの実行にはshellの設定が必要なようです。shell: bashといったような記述を追加しておく必要があります。 記述例 : - name: Set the site package path to the environment variables run: python -c "import site; print(f'site-packages-path={site.getsitepackages()[0]}')" >> $GITHUB_ENV shell: bash 記述が無いとVS Code上などでエラーが表示されます。試していないですがそのままGitHub上に反映しても弾かれるかもしれません。 今回の記事の内容を反映したYAML 参考用に今回のものを反映したYAMLファイルのリンクを貼っておきます(日々更新しているため記事内容と最新のYAMLファイルの差分などはご容赦ください)。 メインのワークフローのYAML: Composite run steps actionとして別ファイルに切り出したYAML: 参考サイトまとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

APIを利用した天気予報ハンズオン

 はじめに 今回は『APIって、どんな動きをしているの?』と中の動きがサッパリだったので、構築を交えながらAPIについて学習していきたいと思いたち、下記参考資料を読みながら手を動かしてAPIを構築しました  参考資料 参考リンクにはAPIのHTTPリクエスト・レスポンスなどの記述の方法などの説明があり、API分からなかったコチラを読めばOKかと どちらもハンズオンを進める中で大変理解が捗りました APIの仕組みが分かる・使いこなせる人材になれる記事(Pythonコード付き) HTTPとPOSTとGET  構成図  ハンズオン 0:「Yahoo! JAPAN Webサービス」でアプリケーションIDを発行する 今回利用するAPIはYahoo!JAPANの「気象情報API」と「Yahoo!ジオコーダAPI」を使用するために「Yahoo!JAPAN ID」と「アプリケーションID」を取得していきます 「アプリケーションID」のアプリケーション名は後からでも変更可能なので拘らなくても構いません。 アプリケーションの管理から自分のアプリケーションIDを確認する 1:ブラウザから天気予報データを取得する 気象情報APIのページへ移動する 気象情報APIを利用するためには『RESTリクエスト(GET)』で、エンドポイント(URL)の中に情報を含める必要があることが分かります。 そのためにURLに値を「?」で繋ぎ、それ以降の条件を「&」で繋いでいきますが日本語だと難しく感じますので、サンプルリクエストURLで確認していきます。 上記のように色分けをすると、「?」以降の値を「&」で繋いでいるのが確認できるかと思います。 ちなみにサンプルリクエストにある「あなたのアプリケーションID」の<>部分は不要のため削除しています。情報を取得するためブラウザにURLをCOPYして確認していきます。 サンプルレスポンスにもあるような値が返信されていれば、無事にデータが取得できていることが分かります。 ちなみにMacのターミナルからでもcurlコマンドを利用することで確認することができますが、『zsh: no matches found:』が出た場合はURLをダブルクォーテーションかシングルクォーテーションで囲って対応ください tetutetu214@mbp rainfall % curl "https://map.yahooapis.jp/weather/V1/place?coordinates=139.732293,35.663613&appid=XXXXX" 2:「Yahoo!ジオコーダAPI」で取得したい地点の『coordinates』データを取得する こちらも手順としては、気象情報APIと同様にエンドポイント(URL)の中に情報を含めていきます。YAHOO!にあるサンプルを利用してもいいですが、今回は自分が取得したい地点の情報が表示されるようにしていきます。確認はブラウザでもターミナルでも構いません。 ・ブラウザのURLの場合(地名は任意) https://map.yahooapis.jp/geocode/V1/geoCoder?appid=XXXXXXXXXX&query=埼玉県川口市 ・ターミナルの場合のコマンド tetutetu214@mbp rainfall % curl "https://map.yahooapis.jp/geocode/V1/geoCoder?appid=XXXXXXXXXX&query=埼玉県川口市" ブラウザの場合、赤枠の値=取得したい地点の値を確認することができる 3:上記の内容を踏まえてコードを記述していく(最後に、まとめたコードの記述あり) # ライブラリのインポート import requests import json #天気予報を出したい都市を入力(input()にすれば取得したい地点のGeoコード取得可能) #AREA = input() AREA = "埼玉県川口市" print(AREA + "の天気を出力します") ・AREA = input()をコメントアウトするれば、『埼玉県川口』など固定した場所ではなく、入力した箇所の天気予報(Yahoo!ジオコーダの地点の取得)をすることができる #変数 api_key = "XXXXXXXXXX" Weather_url = "https://map.yahooapis.jp/weather/V1/place?" geo_url = "https://map.yahooapis.jp/geocode/V1/geoCoder?" parm1 = "coordinates=" parm2 = "&output=json" parm3 = "&query=" ・#変数とあるparm2 = "&output=json"部分は、APIの説明にもあったリクエストパラメータ一覧にもあるアウトプットされる出力形式を指定することができるもので、取得した情報を利用するために記述 # Yahoo!ジオコーダによる取得したい場所の情報をURLにする geo = geo_url + api_key + parm2 + parm3 + AREA # 取得した情報を整形する geo_info = requests.get(geo) geo_obj = json.loads(geo_info.text) # Geometryから座標情報のみ取得する parm = geo_obj["Feature"][0]["Geometry"]["Coordinates"] ・ブラウザでも確認しているかもしれませんが、下記範囲の情報を取得する # 気象情報APIに渡すための情報(「?」の後ろに「&」で条件を繋いでいく)をURLにする url = Weather_url + parm1 + parm + parm2 + api_key # 取得した情報を整形する url_info = requests.get(url) obj = json.loads(url_info.text) ・気象情報APIに『parm』で取得地点の値を渡して、こちらもjson形式で情報(地点の10分ごとの天気)を取得する # 取得した気象情報7回分のうち10分後のものから表示させる for i in range(1,7): type=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Type'] date=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Date'] rainfall=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Rainfall'] print("予想される天気") print(str(i*10) + "分後") print("降水強度は" + str(rainfall)) 全部で今の時刻と60分後なので7回(今+6回)情報が取得されますが、予報なので今の情報は除いてfor文を利用して情報を表示させるようにしています ブラウザでは赤枠で囲った部分を取得する記述となっています ちなみにレスポンスの言葉の意味は気象情報APIのレスポンスフィールドという部分に説明があります まとめたコードの記述(情報を取得表示を優先しています) # ライブラリのインポート import requests import json #天気予報を出したい都市を入力(input()にすれば取得したい地点のGeoコード取得可能) #AREA = input() AREA = "埼玉県川口市" print(AREA + "の天気を出力します") #変数 api_key = "&appid=dj00aiZpPWdGRDA1MkQ0M2VHWSZzPWNvbnN1bWVyc2VjcmV0Jng9ZWQ-" Weather_url = "https://map.yahooapis.jp/weather/V1/place?" geo_url = "https://map.yahooapis.jp/geocode/V1/geoCoder?" parm1 = "coordinates=" parm2 = "&output=json" parm3 = "&query=" # Yahoo!ジオコーダによる取得したい場所の情報をURLにする geo = geo_url + api_key + parm2 + parm3 + AREA # 取得した情報を整形する geo_info = requests.get(geo) geo_obj = json.loads(geo_info.text) # Geometryから座標情報のみ取得する parm = geo_obj["Feature"][0]["Geometry"]["Coordinates"] # 気象情報APIに渡すための情報(「?」の後ろに「&」で条件を繋いでいく)をURLにする url = Weather_url + parm1 + parm + parm2 + api_key # 取得した情報を整形する url_info = requests.get(url) obj = json.loads(url_info.text) # 取得した気象情報7回分のうち10分後のものから表示させる for i in range(1,7): type=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Type'] date=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Date'] rainfall=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Rainfall'] print("予想される天気") print(str(i*10) + "分後") print("降水強度は" + str(rainfall)) 上記をターミナルで動かした際の表示 tetutetu214@mbp rainfall % python lambda_function.py 埼玉県川口市の天気を出力します 予想される天気 10分後 降水強度は0.0 予想される天気 20分後 降水強度は0.0 予想される天気 30分後 降水強度は0.0 予想される天気 40分後 降水強度は0.0 予想される天気 50分後 降水強度は0.0 予想される天気 60分後 降水強度は0.0  さいごに APIの知識も乏しく、なんとなくURLに書けばいいんでしょ程度の知識から、ある程度の書き方を理解して書けるようになったので、また違うAPIを利用しながら理解をより深めていきたいと勤労感謝の日に思うのでした。 次はこちらの内容をLINE APIでAWS APIGateway〜Lambdaで連動して起動できるようにしてみたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YahooAPIを利用した天気予報ハンズオン

 はじめに 今回は『APIって、どんな動きをしているの?』と中の動きがサッパリだったので、構築を交えながらAPIについて学習していきたいと思いたち、下記参考資料を読みながら手を動かしてAPIを構築しました  参考資料 参考リンクにはAPIのHTTPリクエスト・レスポンスなどの記述の方法などの説明があり、API分からなかったコチラを読めばOKかと どちらもハンズオンを進める中で大変理解が捗りました APIの仕組みが分かる・使いこなせる人材になれる記事(Pythonコード付き) HTTPとPOSTとGET  構成図  ハンズオン 0:「Yahoo! JAPAN Webサービス」でアプリケーションIDを発行する 今回利用するAPIはYahoo!JAPANの「気象情報API」と「Yahoo!ジオコーダAPI」を使用するために「Yahoo!JAPAN ID」と「アプリケーションID」を取得していきます 「アプリケーションID」のアプリケーション名は後からでも変更可能なので拘らなくても構いません。 アプリケーションの管理から自分のアプリケーションIDを確認する 1:ブラウザから天気予報データを取得する 気象情報APIのページへ移動する 気象情報APIを利用するためには『RESTリクエスト(GET)』で、エンドポイント(URL)の中に情報を含める必要があることが分かります。 そのためにURLに値を「?」で繋ぎ、それ以降の条件を「&」で繋いでいきますが日本語だと難しく感じますので、サンプルリクエストURLで確認していきます。 上記のように色分けをすると、「?」以降の値を「&」で繋いでいるのが確認できるかと思います。 ちなみにサンプルリクエストにある「あなたのアプリケーションID」の<>部分は不要のため削除しています。情報を取得するためブラウザにURLをCOPYして確認していきます。 サンプルレスポンスにもあるような値が返信されていれば、無事にデータが取得できていることが分かります。 ちなみにMacのターミナルからでもcurlコマンドを利用することで確認することができますが、『zsh: no matches found:』が出た場合はURLをダブルクォーテーションかシングルクォーテーションで囲って対応ください tetutetu214@mbp rainfall % curl "https://map.yahooapis.jp/weather/V1/place?coordinates=139.732293,35.663613&appid=XXXXX" 2:「Yahoo!ジオコーダAPI」で取得したい地点の『coordinates』データを取得する こちらも手順としては、気象情報APIと同様にエンドポイント(URL)の中に情報を含めていきます。YAHOO!にあるサンプルを利用してもいいですが、今回は自分が取得したい地点の情報が表示されるようにしていきます。確認はブラウザでもターミナルでも構いません。 ・ブラウザのURLの場合(地名は任意) https://map.yahooapis.jp/geocode/V1/geoCoder?appid=XXXXXXXXXX&query=埼玉県川口市 ・ターミナルの場合のコマンド tetutetu214@mbp rainfall % curl "https://map.yahooapis.jp/geocode/V1/geoCoder?appid=XXXXXXXXXX&query=埼玉県川口市" ブラウザの場合、赤枠の値=取得したい地点の値を確認することができる 3:上記の内容を踏まえてコードを記述していく(最後に、まとめたコードの記述あり) # ライブラリのインポート import requests import json #天気予報を出したい都市を入力(input()にすれば取得したい地点のGeoコード取得可能) #AREA = input() AREA = "埼玉県川口市" print(AREA + "の天気を出力します") ・AREA = input()をコメントアウトするれば、『埼玉県川口』など固定した場所ではなく、入力した箇所の天気予報(Yahoo!ジオコーダの地点の取得)をすることができる #変数 api_key = "XXXXXXXXXX" Weather_url = "https://map.yahooapis.jp/weather/V1/place?" geo_url = "https://map.yahooapis.jp/geocode/V1/geoCoder?" parm1 = "coordinates=" parm2 = "&output=json" parm3 = "&query=" ・#変数とあるparm2 = "&output=json"部分は、APIの説明にもあったリクエストパラメータ一覧にもあるアウトプットされる出力形式を指定することができるもので、取得した情報を利用するために記述 # Yahoo!ジオコーダによる取得したい場所の情報をURLにする geo = geo_url + api_key + parm2 + parm3 + AREA # 取得した情報を整形する geo_info = requests.get(geo) geo_obj = json.loads(geo_info.text) # Geometryから座標情報のみ取得する parm = geo_obj["Feature"][0]["Geometry"]["Coordinates"] ・ブラウザでも確認しているかもしれませんが、下記範囲の情報を取得する # 気象情報APIに渡すための情報(「?」の後ろに「&」で条件を繋いでいく)をURLにする url = Weather_url + parm1 + parm + parm2 + api_key # 取得した情報を整形する url_info = requests.get(url) obj = json.loads(url_info.text) ・気象情報APIに『parm』で取得地点の値を渡して、こちらもjson形式で情報(地点の10分ごとの天気)を取得する # 取得した気象情報7回分のうち10分後のものから表示させる for i in range(1,7): type=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Type'] date=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Date'] rainfall=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Rainfall'] print("予想される天気") print(str(i*10) + "分後") print("降水強度は" + str(rainfall)) 全部で今の時刻と60分後なので7回(今+6回)情報が取得されますが、予報なので今の情報は除いてfor文を利用して情報を表示させるようにしています ブラウザでは赤枠で囲った部分を取得する記述となっています ちなみにレスポンスの言葉の意味は気象情報APIのレスポンスフィールドという部分に説明があります まとめたコードの記述(情報を取得表示を優先しています) # ライブラリのインポート import requests import json #天気予報を出したい都市を入力(input()にすれば取得したい地点のGeoコード取得可能) #AREA = input() AREA = "埼玉県川口市" print(AREA + "の天気を出力します") #変数 api_key = "&appid=XXXXXXXXXXX" Weather_url = "https://map.yahooapis.jp/weather/V1/place?" geo_url = "https://map.yahooapis.jp/geocode/V1/geoCoder?" parm1 = "coordinates=" parm2 = "&output=json" parm3 = "&query=" # Yahoo!ジオコーダによる取得したい場所の情報をURLにする geo = geo_url + api_key + parm2 + parm3 + AREA # 取得した情報を整形する geo_info = requests.get(geo) geo_obj = json.loads(geo_info.text) # Geometryから座標情報のみ取得する parm = geo_obj["Feature"][0]["Geometry"]["Coordinates"] # 気象情報APIに渡すための情報(「?」の後ろに「&」で条件を繋いでいく)をURLにする url = Weather_url + parm1 + parm + parm2 + api_key # 取得した情報を整形する url_info = requests.get(url) obj = json.loads(url_info.text) # 取得した気象情報7回分のうち10分後のものから表示させる for i in range(1,7): type=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Type'] date=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Date'] rainfall=obj['Feature'][0]['Property']['WeatherList']['Weather'][i]['Rainfall'] print("予想される天気") print(str(i*10) + "分後") print("降水強度は" + str(rainfall)) 上記をターミナルで動かした際の表示 tetutetu214@mbp rainfall % python lambda_function.py 埼玉県川口市の天気を出力します 予想される天気 10分後 降水強度は0.0 予想される天気 20分後 降水強度は0.0 予想される天気 30分後 降水強度は0.0 予想される天気 40分後 降水強度は0.0 予想される天気 50分後 降水強度は0.0 予想される天気 60分後 降水強度は0.0  さいごに APIの知識も乏しく、なんとなくURLに書けばいいんでしょ程度の知識から、ある程度の書き方を理解して書けるようになったので、また違うAPIを利用しながら理解をより深めていきたいと勤労感謝の日に思うのでした。 次はこちらの内容をLINE APIでAWS APIGateway〜Lambdaで連動して起動できるようにしてみたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

私が最近チョコチョコっとつくったSlackアプリのソースコードを全文掲載 (2021/12/11)

2021/12/11(土)の回です。 はじめに Slackを楽しんでいますか 「Bolt 入門ガイド(Bolt for Python)」わかりやすい! です。 ここの通りにやることで例の完全理解1に容易に到達できます。 SDKは他にBolt for JavaScript、Bolt for Javaとありまして、私はなんとな〜くBolt for Pythonに飛びつきました。 完全理解したあとに私が作ってイゴかしてみた2もののソースコードを全文掲載します。 前提と言う名の言い訳とみなさまへのお願い ひとつ言い訳をしておきます。 私はPythonで対価をいただいたことはなく、なんとなく雰囲気で書いています。 ソースコードの改善点などありましたら、ぜひ教えてください。 「優しく教えてください」なんていいません。 罵詈雑言、なんでもです。 教えてくださる方のエネルギーとお時間を頂戴するわけですし、私としては、信用に足りうる記事を公開しておくことを第一義としたいとおもっています。 教えてくださることは本当にありがたいことです。 コメント等をいただけましたら記事を改善アップデートいたします。 忌憚のないご批正を賜り得ば大幸の至りでございます。 ① あるメッセージ配下のスレッドに :ok: 等の良リアクションをつける スラッシュコマンドをトリガにしています 例: /thread-reaction https://hogehoge.slack.com/archives/CKXQHOGEHOGE/p1637107216110100 このスラッシュコマンドを作る前までは、あるスレッド配下の書き込みに対してみたよ的な意味で や を手動で平日毎日10個くらいつけることを3年間くらい続けて参りました 特に議論が巻き起こる可能性があるスレッドではなく日々の予定を書き込んでみんなに共有する目的のスレッドです ちゃんと全部に目を通して、そのうえでリアクションボタンだけスラッシュコマンドで済ますことにしています せっかく投稿をしたのなら、なんらかの反応が欲しいものではないでしょうか3 投稿に対して誰が絵文字リアクションを付けたのかは気にせず、とにかく一個でも絵文字リアクションがついていたらアプリでは追加しないという簡単な仕様にしています スラッシュコマンドを発動したあとに、遅れて書き込みする人がいて、そうしたときにこの判定を入れなずにコマンドを実行すると複数個の絵文字がついちゃうメッセージができてしまいますのでそれを避ける意図です ソースコード app.py import os import random from functools import reduce from slack_bolt import App from slack_bolt.adapter.socket_mode import SocketModeHandler # ボットトークンとソケットモードハンドラーを使ってアプリを初期化します app = App(token=os.environ.get("SLACK_BOT_TOKEN")) @app.command("/thread-reaction") def handle_some_command(ack, body, logger, client): ack() logger.info(body) text = body['text'] parent_ts = text.split('/')[-1][1:][:10] + '.' + text.split('/')[-1][1:][10:] channel_id = body['channel_id'] replies = app.client.conversations_replies( channel=channel_id, ts=parent_ts, limit=1000 ) icons = ['thumbsup', 'tada', 'rocket', 'ok'] messages = replies['messages'] list_of_ts = list(reduce(lambda acc, m: acc if 'reactions' in m else acc + [m['ts']], messages, [])) for ts in list_of_ts: if ts == parent_ts: continue try: client.reactions_add( channel=channel_id, name=random.choice(icons), timestamp=ts) except: pass # アプリを起動します if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() Slackアプリの設定 他にも設定したところがあるかもしれません。 はっきり覚えているところだけ書いておきます。 OAuth & Permissions Slash Commands /thread-reaction ② とにかくメッセージが書き込みがあったら良リアクションをつける 元気ですか! 元気があれば何でもできる! 元気、勢いをつけます ウザいと感じる人はいるとおもいます 元気がないチャンネル、これから勢いをつけないといけないチャンネルの賑やかしです いや、マヂ元気がなかったのです、チャンネルに 締め切りに追われていて、チャンネルに勢いが必要だと感じたわけです(勝手に) それで朝、出勤前に突貫でつくって、さくらインターネットさんのHacobuneにデプロイしてイゴかすところまでを15分くらいで仕上げました もちろん、Bolt 入門ガイド(Bolt for Python)は何周もやったし、HacobuneへのデプロイとDockerの知識はそれなりにはありまして、その瞬間における15分の前に費やした時間はけっこうな長さ ーー 私の人生があります 15分は単なるひとつの点にすぎません 本当に15分という時間だけですべてをやってのける人はいるとおもいます それを私ができないだけのことです 15分の前に膨大な時間が私には必要ですが、本当に全部15分だけでドキュメントを読み込んでやりきってしまう人種がいるのだとおもいます その才能・センスの差が、現実世界では資本主義のいまの日本では収入の差になって現れているのだとおもいます 世間の有り様の一端を詳らかにしたことは一旦置いておいて話を戻します 元気ですか! 元気があれば何でもできる! ソースコード app.py import os import random from slack_bolt import App from slack_bolt.adapter.socket_mode import SocketModeHandler # ボットトークンとソケットモードハンドラーを使ってアプリを初期化します app = App(token=os.environ.get("SLACK_BOT_TOKEN")) @app.event("message") def handle_message_events(body, logger, client): logger.info(body) ts = body['event']['ts'] channel = body['event']['channel'] icons = ['thumbsup', 'tada', 'rocket'] client.reactions_add( channel=channel, name=random.choice(icons), timestamp=ts) # アプリを起動します if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() Slackアプリの設定 他にも設定が必要なところがあるかもしれません。 はっきり覚えているところだけ書いておきます。 Wrapping up Bolt for Pythonを使って、簡単に素早くSlackアプリを作ってイゴかしていくことで、Slackを便利にもっともっと楽しめます。 「Bolt 入門ガイド(Bolt for Python)」がわかりやすいです。 まだの方、はじめてみようという方は、まずはこのガイドから取り組まれることをオススメします。 Enjoy Slack ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡ 元気ですか! 元気があれば何でもできる! 明日は、@MintoAoyama さんの「Slackの投稿、たくさんあって見逃してしまう…」を防げるかもしれない "ピックアップチャンネル" "ストックスレッド" (と読んでるハック)」です。 楽しみです!!! これぜひ欲しいものかも!!! https://twitter.com/ito_yusaku/status/1042604780718157824 ↩ 「動かしてみた」の意。おそらく西日本の方言、たぶん。NervesJPではおなじみ。少し古いオートレースの映像ですが、実況アナウンサーが「針3イゴきます」とはっきり言っています。https://autorace.jp/netstadium/SearchMovie/Movie/iizuka?date=2017-01-04&p=5&race_number=11&pg= ↩ 大時計の針のこと。針がイゴいてある地点まで到達すると選手はスタートを切って良い発走の合図。針がイゴきはじめると(おそらく)選手は緊張するし、スタートはその後のレース展開に大きく影響するので、車券を握りしめている観客たちがもっとも緊張する瞬間であるため、先の尖った鋭いものを連想させる針は緊張の暗喩としても言い得て妙。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bolt 入門ガイド(Bolt入門ガイド)をWindowsで進めるときの読み替え (2021/12/10)

2021/12/10(金)の回です。 はじめに Bolt入門ガイドはとてもよくできたわかりやすいガイドです。 このガイドをそのままやってみることで、Slackアプリを作る基礎を学べます。 Windowsで進めるときに少し読み替えが必要でしたのでそのことを記事にしておきます。 前提という名の言い訳 この記事を信用しないでください。 2021/11/23の情報です。 私はPythonの経験は乏しいです。 対価をもらう形での業務(仕事)で書いたことはありません。 雰囲気で使っているし、書いています。 またWindowsは普段の開発では使っておらず、「まあ、公式からPythonのインストーラをダウンロードしてインストールしておけば間違いではないだろう」くらいのノリでWindowsを使っています。 諸事情により、Windows上でPythonを動かしたいことがありまして、その際に試行錯誤してたどり着いたガイドを一直線で一周できる方法を紹介しておきます。 できるだけ正確な情報を書きたいとはおもっていますが、力量不足は否めませんので嘘を含んでいる場合があります。 そのためこの記事を信用しないでください。 ではなにのために書いているかというと、「きっと私と同じことでつまる人がいるだろう」とおもうからです。 その人の時間の節約をしてあげたいと思っています。 参考にしていただいて、ご自身の判断で最適解を見つけてください。 もし、「こうすればいいよ」という情報をお持ちの方はぜひ教えてください。 「優しく教えてください」なんていいません。 罵詈雑言、なんでもです。 信用に足る記事を書くことを私は第一義としたいとおもっています。 記事をアップデートします。 また、ガイド自体はGitHubに公開されているものですので、Issuesたてて、プルリク送ったらいいじゃん! というご意見はごもっともでもですし、そうしたい思いはあるにはありますが、それをするには英語版のほうから見直しが必要で、私のいまの英語力では書けない1のと、Windowsでの開発経験に乏しいので私の環境依存の可能性を若干ながら捨てきれていないことから、プルリクを出すという行動はいまのところ考えておりません。 typoレベルで修正に自信がある件についてはプルリクを出してマージしていただいことがあります。 https://github.com/slackapi/bolt-python/pull/507 https://github.com/slackapi/bolt-python/pull/508 https://github.com/slackapi/bolt-python/pull/519 四の五の言い訳が長くなりました 本題 環境 まず私が使った環境を記します。 Windows Home editionです。 Python からインストールしました。 Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)] on win32 ここの手順がこの記事のハイライトかもしれません。 Microsoft Storeからもインストールできるようで、そっちからやるとまた結果は違うものになるかもしれません。 ターミナル Windows PowerShell (昔ながらのコマンドプロンプトとの違いはわかっていません) なんとなくデスクトップで右クリックして、「Windows ターミナルで開く」を選ぶと、Windows PowerShellが立ち上がりました。 新しいもののほうがいいだろうとおもってこちらをそのまま使いました。 読み替え コマンドをなぞっていきます。 のWindowsタブの内容を参考にしています。 > mkdir first-bolt-app > cd first-bolt-app 読み替え不要です。 > python -m venv .venv ガイド記載のpython3 -m venv .venvを読み替え。 Pythonの事情に疎いのでpython3 -> pythonを読み替えと呼ぶべきなのかどうか不明です。 > .venv\Scripts\activate source .venv/bin/activateのsourceなんて知らないといわれるので読み替えが必要。 ただし、以下のエラーがでます。 .venv\Scripts\activate : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Use rs\torif\Documents\first-bolt-app\.venv\Scripts\Activate.ps1 を読み込むことができません。詳細に ついては、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照し てください。 発生場所 行:1 文字:1 + .venv\Scripts\activate + ~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : セキュリティ エラー: (: ) []、PSSecurityException + FullyQualifiedErrorId : UnauthorizedAccess about_Execution_Policiesを読んでみてください。 以下、よく内容を確かめてから実行してください。 > Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process これを事前にしておくことで、さきほどのエラーは突破できます。 ググってでてきた記事の内容をそのまま私は実行しましたが、もともとセキュリティで弾かれていたことの設定を変えているのでよく内容を理解して実行してください。(内容を理解せずに実行した私が言うのは変な話ですが) これをyと元気よく答えたあとにもう一度、.venv\Scripts\activateを実行すると仮想環境がアクティベートになります。 > $env:SLACK_BOT_TOKEN="xoxb-<ボットトークン>" > $env:SLACK_APP_TOKEN="<アプリレベルトークン>" 環境変数の設定は、ガイドで紹介されているリンク先のStackExchangeのPostを参考に設定します。 ※Windows PowerShellでの設定方法をここでは書いています。 > pip install slack_bolt 読み替え不要です。 app.pyはそのまま写してください。 実行は > python app.py です。 Wrapping up 私のWindows環境では、上記のようにやると「Bolt入門ガイド」を一通り実施することができました。 一例として参考にしていただければ幸いです。 こうやれば読み替え不要(そもそも全体を通じて、「読み替え」と表現するのが適切なのかどうかPython界の流儀に疎くわかっていません)だよという情報をお持ちの方はぜひお知らせください。 この記事を信用しないでください。 Enjoy slack War is over if you want it いまは英語で書く自信がないと言っているだけで、未来永劫書けないと言っているわけではありません。来年のことを言うと鬼に笑われますが、来年のいまごろは英語がペラペラです。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Databricksで利用できるデータストアにおけるデータへの権限付与機能(認可、アクセス制御)に関する検証

概要 Databricksで利用できるデータストアにおけるデータへの権限付与機能(認可、アクセス制御)を確認するために実施した、データ オブジェクトの特権と動的ビュー関数(行レベルセキュリティ、列レベルセキュリティ)に関する検証内容を共有します。 検証内容としては、下記の記事をベースにしております。 データストア(データ分析基盤)におけるデータへの権限付与(認可、アクセス制御)に関する指針の検討 - Qiita 詳細は下記のGithub pagesのページをご確認ください。 コードを実行したい方は、下記のdbcファイルを取り込んでください。 https://github.com/manabian-/databricks_tecks_for_qiita/blob/main/tecks/data_object_privileges_test/data_object_privileges_test.dbc 検証方法 Databricks管理者権限を保持させたユーザーとデータ参照ユーザーの2つのユーザーを用意して、DDL実行等のメタデータのデプロイのノートブック(01_configure_data_object_privileges)を前者のユーザーで、データ参照の検証を後者のユーザーで実行。 01_configure_data_object_privileges 02_configure_data_object_privileges 下記を事前に実施する必要があります。 ワークスペースレベルでのテーブルアクセス制御を有効にすること クラスター作成権限を拒否して、テーブルアクセス制御が有効なクラスターのみ利用するように設定すること SELECT ON ANY FILE権限を拒否すること 検証結果 下記のデータストアにおけるデータへの権限付与が可能でした。DatabricksをデータレイクやDWHとして利用する際には、データへのセキュリティ要件を満たすことできそうです。 分類 権限付与項目 可否 実施方法 データストアシステムへの権限付与 SQL命令(DDL、DCL)相当に対する実行の権限付与 〇 CREATE権限、あるいは、OWNER権限を付与する。 データストアシステムへの権限付与 オブジェクト(スキーマ)一覧の閲覧の権限付与 〇 USAGE権限とSELECT権限を付与する。 データストアシステムへの権限付与 データ参照の権限付与 〇 USAGE権限とSELECT権限を付与する。 データストアシステムへの権限付与 データ挿入・更新・削除の権限付与 〇 MODIFY権限を付与する。 データへの権限付与 ルールベースによる行レベルセキュリティ 〇 動的ビュー関数を利用。 データへの権限付与 データ権限(認可)テーブルを利用した行レベルセキュリティ 〇 動的ビュー関数を利用。 データへの権限付与 列への参照権限付与による列レベルセキュリティ × データへの権限付与 動的データマスキングによる列レベルセキュリティ(ルールベース) 〇 動的ビュー関数を利用。 データへの権限付与 動的データマスキングによる列レベルセキュリティ(データ権限(認可)テーブル) 〇 動的ビュー関数を利用。 下記の実行を許容する場合には、USAGE権限とSELECT権限だけでなく、READ_METADATA権限も許可してもよさそうです。 EXPLAIN DESCRIBE TABLE SHOW CREATE TABLE 参考リンク ワークスペースのテーブル アクセス制御を有効にする - Azure Databricks - Workspace | Microsoft Docs テーブルのアクセス制御 - Azure Databricks - Workspace | Microsoft Docs クラスターのテーブルアクセス制御を有効にする-Azure Databricks | Microsoft Docs データオブジェクトの特権-Azure Databricks | Microsoft Docs GRANT-Azure Databricks - Workspace | Microsoft Docs DENY-Azure Databricks | Microsoft Docs REVOKE - Azure Databricks | Microsoft Docs SHOW GRANT - Workspace | Microsoft Docs
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

困ったことと解決したことメモ_20211122

困ったこと エポック時間をタイムスタンプ形式に変更したい 解決方法 sample.py In [23]: df.head() Out[23]: date price 0 1349720105 12.08 1 1349806505 12.35 2 1349892905 12.15 3 1349979305 12.19 4 1350065705 12.15 In [25]: df['date'] = pd.to_datetime(df['date'],unit='s') In [26]: df.head() Out[26]: date price 0 2012-10-08 18:15:05 12.08 1 2012-10-09 18:15:05 12.35 2 2012-10-10 18:15:05 12.15 3 2012-10-11 18:15:05 12.19 4 2012-10-12 18:15:05 12.15 In [27]: df.dtypes Out[27]: date datetime64[ns] price float64 dtype: object 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

量子コンピュータとジャンケンする

$$ \def\bra#1{\mathinner{\left\langle{#1}\right|}} \def\ket#1{\mathinner{\left|{#1}\right\rangle}} \def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}} $$ この記事はフューチャーAdvent Calender 2021 19日目の記事です。 ■ はじめに こんにちは、突然ですが量子コンピュータってご存じでしょうか? ニュースでたまに耳にしますが、SFチックな響きがあってロマンを感じますよね! 本記事では話題の量子コンピュータをつかってWebアプリケーションを作ってみます。 先端テクノロジーを使ってアプリを作るって考えただけでもワクワクしますね! それでは早速本題に移ります。 ■ 作成したもの 今回作成するのはジャンケンアプリです。 その名も「janQen」。 量子コンピュータに乱数を生成させて、ひたすらジャンケンするというアプリです。 Herokuにデモアプリをデプロイしているため、まずは一度ジャンケンしてみてください。 (※1デモアプリではqasm_simulatorを使用しています。厳密には量子コンピュータではないです) (※2アプリケーションの特性上レスポンスが非常に遅いです。ジョブの実行には1~2分の時間がかかる場合もあります。ボタンの連打は控えるようにお願いします。 ) リンクを踏むと以下の画面が立ちあがります。 ユーザーは「グー」、「チョキ」、「パー」のいずれかを選択します。 試しにグーを押してみましょう。 画面が切り替わって、、、 ジャンケンに勝ちました! 上にスクロールすると、、 なにやら棒グラフが表示されていますね  このグラフが量子コンピュータの演算結果です。こちらのグラフについては後程説明します。 ■ システム構成 janQenのシステム構成は以下の様にしました。 フロントエンド Vue.js Vuetify バックエンド Flask Flask-RESTful Qiskit フロントエンドとバックエンドの通信にはaxiosを使用し、アプリケーションのホスティングはHerokuを採用しました。どの技術セットも初めて触りましたが以下参考記事のおかげで爆速でデモアプリを構築することができました。 感謝!! 基本的なアプリケーションの作成方法は上記の記事を参考にしてください。 本記事ではバックエンドの量子コンピュータによる乱数生成に注目して説明します。 ■ Qiskit QiskitとはIBM社が開発を進めている量子計算パッケージです。 Pythonで記述されており、IBM社が提供するクラウド量子コンピューティングサービスにもQikitを使用してアクセスすることができます。 今回バックエンドにFlaskを採用したのもQiskitがPythonパッケージであることが理由です。 利用にあたって基本的な線形代数の知識が必要となりますが、日本語ドキュメントが豊富にあるため、初学者の方でも比較的簡単にパッケージの使い方を習得することができると思います。 では早速量子コンピュータに乱数生成させましょう。 ■ 量子乱数生成 こちらのサイトに記載されている手法で量子乱数を生成します。 □ 量子ビット 量子乱数の生成方法を紹介する前に量子ビットについて簡単に説明します 量子コンピュータをつかった計算処理は量子ビットに行列(ユニタリー演算子)演算を行うことで実現されます。 演算対象である量子ビットは(複素)ベクトルとして量子的な状態を持っています。 古典の情報の最小単位は0と1の2値で表現される一方で、量子的な状態ではこの2つの値を重ね合わせた状態が許容されます。 例えば0という状態と1という状態をそれぞれ以下の様に表現します。 \ket{0} = \begin{pmatrix} 1 \\ 0 \end{pmatrix} \ket{1} = \begin{pmatrix} 0 \\ 1 \end{pmatrix} 一般的な量子状態$\ket{\psi}$は次のように表現されます。 \ket{\psi} = \alpha\ket{0}+\beta\ket{1} = \alpha \begin{pmatrix} 1 \\ 0 \end{pmatrix} + \beta \begin{pmatrix} 0 \\ 1 \end{pmatrix} = \begin{pmatrix} \alpha \\ \beta \end{pmatrix} ここで$\alpha$と$\beta$は確率複素振幅と呼ばれます。結論から述べると確率複素振幅の絶対値の2乗でその状態が取得されます。 つまり今回のケースでは測定結果が状態$\ket{0}$になる確率は$|\alpha|^2$、測定結果が状態$\ket{1}$になる確率は$|\beta|^2$です。 この性質を利用して量子乱数を生成します。 例えば次のような状態を仮定します。 \ket{\psi_{rand}} = \frac{1}{\sqrt{2}}\ket{0}+\frac{1}{\sqrt{2}}\ket{1} このとき状態$\ket{0}$になる確率と状態$\ket{1}$になる確率は先ほどの計算(複素振幅の絶対値の2乗)で50%であることがわかります。 つまり量子ビットを$\ket{\psi_{rand}}$のような状態にすることができれば測定を繰り返すごとに50%の確率で状態$\ket{0}$もしくは状態$\ket{1}$が取得できます。 これは純粋な物理現象(量子力学)に由来するランダム性です。 そして状態$\ket{\psi_{rand}}$はQiskitを使えば1行で生成することができます。 すごい!! □ Qiskitで量子乱数生成を実装 まずは量子回路を作成します。以下はQiskitを使用して作成した量子回路です。 provider = IBMQ.get_provider(hub="ibm-q") num_q = 4 device = "ibmq_qasm_simulator" backend = provider.get_backend(device) q = QuantumRegister(num_q, "q") c = ClassicalRegister(num_q, "c") circuit = QuantumCircuit(q, c) circuit.h(q) circuit.measure(q, c) num_qは量子ビット数です。今回は4つの量子ビットを使用しています。 量子ビットの状態の数は量子ビット数nに対して$2^n$個存在します。 なので例えば2つの場合は$\ket{00},\ket{01},\ket{10},\ket{11}$の4状態です。ここで$\ket{nm}$という表記が現れましたが、基本法則は先ほどと同じです。状態の確率複素振幅の2乗がその状態が測定される確率となります。 deviceでは使用する実機を指定します。利用できる実機はこちらから選ぶことができます。 qは量子ビットのレジスタ、cは古典量子ビットのレジスタです。測定によって得られた値をcに格納します。 circuitで量子回路インスタンスを生成した後は、Qikitで定義されたメソッドを使用します。 上記に記載している.h()もその1つです。.h()アダマールゲートと呼ばれ、$\ket{0}$状態の量子ビットにアダマールゲートを演算すると状態 \ket{\psi_{rand}} = \frac{1}{\sqrt{2}}\ket{0}+\frac{1}{\sqrt{2}}\ket{1} を生成することができます。 例えば、量子ビットを2つ用意し、それぞれにアダマールゲートを適用すると全体の状態を以下の様に記述することができます。 \ket{\psi_{rand}} = \frac{1}{2}\ket{00}+\frac{1}{2}\ket{01}+\frac{1}{2}\ket{10}+\frac{1}{2}\ket{11} 基本法則は同じです。状態$\ket{00}$が取得される確率は絶対値の2乗なので25%です。 1量子ビットの時と2量子ビットの時、それぞれを比較すると規則性があることがわかります。 すなわち、n量子ビットの状態は$2^n$個存在し、アダマールゲートをn量子ビットに演算することで$2^n$個存在する状態からその1つを測定する確率は$\frac{1}{2^n}$になるということです。 例えば上記では量子ビットの数4つに定義しているため、状態$\ket{0000}$を取得する確率は6.25%です。 この値は後ほど使用するので覚えておいてください。 アダマールゲートを演算したら最後は測定します。測定のメソッドは.measure()で定義されています。 量子回路が完成したらつぎは量子回路を量子コンピュータに実行させます。アクセストークンを発行することでAPIでジョブを実行することができます。 job = execute(circuit, backend, shots=1024) shotsは測定回数です。shots=1では1回しか測定を行わないため、仮に$\ket{0000}$が測定された場合は$\ket{0000}$が確率100%として実行が完了します。shots回数を増やすことでそれぞれの状態の取得確率が6.25%に漸近していきます。(実際にはノイズなどの影響により完全に6.25%に分布することはありません。) この分布図をプロットしたものが冒頭の棒グラフです。 counts = job.result().get_counts() plot_histogram(counts, color='#BB2528', title="janQen Result") 6%付近のものが多いですが大きく偏りが生じていることがわかります。これはshots回数が少ないということと実機特有のノイズが影響していることが原因であると想定されます。 □ 量子乱数からグー、チョキ、パーへのマッピング 上記の方法で量子力学的に等確率な状態を測定する量子回路を作成することができました。 つまり、この測定により取得した最頻値の状態は乱数として利用することができます。 次はそれぞれの状態に対して「グー」「チョキ」「パー」を順にマッピングします。 このマッピングですが、注意して行う必要があります。 例えば、状態$\ket{0000}$から状態$\ket{1111}$まで順番にマッピングを行うと 以下の様になります。 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 グー チョキ パー グー チョキ パー グー チョキ パー グー チョキ パー グー チョキ パー グー グーの確率が多くなりますね。そこで状態$\ket{1111}$が測定したときはもう一度ジョブを実行する処理を加えます。 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 グー チョキ パー グー チョキ パー グー チョキ パー グー チョキ パー グー チョキ パー ジョブを再実行 こうすることで量子力学的にはランダムな条件で「グー」「チョキ」「パー」を取得することができるようになりました。 上記を一般化します。 量子ビットの数によって「再実行エリア」の値は変わります。「グー」が確率的に多くなってしまう場合もあれば、「グー」と「チョキ」が確率的におおくなる場合もあります。よって量子ビット数に対してマッピングリストを作成し、最頻値を取得する際に照合を行います。 最頻値がマッピングリストにない場合(=最頻値が再実行エリアに該当)ジョブを再実行するようにします。 10進数に変換して実装したものを以下に示します。 以下の例ではdeviceをqasm_simulatorにしていますが、こちらは適宜使用するマシンに置き換えてください。 処理の流れを大まかに記載します。 Postで受け取ったフロントエンドよりユーザーの入力値を受け取る。 Postリクエストを取得時に量子コンピュータでジョブを実行し、乱数を受け取る。 最後に勝敗結果と出力画像のパスをフロントエンドにレスポンスとして戻す。 import os import glob import datetime import matplotlib.pyplot as plt from qiskit.visualization import plot_histogram from flask import Blueprint, jsonify, request from flask_restful import Api, Resource from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, IBMQ def mapping_list(num_q): # 0,1,2をそれぞれ「グー」「チョキ」「パー」とする。 # 例えばnum_q=4のとき15%3=0であるがこのとき0の確率が多くなることがわかる。 # マッピングリストには15を含めないリストを作成する if (2**num_q-1)%3==0: lst = list(range(0,2**num_q-1)) return lst # 例えばnum_q=3のとき7%3=1であるがこのとき0,1の確率が多くなることがわかる。 # マッピングリストには6,7を含めないリストを作成する if (2**num_q-1)%3==1: lst = list(range(0,2**num_q-2)) return lst def generatefig(counts,date): plt.rcParams['figure.subplot.bottom'] = 0.15 plot_histogram(counts, color='#BB2528', title="janQen Result").savefig("dist/"+date) def cleanfig(): file_names = glob.glob("dist/static/tmp/*.png") for file_name in file_names: os.remove(file_name) def judge(i, j): p = (i - j + 3) % 3 if p == 0: return "引き分け" elif p == 1: return "あなたの負け" else: return "あなたの勝ち" def convObject(i): if i == 0: return "グー" if i == 1: return "チョキ" else: return "パー" TOKEN = os.environ["TOKEN"] IBMQ.enable_account(TOKEN) provider = IBMQ.get_provider(hub="ibm-q") num_q = 4 device = "ibmq_qasm_simulator" backend = provider.get_backend(device) q = QuantumRegister(num_q, "q") c = ClassicalRegister(num_q, "c") # post時に量子乱数を生成、ゲームの勝敗をレスポンスするapi(POSTメソッド) quantum_random_generator_bp = Blueprint( "quantum_random_generator", __name__, url_prefix="/api/post" ) class QuantumRandomGenerator(Resource): def post(self): cleanfig() now = datetime.datetime.now() new = "static/tmp/{0:%Y%m%d_%H%M%S}.png".format(now) input_data = request.json circuit = QuantumCircuit(q, c) circuit.h(q) circuit.measure(q, c) while True: job = execute(circuit, backend, shots=100) print("Executing Job...\n") counts = job.result().get_counts() print("RESULT: ", counts, "\n") # 最頻値を10進数で取得 rand = int(counts.most_frequent(), 2) # 0は「グー」、1は「チョキ」、2は「パー」 output_num = rand % 3 if rand not in mapping_list(num_q): continue input_num = input_data["input"] input = convObject(input_num) output = convObject(output_num) result = judge(input_num, output_num) generatefig(counts, new) result_data = { "input": input, "output": output, "result": result, "output_num": output_num, "fig": new, } break return jsonify(result_data) これでバックエンド部分の構築は完了しました。 あとはバックエンドから取得した値に沿ってフロントエンドでゴニョゴニョすることでよい感じになります。 フロントエンドほかソースコードはこちらのリポジトリに格納しているので参考にしてください。 ■ まとめ 長くなってしまいましたがここまで読んでいいただきありがとうございました。 私自身まだまだQiskitに不慣れな部分があるため誤った表現をしている箇所があるかもしれません。 その際は優しくご指摘いただけると幸いです。 また、Webアプリを構築するうえでFlaskとVue.jsを初めて触りましたがとても快適に実装することができました。 今後も使っていきたいと思います。 明日のアドベントカレンダーは@hichikawa1126 さんです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

google-cloud-bigquery の load_table_from_json で 400 Error

小一時間悩んだ 環境 google-cloud-bigquery 2.30.1 (Python Client for Google BigQuery) Python 3.9.0 現象 下記のようなコードで、BigQuery に insert しようとしたところ foo.py import json from google.cloud import bigquery from google.cloud.bigquery.job import LoadJob from google.cloud.bigquery.table import Table if __name__ == '__main__': a = '{"foo":"123","bar": "123"}' row = json.loads(a) project_id = 'foo_project' dataset_id = 'bar_dataset' table_id = 'test' client = bigquery.Client(project = project_id) dataset = client.dataset(dataset_id) table = dataset.table(table_id) table_result: Table = client.get_table(table) job_config = bigquery.LoadJobConfig() job_config.source_format = bigquery.SourceFormat.NEWLINE_DELIMITED_JSON job_config.schema = table_result.schema table_id_constructor = ".".join([project_id,dataset_id,table_id]) job: LoadJob = client.load_table_from_json(json_rows=row, destination=table_id_constructor, job_config=job_config) print(job.result()) 下記のようなエラーになる(そっけない...) google.api_core.exceptions.BadRequest: 400 Error while reading data, error message: JSON table encountered too many errors, giving up. Rows: 1; errors: 1. Please look into the errors[] collection for more details. 原因 あれこれ見たりしたがエラーからはよくわからず、結果 load_table_from_json() の json_rows に渡すのは、list型じゃないとあかん ということがわかった。 というわけで以下のようにすることで insert できましたとさ。 (JSONLを期待してるだろうから、まぁ、そうなんだろうなという気もするが) foo2.py import json from typing import List from google.cloud import bigquery from google.cloud.bigquery.job import LoadJob from google.cloud.bigquery.table import Table if __name__ == '__main__': a = '{"foo":"123","bar": "123"}' row = json.loads(a) rows: List[dict] = [] rows.append(row) # 変更点: list に append してあげる project_id = 'foo_project' dataset_id = 'bar_dataset' table_id = 'test' client = bigquery.Client(project = project_id) dataset = client.dataset(dataset_id) table = dataset.table(table_id) table_result: Table = client.get_table(table) job_config = bigquery.LoadJobConfig() job_config.source_format = bigquery.SourceFormat.NEWLINE_DELIMITED_JSON job_config.schema = table_result.schema table_id_constructor = ".".join([project_id,dataset_id,table_id]) # 変更点: row じゃなくて、 rows を渡す job: LoadJob = client.load_table_from_json(json_rows=rows, destination=table_id_constructor, job_config=job_config) print(job.result())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】画像ファイルの解像度取得方法メモ

Python で画像ファイル (*.png など) から解像度 (WxH) の情報を抽出する方法として OpenCV (https://opencv.org/) Pillow (https://pillow.readthedocs.io/en/stable/) imagesize (https://github.com/shibukawa/imagesize_py) の使い方,速度のメモ. 画像ファイルはいらすとやの人工知能のキャラクターを使用 バージョン情報:Python 3.7.3, OpenCV 4.5.4.60, Pillow 8.4.0, imagesize 1.3.0 OpenCV インストール pip install opencv-python 使い方 import cv2 filename = 'ai_character.png' img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) height, width = img.shape[:2] print(f'Height: {height}, Width: {width}') # Height: 800, Width: 688 Pillow インストール pip install pillow 使い方 from PIL import Image filename = 'ai_character.png' img = Image.open(filename) width, height = img.size print(f'Height: {height}, Width: {width}') # Height: 800, Width: 688 imagesize インストール pip install imagesize 使い方 import imagesize filename = 'ai_character.png' width, height = imagesize.get(filename) print(f'Height: {height}, Width: {width}') # Height: 800, Width: 688 手法の比較 1,000 回平均を記録 (MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)) OpenCV Pillow imagesize 8.4E-3 [s] 1.3E-4[s] 3.6E-5 [s] OpenCV は cv2.imread で np.ndarray のデータを読み込む一方,Pillow ではデータに処理が施されるまでデータを読み込まない (https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.open). また,imagesize では画像ファイルのヘッダをパースするのみである. よって,速度は OpenCV < Pillow < imagesize となっている. 参考文献 python - Get Image size WITHOUT loading image into memory - Stack Overflow opencv - getting the shape of an image file from python without loading the image - Stack Overflow
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オリジナルCコンパイラ neo-c2 version 1.5.0リリースです。BoehmGCをデフォルトとしました。

タイトル通りです。オリジナルヒープシステムはいずれ消します。 neo-c2でPython処理系を作っているとオリジナルヒープシステムにバグが見つかって デバッグで悶えて死にそうになってました。 一人でブツブツ言いながら、必死でデバッグしてたら、嫁には離婚だと言われるし。 あとの余生はオリジナルPython処理系をBoehmGCを使ったお気楽neo-c2で作って行きたいと思います。 僕はイチローや大谷になりたかったのかもしれませんが、所詮は凡人です。 Rustは作れません。白人様ほど頭が良くありません。 男は諦めが肝心ですね。女の一念岩をも通すと言いますが僕は女じゃありません。 東大はどんなガリ勉しても、受かりません。 とりあえずCコンパイラの方はオリジナルのヒープシステムを消して、ちょっとソースを整理します。 viクローンの方もBoehmGCを使ったものとなってます(ソースコードレベルでは互換性があるので、特に変更は加えてませんが) Python処理系を作るのが楽しみですね。あとはJava Script処理系も気が向けば実装したいと思います。 Let's enjoy Programing!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む