7月末には岡山県宇野バスにてリアルタイム混雑状況のGTFS-RTの配信が開始されたり、今月には東京公共交通オープンデータセンターから都営バスや横浜市営バスのリアルタイム運行位置情報のGTFS-RTの配信が開始されたりと取得できるデータが増えてきました。
GTFS-RTやODPT、気象データなど、固定されたURLからデータを読み込み、蓄えておきたい機会が増えてきたので、複数のURLからファイルを取得し、溜め込んでいくプログラムを作りました。
なお、この方法はとても簡易的な物です。RaspberryPiやVPSなどでファイルを常に取得し続け、ある程度溜まったらより大容量なHDDなどに移動させることを想定しています。
このプログラムのように、単一のプログラムを動かし続けるような構成とすると、何かの拍子にプログラムが終了してしまった際にデータ収集も止まってしまうリスクがあります。お金が許すなら、取得する度にファイル取得用のコンテナを起動し、ファイルを取得した上でAWSのS3を初めとするクラウドサービスのオブジェクトストレージに溜め込むような仕様にした方が良いようです。
k8sなどで死活管理をして、コンテナが終了してしまった場合にのみ再起動するような構成にすれば、このプログラムでもいいのかな?
コード及びDockerfile
簡単にうごかすことのできるコードは、Githubにアップしていますので、そちらをクローンして頂ければと思います。
ファイル保存までの流れ
このプログラムは、次のような動作を行います。
- 前回取得してから予め定めた時間が経過したら、URLからファイルを取得する
- ファイルを一時的に保存する
- 1時間ごとにzip圧縮し、保存する
初期化
<?php
// 定数の設定
const sleep_time = 15;
date_default_timezone_set('Asia/Tokyo');
$counter=0;
$last = time();
$last_zip = date("Y_m_d_H");
初期化では、次のことを行っています。
-
繰り返し間隔を定める定数の設定
ループする際の繰り返し間隔を設定します。今回は、15秒に一度ループするようにしています。15の倍数以外の間隔でファイル取得を行いたい場合は、ここの値を変更してください。
どのURLを取得する際も、過度なアクセスはしないよう心がけましょう。 -
タイムゾーンの設定
タイムゾーンを東京に設定します。取得するデータの種類によっては、世界標準時などでもよいかもしれません。 -
カウンター変数の定義
カウンターとなる変数を定義します。ループの度に、プログラムが経過してからの時間をループの度に加算していきます。 -
プログラム開始時の時間の取得
ファイル取得に時間がかかった場合の待機時間を計算する為、プログラムを開始した時刻を記憶する。 -
現在時刻に取得したファイルを保存する際の圧縮ファイルのファイル名となる文字列を記憶する。
ループ及び圧縮するか判定
続いて、ファイルを定期的に取得する為の無限ループを行います。
ループの初めには、現在時刻に取得した場合のファイル名の取得を行います。
ここで取得したファイル名が最後に取得したファイル名と異なる場合には、これまでに取得したファイルをzip圧縮するというフラグである$is_zip
変数をtrueとします。
while(true){
$now_zip = date("Y_m_d_H");
$is_zip = $last_zip!=$now_zip;
1時間分のファイルの圧縮
CSVファイルに間隔、保存ディレクトリ名、URL、拡張子の4要素以上含まれているか確認します。
4要素に満たない場合には、スキップします。
zip圧縮を行うか否かのフラグがtrueでzip圧縮する場合には、zipファイルの保存先ディレクトリが存在するか否かを調べ、存在しない場合には新たにディレクトリを作成し、tmp
ディレクトリにこれまで蓄えた1時間分のファイルを圧縮し、圧縮済みのファイルを削除します。
nohup zip -j [圧縮するzipファイル] [圧縮対象のファイル]; rm [圧縮したファイル] &
とすることで、本プログラムと同時にzipコマンドを実効し、ファイルを圧縮した後に圧縮したファイルの削除を行っています。
while($line = fgetcsv($f)){
if( count($line) < 4) continue;
// zip圧縮を行う
if($is_zip){
// ディレクトリが存在していなければ作成
if(!file_exists('data/' . $line[1])) exec('mkdir ' . 'data/' . $line[1]);
$cmd = 'nohup zip -j data/' . $line[1] .'/'. $last_zip.'.zip tmp/' . $line[1] .'/'.$last_zip.'_*; rm tmp/' . $line[1] .'/'.$last_zip.'_*.'.$line[3].' -f &';
exec($cmd);
echo 'Zip ' . $last_zip . ' ' . $line[1] . "\n";
}
ダウンロード対象URLのダウンロード
現在のカウンタをダウンロード時間の間隔で除算し、余りが0であればダウンロードして保存します。
この際も、ディレクトリが存在していなければ新たにディレクトリを作成し、保存します。
// ダウンロード対象でなければスキップ
if( $counter % (int)$line[0] != 0 ) continue;
// ディレクトリが存在していなければ作成
if(!file_exists('tmp/' . $line[1])) exec('mkdir ' . 'tmp/' . $line[1]);
// URLをダウンロードして保存
$file = file_get_contents($line[2]);
$filename = date("Y_m_d_H_i_s") . '.' . $line[3];
file_put_contents( 'tmp/' . $line[1] . '/'. $filename,$file);
echo 'Get ' . $filename . ' ' . $line[2] . "\n";
}
// url_list.csvを閉じます。
fclose($f);
次のループまで待機
次のループまで待機する時間を求め、算出した時間分待機します。
待機時間は、プログラムが起動してからのカウンタ+待機時間定数-プログラムが起動してからの経過時間で算出し、ファイルダウンロードに時間がかかってしまっても、ファイル取得間隔があまりずれないようにしています。
$last_zip=$now_zip;
// スリープ
$d = $counter + sleep_time - (time() - $last);
if($d<0) $d=0;
sleep($d);
$counter += sleep_time;
}
?>
実行方法
上述しましたが、GitHubに本コード及びこれを動かすためのDockerfile,docker-composeファイルをアップしていますので、ご利用ください。
- url_list.csvにダウンロードする間隔(15秒単位)、保存ディレクトリ名、URL、保存拡張子を記載
定期的に岡山県宇野バスのGTFS-RTを取得する例
url_list.csv
60,unobus_TripUpdate,http://www3.unobus.co.jp/GTFS/GTFS_RT.bin,bin
15,unobus_VehiclePosition,http://www3.unobus.co.jp/GTFS/GTFS_RT-VP.bin,bin
300,unobus_Alert,http://www3.unobus.co.jp/GTFS/GTFS_RT-Alert.bin,bin
- 次のコマンドを実行
# Dockerがインストールされている場合
docker-compose up -d
# PHPがインストールされている場合
cd volume
nohup php download_urls.php
-
自動でファイルが保存される
ファイルが取得される毎にvolume/tmp/設定したディレクトリ名
にファイルが保存される。 -
1時間ごとに圧縮される
volume/data/設定したディレクトリ名
に1時間ごとに圧縮されたzipファイルが保存される。
間違いや改善点等がありましたら、編集リクエストやコメントを頂けると幸いです。