もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

Pythonスクリプトをデーモン化するのにsupervisorを利用した話

簡単なスクリプトをデーモン化出来ないかと調べたところ

supervisorを使うことでスクリプトをデーモン化させることが出来そう

↓参考にしたサイト

Supervisorで簡単にデーモン化 - Qiita

SupervisorでPythonのスクリプトをデーモンプロセスとして動かす - Symfoware


私が大好きなCentOS6.8でやってみた

ちなみに私の環境はこんな感じ


見出し


supervisor(version 2.1)のインストール

supervisorのインストールにepelリポジトリが必要なのでyumでインストール

# yum install epel-release

supervisorのインストール

# yum install supervisor

デーモン化するスクリプトの作成

とりあえず簡単なスクリプト

supervisorの動作確認のため、簡単なスクリプトを作成

「/usr/local/bin/」ディレクトリにファイル名「test.py」でスクリプトを作成

# cd /usr/local/bin/
# mkdir /test
# vim test.py

test.pyの中身(Python3.5用)

#!/usr/bin/env python
# -*- coding: utf-8 -*-


from time import sleep
from datetime import datetime


while True:
    now = datetime.now()
    now_str = now.strftime('%Y-%m-%d %H:%M:%S')
    print('[%s] %s' % (now_str, 'unko'))
    sleep(5)

スクリプトの機能としては、単純に時刻付きで「unko」を出力するだけ

supervisorの設定

先程作成したPythonスクリプトを動作させるため、supervisorの設定ファイルを編集する

# vim /etc/supervisord.conf

設定ファイル内に、コメントで設定のテンプレートがあるので、コピペで以下の内容をファイルの最後に追記した。

[program:unko]
command=/usr/local/lib/anaconda3/bin/python /usr/local/bin/test.py
;priority=999                ; the relative start priority (default 999)
autostart=true              ; start at supervisord start (default: true)
autorestart=true            ; retstart at unexpected quit (default: true)
startsecs=10                ; number of secs prog must stay running (def. 10)
startretries=3              ; max # of serial start failures (default 3)
;exitcodes=0,2               ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT             ; signal used to kill process (default TERM)
;stopwaitsecs=10             ; max num secs to wait before SIGKILL (default 10)
;user=chrism                 ; setuid to this UNIX account to run the program
log_stdout=true             ; if true, log program stdout (default true)
log_stderr=true             ; if true, log program stderr (def false)
logfile=/var/log/supervisor/unko.log    ; child log path, use NONE for none; default AUTO
;logfile_maxbytes=1MB        ; max # logfile bytes b4 rotation (default 50MB)
;logfile_backups=10          ; # of logfile backups (default 10)

commmandのキーに、実行するコマンドを記載

自分の環境ではAnaconda3を利用しているので、Anaconda3のpythonコマンドのフルパスを記載している

supervisorハマった点

CentOSyumでインストールされるのは2.x系

インターネットで調べると3.x系の設定方法が結構見つかるけど、それを書いてしまうともちろん動作しない

supervisorで使用できる設定は、2.x系・3.x系両方ともコメントでテンプレート化されている感じ

別の言い方をすると「/etc/supervisord.conf」のコメントに無い設定は、多分使えない

自分は以下の点で結構ハマった

  1. [include]セクションが使えない
  2. 環境変数がほとんど設定されていない状態
  3. カレントを設定する「directory」キーが使えない
  4. 変数(?)が使えない

1. [include]セクションが使えない

色んな所で書かれている設定だけど

[include]
files = /etc/supervisord.d/*.ini

これはsupervisor2では使えない・・・

supervisor3ではコメント化されてるけど、supervisor2はコメントに無いじゃないですかー

2. 環境変数がほとんど設定されていない状態

自分が使用したsupervisor2では、環境変数が設定されていなかった

正確に言うとPATHの中身が「/sbin:/bin:/usr/sbin」って感じ

なので自分のようにデフォルトではないpythonを使わないときにうまくいかなかった

「/etc/profile」、「/etc/profile.d/*.sh」の設定を使うためには、commandの設定を変更する

↓設定例

command=bash -c 'python /usr/local/bin/test.py;'

3. カレントを設定する「directory」キーが使えない

supervisor2では「directory」キーが使えない(supervisor3では使えるし、コメントにも書いてある)

カレントを変更する場合は、commandの設定を工夫する

↓例

command=bash -c 'cd /usr/local/bin; python /usr/local/bin/test.py;'

bash -c」便利やな

4. 変数(?)が使えない

supervisorの設定を調べると、時々以下のような設定を見ることがあった。

[program:sample]

logfile=/var/log/supervisor/%(program_name)s.log    ; child log path, use NONE for none; default AUTO
  ```

supervisor3.xでは%(program_name)sに[program:sample]のsampleが自動置換されるのだろうが、supervisor2.xでは置換されなかった。



supervisorでスクリプトを起動

supervisorを起動する

# service supervisord start
supervisord を起動中:                                      [  OK  ]

起動状態の確認

# supervisorctl status
unko RUNNING    pid 6647, uptime 00:00:56

ここで何も表示されていないときは、(ほぼ)設定ファイルに問題がある

スクリプトがすぐ落ちる場合は、別のメッセージが表示される

ログファイル(/var/log/supervisor/unko.log)の中身の確認

[2017-01-28 23:15:17] unko
[2017-01-28 23:15:22] unko
[2017-01-28 23:15:27] unko
[2017-01-28 23:15:32] unko
[2017-01-28 23:15:37] unko

supervisorでpythonをデーモン化することができた。

今は別のスクリプトを起動させているが、1日経って特に今のところ問題なし

ログファイル周りの設定をデフォルトのまま使っているので、次はsupervisorのログファイル周りを調査しないといけませんな

PythonでSlackにサーバの状態を送信してみた

はじめに

はてなブログのTOPページを見ていたらたまたま見つけたブログ

blog.8arrow.org

このサイトを見るまでSlackを知らなかった

Slackについて、失礼ながら以下のサイトから転記

mayonez.jp

Slackとは、チャットができるコミュニケーションツールです。シンプルなUIデザインで、ショートカットキーも豊富、プログラミングコードを綺麗に投稿できるスニペットが用意されていて、高性能な検索機能など、情報共有のしやすさ情報の蓄積として使いやすいコミュニケーションサービスです。機能は他にもまだまだあります。Web版、スマホアプリ版、タブレット版があり、いつでもどこでも情報共有ができます。「ショートカット」が豊富なのと、「プログラムコード投稿オプション」がついているので、特にエンジニアがコミュニケーションとるのに便利です。

社内のチャットツールとして結構使われているらしい

いくつか料金プランがあり、無料でも使える

↓料金プランについてはコチラのサイトがわかりやすい

iteach.jp

すごく個人的な問題点

実は1年前から気象庁XMLを受信しているけど、全然活用できていない・・・

XMLが3~4GBたまっているんですけど

どこかのAPIを利用したいと思いつつも、万が一間違った情報が人の目に付くところに出るのも困る

でも何かに利用したいと考えていたところ、Slackで一人チャットなら問題ないでしょ!

というわけで、SlackのAPIを利用してみた

Slackのユーザ登録はおいていて、個人的にハマったアプリの登録から手順をφ(..)メモメモ

アプリの登録・OAuthの認証

アプリの登録・OAuthの認証方法は以下のサイトと同じ手順でやった

qiita.com

一応、公式サイトと同じ手順ではある

api.slack.com

アプリの登録は、Slack APIのサイトの右上の方にの「Your Apps」から登録した

f:id:monakaice88:20170124224656p:plain

あとアプリのOAuth認証は、ブラウザを使った手動で行った。(本当は、OAuthの認証まで内部処理を作らないとイケないでしょうが・・・)

また、OAuthをする前に「Redirect URL(s)」の設定をしないと「Step1」でうまく行かなかった

f:id:monakaice88:20170124232612p:plain

Step1(認証)

以下のようなURLにアクセスする

https://slack.com/oauth/authorize?client_id=xxx&scope=channels:read chat:write:bot&

client_idscopeは必須

  • client_id - アプリと登録されたときに発行されたID
  • scope - アプリの権限。ココでアプリが出来ること(やること)を設定する

scopeの詳細は以下のサイトを見るべし!

api.slack.com

アクセスすると「なんかアプリが許可求めているけどどうする?」的な画面が表示されるのでOKする。

すると「Redirect URL(s)」で設定したページへGETクエリ付きでリダイレクトする。

GETクエリに「code=???」と付いているけど、次のstepで使用する

Step2(アクセストークンの取得)

次にアクセストークンを取得する

https://slack.com/api/oauth.access?client_id=xxx&client_secret=yyy&code=zzz

client_idclient_secretcodeは必須

  • client_id - アプリと登録されたときに発行されたID
  • client_secret - アプリと登録されたときに発行されたモノ
  • code - Step1のリダイレクトで送られた(取得した)コード

アクセスするとjsonが帰ってくる

{
  "access_token": "xxx",
  "ok": true,
  "scope": "xxx",
  "team_id": "xxx",
  "team_name": "xxx",
  "user_id": "xxx"
}

Pythonでサーバの状態を通知する

取り敢えず動作確認なので、Pythonスクリプトでtopコマンドを実行した結果を1時間の間隔で通知することにした。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import subprocess
import requests

def res_cmd_lfeed(cmd):
 return subprocess.Popen(
   cmd, stdout=subprocess.PIPE,
   shell=True).stdout.readlines()

def res_cmd_no_lfeed(cmd):
  return [x.decode('utf-8').rstrip("\n") for x in res_cmd_lfeed(cmd)]


def main():
  cmd = ("top -b -n 1 | sed -n '1,5p'")
  cmd_res = res_cmd_no_lfeed(cmd)
  msg = '\n'.join(cmd_res)
  post_message(msg)


def post_message(msg):
  postUrl = 'https://slack.com/api/chat.postMessage'
  token = 'xxxxxx'
  channel = 'xxxxxx'
  username = 'xxxxxx'

  pyload = {
    'token': token,
    'channel': channel,
    'username': username,
    'text': msg
  }

  res = requests.post(postUrl, data=pyload)

if __name__ == '__main__':
  main()

参考にしたサイト

qiita.com

作ったPythonスクリプトを「/usr/local/bin/cron/chat_top.py」に保存し、cronで1時間おきに実行するようにした。

以下の様なテキストファイルを「crontab.conf」に保存

0 */1 * * * export PATH="/usr/local/lib/anaconda3/bin:$PATH"; python /usr/local/bin/cron/chat_top.py

以下のコマンドでcronを設定

crontab crontab.conf

一時間おきにサーバからチャットが送られるようになった

f:id:monakaice88:20170124224649p:plain

サーバからSlackへチャットを送ることが出来るようになった。

次はPythonでデーモンを作って、電車の運行情報や気象庁XML情報をチャットに送ることをやってみようと思う

【Python】スクレイピングで電車の運行情報を取得してみた

はじめに

最近、Slackというチャットサービスを個人的に利用していて

Slack APIを使って電車の運行情報をチャットで流せないかなーて考え中

電車の運行情報のAPIを公開されているけどお金がかかる・・・

プライベートで公開されている方もいるけど、こだわり始めるとその方のサーバに負荷かけそう

大手サイトのWEBページから取得できたらいいなーと思っていたら、

Qiitaに情報が載っていた

qiita.com


このサイトを見るまで「スクレイピング」という単語を知らなかった・・・

情報元

電車の運行情報で、時々利用するのが以下のサイト。

Pythonを動かしているときにちょうど遅延情報が出ていた。

transit.yahoo.co.jp

江ノ島電鉄線は利用したこと無いけど

見た目はこんな感じ

f:id:monakaice88:20170122124201p:plain


htmlの構造はこんな感じ

f:id:monakaice88:20170122124522p:plain


このサイトから、Python3.5とライブラリ「Beautiful Soup4」を利用して運行情報を取得する

環境構築の準備

私はAnacondaをWindowsにインストールしているため、以下のコマンドで今回用の環境を構築

conda create -n py35 python=3.5 anaconda
activate py35
pip install beautifulsoup4

実装

あまり深く考えず、参考にしたサイトを見よう見真似でコード作成

# -*- coding: utf-8 -*-

import urllib.request
import bs4

url = 'http://transit.yahoo.co.jp/traininfo/detail/148/0/'
html = urllib.request.urlopen(url).read()
soup = bs4.BeautifulSoup(html, 'lxml')

informationElement = soup.find('div', {'id': 'mdServiceStatus'})
information = informationElement.find('p').find(text=True, recursive=False)

postingDateElement = informationElement.find('p').find('span')
postingDate = ''
if postingDateElement is not None:
    postingDate = postingDateElement.find(text=True, recursive=False)

print(information + postingDate)


実行結果

f:id:monakaice88:20170122125611p:plain

とりあえず運行情報が取得できるようになったので、DBも利用してSlackを通して運行情報を知らせるサーバを構築しよう