【python】atコマンドでpythonを自動実行してみる。

at

ヤマモトです。こんにちは。

先日、at コマンドの存在を知らず、crontab で迷走していた。。。

迷走の記録

今回は予告通り、at コマンドをpythonで設定して、pythonを実行してみようと思う。
(わかりにくい表現だなあ…)

atコマンド

atコマンドについて

未来日時と実行したいジョブを指定して、その日時にジョブを実行するというもの。

書き方はいたってシンプル。

at "日時"

日時の指定は色々あるが、例えば "19:00 20.9.22" とすると、2022/9/20 19:00 にスケジュールされる。

上記のコマンドを実行すると、対話型のプロンプトが起動し、実行したいコマンドを入力する。

詳しい使い方は以下を参照。

-f オプションを使って引数にシェルスクリプトを与えて利用するのが一般的なようだ。

試しにatコマンドを使ってみる

先日作った testHello.py を流用する。

import datetime

dt_now = datetime.datetime.now()

print(f"Hello {dt_now}")

実行したい処理をシェルスクリプトにまとめる。

#!/bin/sh
python /home/yamamoto/python/testHello.py >> /home/yamamoto/python/testHello.txt

シェルスクリプトは実行権限を付与しないと実行できないので、権限を付与。

権限の話については先日Qiitaで記事を書いたのでそちらを見てもらえると。

[yamamoto@my-linux]% ls -al /home/yamamoto/python/
...
-rw-r--r--.  1 yamamoto yamamoto   91 Sep XX XX:XX testPython.sh
...

# ユーザーに実行権限を付与
[yamamoto@my-linux]% chmod 744 /home/yamamoto/python/testPython.sh

[yamamoto@my-linux]% ls -al /home/yamamoto/python/
...
-rwxr--r--.  1 yamamoto yamamoto   91 Sep XX XX:XX testPython.sh
   ↑実行権限のxが付与された。

そのまま at コマンドでジョブスケジュールを設定してみる。

# 2022/9/23 20:00 に実行されるように設定
[yamamoto@my-linux]% at "20:00 23.9.22" -f /home/yamamoto/python/testPython.sh

# 設定を確認
[yamamoto@my-linux]% at -l
1 	Sat Sep 23 20:00:00 2022 a yamamoto

# 日時を待つ...

[yamamoto@my-linux]% cat /home/yamamoto/python/testHello.txt
...
Hello 2022-09-23 20:00:00.065603
# 指定の日時に書き込まれていた!!

(本題) ジャンプの発売日をatコマンドに渡す

ジャンプの発売日を取得する

ジャンプの発売日は以下の今週号のページの下の方に記載されている。これをwebスクレイピングで取ってくる。

#!/usr/bin/env python
# coding: utf-8
import os
import re
import datetime

# webスクレイピング用のモジュール
import requests
from bs4 import BeautifulSoup


# ジャンプのサイトからHTMLを取ってくる。
# URLは今週号のページ
url = 'https://www.shonenjump.com/j/weeklyshonenjump/'
res = requests.get(url,timeout=3)

# 文字コードを推定して適宜変換する関数を使用。
res.encoding = res.apparent_encoding


# html.parserに渡してHTMLの解析を行う。
soup = BeautifulSoup(res.text, "html.parser")

# 次回発売日の取得
jump_next = soup.find_all(name="p",text=re.compile(".*発売予定"))[0]
jump_next_Day = re.search(r'[0-9]+月[0-9]+日',str(jump_next)).group()

# 現在日付を取得
dt_now = datetime.datetime.now()
dt_now_Year = dt_now.strftime('%Y年')

# 日付型に変更
T_jump_next_Day = datetime.datetime.strptime(f'{dt_now_Year}{jump_next_Day}','%Y年%m月%d日')


# 生成した日付が未来日付であることを確認。
if T_jump_next_Day <= dt_now:
    #過去日付の場合は年越しを考慮して年数を1年足す。
    New_jump_next_Day = datetime.datetime(T_jump_next_Day.year + 1, T_jump_next_Day.month, T_jump_next_Day.day)
else:
    New_jump_next_Day = T_jump_next_Day

print(New_jump_next_Day.strftime('%Y年%m月%d日 %a'))

普通のwebスクレイピングの処理なので中身の細かい説明は割愛するが、ジャンプの公式ページでは年数表記が無いため、datetime のモジュールで現在の年数を取得してきて結合する処理を行っている。

ただ、年越しの場合はずれてしまうので、最終的に出来上がったジャンプ発売日の年月日と現在の日付を比較して、ちゃんと発売日が未来日付になっていることを確認している。逆に、過去日付だった場合は1年足すようにしている。

例: 2022/12/26(月) にスクリプトを走らせた場合 (次号の発売日は2023/1/10(火)とする)

普通にスクリプトを走らせると、現在の年数:2022年 + webスクレイピングで得た発売日:1月10日を結合して、次号の発売日は「2022/1/10」と算出される。

算出された発売日はスクリプト実行日と比較して過去日付にあたるため、次号発売日は年越しと判断し、発売日の年数を1年足して最終的に「2023/1/10」と出力する。 

ジャンプの発売日をatコマンドに渡して実行

ここで思ったのだが、どういう形式で渡せばいいのだろう??

まあ datetime モジュールだと好きなように出力を変えられるから、python側で適当に調整すればいいか。

at コマンドの実行はpythonの subprocess を用いる。

まずはpython側を設定。

#!/usr/bin/env python
# coding: utf-8
import os
import re
import datetime
import subprocess


# webスクレイピング用のモジュール
import requests
from bs4 import BeautifulSoup


# ジャンプのサイトからHTMLを取ってくる。
# URLは今週号のページ
url = 'https://www.shonenjump.com/j/weeklyshonenjump/'
res = requests.get(url,timeout=3)

# 文字コードを推定して適宜変換する関数を使用。
res.encoding = res.apparent_encoding


# html.parserに渡してHTMLの解析を行う。
soup = BeautifulSoup(res.text, "html.parser")

# 次回発売日の取得
jump_next = soup.find_all(name="p",text=re.compile(".*発売予定"))[0]
jump_next_Day = re.search(r'[0-9]+月[0-9]+日',str(jump_next)).group()

# 現在日付を取得
dt_now = datetime.datetime.now()
dt_now_Year = dt_now.strftime('%Y年')

# 日付型に変更
T_jump_next_Day = datetime.datetime.strptime(f'{dt_now_Year}{jump_next_Day}','%Y年%m月%d日')


# 生成した日付が未来日付であることを確認。
if T_jump_next_Day <= dt_now:
    #過去日付の場合は年越しを考慮して年数を1年足す。
    New_jump_next_Day = datetime.datetime(T_jump_next_Day.year + 1, T_jump_next_Day.month, T_jump_next_Day.day)
else:
    New_jump_next_Day = T_jump_next_Day

# デバッグ用に出力
print(New_jump_next_Day.strftime('次回発売日: %Y年%m月%d日 %a'))


# at コマンドでスケジュールを設定
set_Day = f"00:00 {str(New_jump_next_Day.strftime('%d.%m.%Y'))}"
command = ['at', f'{set_Day}', '-f', '/home/yamamoto/python/Jump_next_day.sh']
subprocess.call(command)


ここでは、Jump_next_day.shat コマンドの実行対象にしているが、このシェルスクリプトの中身にはこのスクリプト自身の Jump_next_day.py を記載する。

#!/bin/sh
python /home/yamamoto/python/Jump_next_day.py >> /home/yamamoto/python/Jump_next_day.txt                                                                                        

実際に Jump_next_day.py を実行してみる。

[yamamoto@my-linux]% python Jump_next_day.py
job 2 at Mon Oct 3 00:00:00 2022

# 設定されているかを確認
[yamamoto@my-linux]% at -l
2     Mon Oct 3 00:00:00 2022

## 成功 !!

プログラム実行 ⇔ atコマンド設定の循環が回るかを確認

python から設定できることは確認できたので、次は Jump_next_day.shat コマンドでスケジュール設定 → Jump_next_day.sh のようにサイクルが回るかを確認する。

今回のテストでは1分おきに上記サイクルを回してみる。

まずは Jump_next_day.pyに以下のテストプロセスを追加。

#### test Process ####
New_jump_next_Day = datetime.datetime(dt_now.year,dt_now.month,dt_now.day,dt_now.hour,dt_now.minute+1)

# デバッグ用に表記を書き換え
print(New_jump_next_Day.strftime('次回発売日: %Y年%m月%d日 %H:%M'))


# at コマンドでスケジュールを設定
## 時間も指定するので少し書き換え
set_Day = f"{str(New_jump_next_Day.strftime('%H:%M %d.%m.%Y'))}"
command = ['at', f'{set_Day}']
subprocess.call(command)

実際に実行してみる。

[yamamoto@my-linux]% python Jump_next_day.py
job 2 at Sat Sep 24 21:00:00 2022

# ジョブリストを確認
[yamamoto@my-linux]% at -l
8	Sat Sep 24 21:00:00 2022 a yamamoto

# 1分後にジョブリストを確認
[yamamoto@my-linux]% at -l
9	Sat Sep 24 21:01:00 2022 a yamamoto

## ジョブが書き換わっている!!


# デバッグを確認
[yamamoto@my-linux]% cat Jump_next_day.txt
次回発売日: 2022年09月24日 21:00
次回発売日: 2022年09月24日 21:01
...

## ちゃんと継続して実行されている模様。

ちなみにしばらく実行させていたところ、1時間後に止まっていた。
理由はhour: 時刻を+1にしていなかったので、22時になった瞬間にatで指定する日時が過去日付になり、エラーになってしまった模様。なるほどなるほど。


はい。お疲れさまでした。

at コマンドは初めて使ったのだが、なかなか便利だなあ。datetime との相性が良いね。

さあて、次回はレンタルサーバーで実際に実装してみましょうか。



コメント

タイトルとURLをコピーしました