herokuでのスケジュール管理にAPSchedulerを使ってみる
目次
- herokuで定期的にファイルを実行したいとき何を使っていますか?
- cron...?
- heroku scheduler...?
- cronよりもheroku schedulerよりAPSchedulerが良い
- heroku上でAPSchedulerを使ってみる
- 用意するもの
- まとめ
- 参考
herokuで定期的にファイルを実行したいとき何を使っていますか?
cron?
heroku scheduler?
APScheduler?
cron...?
DBの更新、データの取得、バックアップの保存などのスケジュールの管理はモダンなWebアプリには必須機能です。
ubuntuのデフォルトでインストールされているcronはあらゆるスケジューリングが可能で、上記のほとんどのことはcronによって達成することが出来ます。
ですが、cronはサーバー上で動作することもあり、Webアプリのテストをローカル環境で行うことを考えると、もっと高いレイヤーのアプリケーションのインスタンスで動作するほうが勝手がいいです。
heroku scheduler...?
herokuにはスケジュール管理のためのファンタスティックなアドオンが用意されています。
このheroku schedulerはシンプルなタスクを10分、1時間、一日、複数の間隔での実行が可能です。
しかし、5分や37分など中途半端な時間での実行は難しいです。
cronよりもheroku schedulerよりAPSchedulerが良い
アプリケーションのインスタンスで動作し、かつ、中途半端な時間でも簡単にスケジュールの管理が可能なのが、APSchedulerです。
これはPython製のライブラリなので、私のようにwebアプリを全てpythonで完結させたい人たちにはもってこいです。
heroku上でAPSchedulerを使ってみる
ということで、1分おきに現在時刻をDBにインサートするプログラムを書いてみます。
最終的な出力はこうなります。
herokuのログを見ると、1分おきにDBを更新していることがわかります。
用意するもの
herokuでアプリを運用するために必要なファイルは以下です。
#適当に用意してください runtime.txt requirements.txt #以下は今から作成します Procfile gettime.py time.db timeclock.py
スタート。
#screamtimeという名前のアプリを作ります heroku create screamtime
次に、gettime.pyを作成します。
import sqlite3 import pandas as pd import datetime #現在時刻を取得する now = datetime.datetime.today() hour = now.hour minute = now.minute #DataFrame形式にぶち込む #普通はSQL文書いてインサートすると思いますが、 #私がDataFrame形式に慣れているので、こちらを使用していますが、各自なれた方法でお願いします time_df = pd.DataFrame({"Hour" : [hour], "Minute" : [minute]}) #db作成 dbname = "time.db" #dbコネクト conn = sqlite3.connect(dbname) c = conn.cursor() #dbのnameをtime_dfとし、読み込んだcsvファイルをsqlに書き込む #if_existsでもしすでにtime_dfが存在していても、置き換えるように指示 time_df.to_sql("times", conn, if_exists="replace") #作成したdbを見てみる select_sql = 'select * from times' for row in c.execute(select_sql): print(row) conn.close()
これをgettime.pyという名前で保存します。
次は、今作成したgettime.pyをAPSchedulerで定期的に実行させるコードを書きます。
from apscheduler.schedulers.blocking import BlockingScheduler import os sched = BlockingScheduler() #間隔は1分ごとにしています #minutesではなくてhourに変更したら、時間での指定も可能です @sched.scheduled_job('interval', minutes=1) def timed_job(): print("Let's start") os.system("python gettime.py") sched.start()
これをtimeclock.pyという名前で保存します。
最後にProcfileを作成します。
clock: python timeclock.py
これで、herokuにclockのファイルであるということを宣言します。
ファイルが全て揃ったので、あとはherokuに上げるだけです。
念の為、herokuに上げるまでのgitでの手順を載せておきます。
git init git add . git commit -m "first commit" git push heroku master
エラーなく行けば、これでherokuには「1分おきに現在時刻をDBにインサートするプログラム」がアップロードされました。
以下でherokuにclockをスケールします。
heroku ps:scale clock=1
上手くスケールされたことを確認して。
heroku ps |< 数分待ってからherokuのログを見てみます。 >|| heroku logs
以上で「1分おきに現在時刻をDBにインサートするプログラム」が完成しました。
まとめ
APSchedulerはとても簡単にスケジュールの管理が行うことができます。
コードもpythonで書くことができるので、使い勝手がとても良いです。
cronよりもheroku schedulerよりもAPSchedulerが良いんじゃあないでしょうか!?
herokuのworker dynoとweb dynoの違いって何?
目次
dyno?
herokuでは、プロセスの処理はdynoによって行われます。
プロセスの処理とはHTTPのリクエストやレスポンス、バックグラウンド処理などです。
dynoは3種類あり、処理をするプロセスの種類によって使い分けられます。
- web dyno
- worker dyno
- one-off dyno
herokuを勉強していて、dynoはheroku内の独特の単位で一番はじめにつまづいたのでまとめておきます。
web dyno
HTTPのリクエスト・レスポンスを処理します。
ユーザーからのリクエストはweb dynoが処理するので、webアプリが行う処理の大半はweb dynoによって行われています。
worker dyno
バックグラウンド処理を行います。(ほぼweb dyno以外の処理)
バックグラウンド処理とは、例えばAPIsからのデータ取得やRSSフィードの読み込み、画像のリサイズ、S3へのデータ送信などが当てはまります。
one-off dyno
一時的な処理を行うためのdyno。
heroku run
などのherokuコマンドを実行したときに使用されるdynoです。
ログ
これらdynoの動作状況はログを見ることによって確認できます。
#ログの確認 heroku logs -t #見たい行数を指定する(最大1500行まで) heroku logs -n 200
Control+Cでログの出力を中止できます。
ログは直近の1500行のみしか表示されず、ログの保存を行う場合はアドオンを追加します。
使用中のdynoを確認する
heroku ps
このコマンドで現在、使用しているdynoを確認することができます。
まとめ
worker dynoとweb dynoの違いは、まとめると処理するプロセスの違い、ということになります。
Webアプリによって必要な処理は違ってくるので、アクセス数がひたすら多いWebアプリはweb dynoを増やしたり(=スケーリング)、バックグラウンドでの処理が多いならworker dynoを増やしたりと適宜変更しながら運用していきたいですね。
cronで定期的にフォルダを作成してみる(Ubuntu 16.04)
色々とcronについて勉強したので、今回はcronで一分毎にTestという名のフォルダを作成するまでやってみようと思います。
cronとは
設定したファイルを定期的に実行させるためのツール
crontabに登録されているcronを確認
crontab -e
編集等はvimで行うことになります。
もし、まだcronがひとつも登録されていなければ、no crontab for <ユーザー名> が表示されます。
crontabは /etc の中にあるのですが、私はvimの使い方がわからなかったので、sublimetext(テキストエディタ)に移してから編集していましたが、これではうまくいきませんでした。(編集しても内容が反映されない)
おそらく、編集・実行の権限が与えられていなかったからだと思います。
諦めてvimの使い方をドットインストールで学んできて、ようやく編集することができました。
(vimは癖が強すぎるが、慣れれば最高に使いやすいツールになることの片鱗をつかめた!)
そもそもcronが動作しているのかをチェック
sudo service cron status
active(running)が表示されていれば正常に動作しています。
また、cronの動作ログもこれで確認可能です。
基本的にcronの動作ログは /var/log のsyslogファイルに書かれています。
なので。
cd /var/log cat syslog
で、確認可能ですが、いちいちsyslogを見に行くのも面倒なので、sudo service cron status で確認したほうが楽です。
定期的に実行させるファイルを作成
/home にtest.shというファイルを作成します。
#!/bin/shはシェルスクリプトであるということを明示するためのものです。
mkdir Test にしているので、cronが上手く実行されれば/home にTestという名のフォルダが作成されるはずです。
(mkdirでもなくても大丈夫です、今回はcronが動作しているかどうかを確認したいため、適当にmkdirにしました)
#!/bin/sh mkdir Test
crontabからcronを設定
crontab -e
* * * * * /bin/sh /home/ubuntu/test.sh > /dev/null 2>&1
時間は1毎分に設定しています。
エラーが発生していた場合、すぐに確認できるので。
また、cronによって実行するファイルがシェルスクリプトであることを明示するために /bin/sh を記述しています。
cronでの時間設定の書式は以下のサイトがわかりやすかったです。
これでcronの設定は終わったので、ログを見てみます。
3月 30 23:09:01 tk CRON[4148]: (ubuntu) CMD (/bin/sh /home/test.sh)
CMDとはcronが実行したコードを示しています。
ログを見る限り、cronは正常に実行されています。
ディレクトリを見てみると...
これが...
テンプレート ピクチャ test.sh デスクトップ ミュージック ドキュメント 公開 ダウンロード ビデオ
1分後...
Testフォルダが作成されたので、無事にcronが動作したようです。
Test ダウンロード ビデオ テンプレート ピクチャ test.sh デスクトップ ミュージック ドキュメント 公開
MTAというエラーが出た場合
cronはエラーが出た際、メールで通知しようとするのですが、メールの設定を行っていない場合にMTAというエラーが出ます。
postfixなどでメールの設定を行うことでこのエラーは解決できます。
以下のサイトがわかりやすかったです。
bootの容量がいっぱいなのに、古いカーネルが消せない問題(ubuntu 16.04)
bootの容量がいっぱいすぎて、新しいカーネルに更新できず、まわりまわって他のソフトウェアもアップデートできないという問題が発生しました。
試しにbootの容量を確認してみると...
df /boot
Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda7 236876 224404 0 100% /boot
見事に100%!!
100%って何か気持ち良いですね!元気がでました。
だがしかしだけれども、何とかbootの容量を空けないと身動きができないので、古いカーネルを消そうとしました。
とりあえず、現在使用中のカーネルは...
uname -r
4.4.0-63-generic
そして、boot内にあるカーネルは...
dpkg --get-selections | grep linux-
linux-base install linux-firmware install linux-generic install linux-headers-4.4.0-57 install linux-headers-4.4.0-57-generic install linux-headers-4.4.0-59 install linux-headers-4.4.0-59-generic install linux-headers-4.4.0-62 install linux-headers-4.4.0-62-generic install linux-headers-4.4.0-63 install linux-headers-4.4.0-63-generic install linux-headers-4.4.0-64 install linux-headers-4.4.0-64-generic install linux-headers-4.4.0-66 install linux-headers-4.4.0-66-generic install linux-headers-4.4.0-70 install linux-headers-4.4.0-70-generic install linux-headers-generic install linux-image-4.4.0-21-generic deinstall linux-image-4.4.0-53-generic deinstall linux-image-4.4.0-57-generic install linux-image-4.4.0-59-generic install linux-image-4.4.0-62-generic install linux-image-4.4.0-63-generic install linux-image-4.4.0-64-generic install linux-image-4.4.0-66-generic install linux-image-4.4.0-70-generic install linux-image-extra-4.4.0-21-generic deinstall linux-image-extra-4.4.0-53-generic deinstall linux-image-extra-4.4.0-57-generic install linux-image-extra-4.4.0-59-generic install linux-image-extra-4.4.0-62-generic install linux-image-extra-4.4.0-63-generic install linux-image-extra-4.4.0-64-generic install linux-image-extra-4.4.0-66-generic install linux-image-extra-4.4.0-70-generic install linux-image-generic install linux-libc-dev:amd64 install linux-sound-base install syslinux-common install syslinux-legacy install
放置っぷりが明らかに...
古いカーネルはたくさんあるし、最新のカーネルにアップデートもしてないし...
現在使用しているのが、4.4.0-63なので、それ以前のカーネルは削除しようと思います。
sudo apt-get autoremove --purge linux-image-4.4.0-57-generic
すると未解決の依存関係があるようで、削除できません。
パッケージリストを読み込んでいます... 完了 依存関係ツリーを作成しています 状態情報を読み取っています... 完了 以下の問題を解決するために 'apt-get -f install' を実行する必要があるかもしれません: 以下のパッケージには満たせない依存関係があります: linux-image-extra-4.4.0-57-generic : 依存: linux-image-4.4.0-57-generic しかし、インストールされようとしていません linux-image-extra-4.4.0-70-generic : 依存: linux-image-4.4.0-70-generic しかし、インストールされようとしていません linux-image-generic : 依存: linux-image-4.4.0-70-generic しかし、インストールされようとしていません E: 未解決の依存関係です。'apt-get -f install' を実行してみてください (または解法を明示してください)
'apt-get -f install'を実行したとしても、bootの容量がいっぱいなのでどのみちインストールはできません。
sudo apt-get -f install linux-image-4.4.0-70-generic
けっこうな詰みです。
古いカーネルを消そうとしても、未解決の依存関係が原因で消せない、にもかかわらず、その未解決の依存関係を修復するために最新のカーネルをインストールしようとしてもbootの容量がいっぱい...
ということで最終手段、依存関係を無視して古いカーネルを消すことにしました。
cd /boot ls boot
一番古い57系のものを色々と削除。
sudo rm linux-image-4.4.0-57-generic
ここでもう一度容量を確認。
df /boot Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda7 236876 184731 35608 84% /boot
無事にbootを整理できました。
以上終わり!
SQLite3をPandasから操作する
SQL文の操作を覚えるのが面倒...
select * from tbl_nameまでが覚えれる限界でした。
データの検索・変更などのwhere文等を覚えるのが面倒かつ、逃げ道を見つけてしまったので、逃げ道のほうに行くほかない。
ということで、SQLite3のデータをPandasのDataFrame型に出力してから、DataFrame型でデータを加工するという技を身につけたので、ここにメモとして保存します。
これだと、Pandasでデータをいじくり回して、最後にSQLite3に保存するだけなので、楽ちん。
Pandasの万能さを実感し、かつ、SQL文を覚える機会をまたまた失ったわけであります。
#SQLite3をインポート import sqlite3 import pandas as pd #db dbname = "code.db" #dbコネクト conn = sqlite3.connect(dbname) c = conn.cursor() #dbを見てみる pd.read_sql_query("select * from code_7776", conn) #dbをpandasのDataFrame型で読み込む df = pd.read_sql("select * from code_7776", conn) #dfの中身はすでにDataFrame形式 #データの変更等はPandasでのデータいじりの操作方法でいける df["Altering"] = None print(df) #データをいじり終わったら、SQLite3のdbに書き込む df.to_sql("code_7776", conn, if_exists="replace")
【参考】
PandasのデータをSQLite3で保存する
スクレイピングしたデータをどうすればいいかわからない問題発生
株価データ等をYa〇〇〇ファ〇〇〇〇などからスクレイピングして、今まではCSVファイルでローカルのディレクトリに置いていました。
言ってしまえば、スクレイピングして放置状態でした。
データを使用するときはCSV形式なので、今まではそれが都合良かったのですが...
毎回毎回スクレイピングするのは相手方のサーバーに申し訳ないので、そろそろデータベースで管理したほうがいいかなぁ、と思うようになりまして。
データベースで管理すれば、サイトが更新されたデータ分だけを拾って、データベースを更新するだけで済むので、かなり時間も節約できますし。
そこでSQLite3を勉強していたのですが、はてさてpandasのDataFrame形式はいかにしてDBに放り込むのか。
少し調べるとpandasでできるらしいぞ、と。
コードを以下に貼ります。
import sqlite3 import pandas as pd #pandasでcsvファイルを読み込む df = pd.read_csv("code_1332.csv") df.columns= ["Date", "Open", "High", "Low", "Close", "Volume", "Adj Close", "Number"] #db作成 dbname = "code.db" #dbコネクト conn = sqlite3.connect(dbname) c = conn.cursor() #dbのnameをcode_1332とし、読み込んだcsvファイルをsqlに書き込む #if_existsでもしすでにcode_1332が存在していても、置き換えるように指示 df.to_sql("code_1332", conn, if_exists="replace") #作成したdbを見てみる select_sql = 'select * from code_1332' for row in c.execute(select_sql): print(row) conn.close()
出力を見てみる。
SQLite3に書き込まれ、DBが作成されています。
pandasって万能やなぁと今日も感じた次第であります。
以上報告終わり!なのですが...
他のシストレやる方たちはどのようにデータを集めて保存しているのですか?
もっと効率良い方法があるなら教えて欲しいです。
もしよければコメント下さい、お願いしますm(_ _)m
Yahooファイナンスの株価予想ページをScrapyでクローリング・スクレイピング
目次
本日やること
Yahooファイナンスでは、毎日アナリスト的な人たちが株価予想を行っています。
今回は、そのページをScrapyでクローリングしながら、各アナリストたちの予想をスクレイピングして、ファイルに保存します。
完成予定ファイル
こんな感じのページを…
こんな感じのファイルにまとめます!
必要な環境
python3 scrapy jq ubuntu(OSは自由)
なぜscrapy?
scrapyはクローリング・スクレイピングを行うためのライブラリです。
ライブラリの記述ルールを覚えてしまえば、とてもシンプルにクローリング・スクレイピングを行うことができます。
分析等を行うには大前提としてデータが必要です。
データは様々なサイトから集めてくると思うのですが、個々のサイトごとにコードを書いていては大変ですし、また管理の手間も増えます。
scrapyでスクレイピングを行えば、トータル的に無駄が省けてハッピーということです。
Python以外にはあまりスクレイピングのためのライブラリはないようなので、scrapyをバンバン使って、無駄という無駄を省きパイソニストのメリットを最大限に活かしましょう!笑
スクレイピングの流れ
スクレイピング対象のサイトのHTML構成を確認。
scrapyでサイトからスクレイピング。
以上!!!(めっちゃ簡単)
Scrapyのインストール
pip install scrapy
Scrapyプロジェクトの作成
scrapy startproject mypredict
Ubuntuで言う端末、Macで言うターミナル、Windowsで言うコマンドプロンプトに上記のコードを打ちましょう。
これでScrapyのプロジェクトが作成されます。
このコマンドを打つと、mypredictというファイルが作成されます。
以降、このフォルダの中で作業を行います。
念の為、ファイル構成を確認しておきましょう。
中には、Scrapyの設定を記述してあるファイルやスクレイピングした内容を検証するためのファイルがありますが、今回は使用しません。
では、mypredictに移動します
cd mypredict
基本的には、作業はこの階層で行います。
Spiderの作成
scrapyでは、スクレイピングをするためのルールをこのspiderに記述します。
そのため、スクレイピングを行うサイトごとにspiderを作成するイメージです。
スクレイピングをするにはサイトごとに異なるページ構成を考慮する必要があります。
class名やid名などサイトによって使用しているものが異なるので、そういったサイトの個別のルールをこのspiderに記述します。
※ページ内容のダウンロード等は他のファイル(すでに用意されている)
まず、spidersフォルダの中にpredict.pyというファイルを作成します。
このファイルがクローリング・スクレイピングするファイルとなります。
今回作成するファイルはこれだけです。
このファイルで全ての処理を行います。
記述するコードも30行にもいきません。
※注意、ファイルを作成するのはspidersの中ですが、作業をするのはあくまでもmypredict直下です。scrapy.cfgがある階層です。
とりあえず、predict.pyというファイルだけ作成して、そのままにしておきます。
スクレイピングするサイトを確認
先ほど貼った画像と同じです。
この予想一覧を上から順にクローリングしていき、URLをたどっていきます。
最終的には、このページ内にある全ての個別の予想ページの中身の文章をスクレイピングしていきます。
ここでクローリングとスクレイピングの意味を確認しておきます。
私もそこまではっきりと認識しているわけではなくて、ニュアンスで使っていますが...
クローリングはサイトの情報を閲覧する
スクレイピングは、閲覧している情報をダウンロードしてくること
こんな感じだと思います、以降このような意味でクローリング・スクレイピングという言葉を使っていきます。
スクレイピングはじまるよー
ではまず、予想トップページをスクレイピングしてみましょう。
この予想トップページから個別ページに繋がるURLを拾ってくるのが目的です。
以下のコードを、ちょっと前に作成してほったらかしにしているpredict.pyに記述します。
import scrapy class PredictSpider(scrapy.Spider): #predictはSpiderの名前。spider実行時に入力する。 name = "predict" #必ず必要なコードです #相手サーバーに配慮するために、スクレイピングの間隔に1秒入れます custom_settings = { "DOWNLOAD_DELAY": 1, } #スクレイピングを行う際に一番最初にクローリングするページ start_urls = ["http://info.finance.yahoo.co.jp/kabuyoso/article/"] def parse(self, response): #Google Chromeの検証等でページの構成を確認すると。 #URLはHTMLのh3タグのクラス st02の下に格納されていることがわかる。 for href in response.css("h3.st02 a::attr('href')"): #相対URLから絶対URLに変換 url = response.urljoin(href.extract()) #スクレイピングした内容を確認 print(url)
ちなみに、ページ構成のスクショは以下。
こればっかりはページのHTMLを確認しながら自分で地道にたどっていくしかないです。
先ほどのコードを実行してみます。
mypredict直下で以下のコードを打ちます。
scrapy crawl predict
こんな感じでURLがリスト形式で表示されれば成功です。
さて、これでたどるURLを入手することができました。
次にこのURLを辿って、ページの中身をスクレイピングします。
先ほどのコードを少し変更し、さらに個別ページをたどるコードを追加しています。
import scrapy class PredictSpider(scrapy.Spider): name = "predict" custom_settings = { "DOWNLOAD_DELAY": 1, } start_urls = ["http://info.finance.yahoo.co.jp/kabuyoso/article/"] def parse(self, response): for href in response.css("h3.st02 a::attr('href')"): url = response.urljoin(href.extract()) #ここから下が追加分 #スクレイピングしたURLをたどるためにparse_topicsに投げる yield scrapy.Request(url, self.parse_topics) def parse_topics(self, response): #個別ページのHTML構造を確認する #h3タグのクラスst02にタイトルが格納されていることがわかる title = response.css("h3.st02 ::text").extract_first() #本文はdlタグのクラスmarB20に格納されていることがわかる #これを::textで抽出するとbrタグも含めまれて上手くスクレイピングできない #xpathのstring()を使用すると、うまい具合にスクレイピングすることができる body = response.css("dl.marB20").xpath("string()").extract() #辞書形式で格納する yield { "title" : title, "body" : body }
先ほどと同じように、たどるページ先のHTML構成を確認しておきます。
さて実行してみます。
Scrapy crawl predict
実行結果はこちらです。
スクレイピングが成功していたら、何かしらの文字列が表示されると思います。
スクレイピング内容をファイルに保存
とても簡単です。
#-oはファイルに保存する #predict.json、ここは自由 #保存したいファイル名とファイル形式を指定するだけです scrapy crawl predict -o predict.json
これだけです。
中身を見てみましょう。
#jqを事前にインストールする必要あり cat predict.json | jq
実行結果です。
まとめ
上手くできましたか?
思いの外簡単にスクレイピング出来たと思います。
なんせ、書いたコードは30行に満たない程度ですから。
今回はファイルに保存しましたが、これをデータベースに保存してもOKですし、実際には管理のしやすさ等を考えるとそちらのほうがベターかと思います。
私もまだまだscrapyを触り始めたばかりなので、これから色々と使っていきたいと思います。
参考
Pythonクローリング&スクレイピング -データ収集・解析のための実践開発ガイド-
- 作者: 加藤耕太
- 出版社/メーカー: 技術評論社
- 発売日: 2016/12/16
- メディア: 大型本
- この商品を含むブログ (1件) を見る