LYNCSブログ

慶應義塾大学公認団体 宇宙科学総合研究会(LYNCS)のブログです。

JSONファイルを定期的に取得・保存したい (Node.js)

個人タスクで発生した備忘録です。

JSONを採取したい

東京メトロのオープンデータでは、JSON形式で運行情報などを取得できます。 実際の運行情報の事例を収集して解析の対象とするために簡単なスクリプトを書きました。

使用するもの

  • Node.js
  • date-utils
  • cheerio-httpcli
    • DOM解析するわけではないですが、単にエンコードとかの取り扱いが楽になるので。

コード

require('date-utils');
const cheerio = require('cheerio-httpcli');
const fs = require('fs');

var datetime = new Date().toFormat('YYYY-MM-DD-HH24_MI_SS');
var filename = datetime + '.json';
var savepath = './saved_json/' + filename;

cheerio.fetch('***input URL here***', 'utf-8', function (err, $, res, body) {
    //今回はダイヤ乱れなどの情報がある場合のみJSONを保存したいので
    if ($.html().search(/のため/) != -1){ 
        fs.writeFileSync(savepath, $.html(), 'utf8');
    }   
});

定期実行

言わずもがな、cronに登録します。 筆者はラズパイで走らせています。ラズパイが1台あれば気軽にcron回せるのでおすすめです。

(ryo-a)

MediaWiki「サムネイルの作成エラー: 12.5 MPよりも大きな寸法のファイル」の表示を解決する

問題

MediaWikiはアップロードされた画像に対して自動でサムネイル画像を作成し、ページ内ではそのサムネイル画像を表示しています。

しかし、一定のサイズ(ファイルサイズではなく解像度)を超えた画像がアップロードされた場合、以下のようなエラーを返します。 f:id:lyncs:20170618125307p:plain なお、このエラーが出ても画像はしっかりとアップロードされています。サムネイルが生成されないだけです。

解決策

初期値では解像度の上限は12.5MP(3500 x 3500ピクセル)となっていますので、単にこの値を引き上げてやれば大丈夫です。
LocalSettings.php$wgMaxImageArea の値を設定しましょう。 Manual:$wgMaxImageArea - MediaWiki

4.9MP(7000x7000px)の場合こんな感じです。

$wgMaxImageArea = 4.9e7;

通常、LocalSettings.phpの初期値では$wgMaxImageAreaの項目がないはずなので、上記の内容を最終行に追加してあげればOKです。

多くの人が慣れ親しんだ◯x◯ピクセルという数値ではなく、ピクセル数で数値指定する必要があるので、参考のために上記MediaWikiマニュアルにはいくつかの例が記載さています。

  • 25 million pixels or 5000×5000: 2.5e7
  • 36 million pixels or 6000×6000: 3.6e7
  • 49 million pixels or 7000×7000: 4.9e7
  • 64 million pixels or 8000×8000: 6.4e7
  • 72 million pixels or 9000×9000: 7.2e7
  • 100 million pixels or 10000×10000: 10e7

その他

設定ファイルを変更すれば既存の画像もサムネイルが作成されるので、アップロードし直す必要はありません。

(LYNCSwiki運用担当 ゆん)

Slackの古くなったファイルを自動で削除してみた

概要

Slack(無料プラン)の容量制限をオーバーしないために、特定チャンネルの、ある日数より古いファイルを削除するbotを作成した記録です。 GAS(Google Apps Script)で毎日タイマー起動するように設定し、ファイル容量を管理するコストを大幅に抑えることができます。

対象読者

  • Slackを普段使っている方
  • JavaScriptを少し使ったことがある方

ねらい

LYNCSでは、2015年11月ごろからSlackをメインの連絡ツールとして利用しています。 Slackは企業向けのチャットサービスではありますが、無料プランでもかなり便利に使えるのが嬉しいですよね。

しかし、少人数で数ヶ月ほどの利用ならいざ知らず、人数が増え長く使い続けるとある問題にぶち当たります。 ファイルストレージの5GB容量制限です。

f:id:lyncs:20170604130347p:plain
LYNCS SlackチームのStatistics

画像は現在のLYNCSチームの統計ですが、数千のファイルが存在していることがわかります。 何も対策せずに使っていると、「5GB超えてるぞ金払えコラ(意訳)」と怒られることもしばしばでした。 しかし、Slack有料プランの価格設定は企業向けとなっており、学生団体にとってはおいそれと払える額ではありません。

従っていらないファイルを削除し、無料プランの制限内で使うしかないことになります。 でも、Slackにはファイルの一括削除機能がなく、ポチポチ手作業で消すことしかできません。 「ファイルを選ぶ→削除ボタンを押す→削除の確認ボタンを押す」を数百回も繰り返すのはさすがに耐えがたい。

f:id:lyncs:20170604130316p:plain
ファイル削除の確認メッセージ

そこで、SlackのWeb APIを利用して古いファイルを消す仕組みを作ることにしました。

実行環境

GASを知らない人のために簡単に説明すると、「GoogleのサーバーでJavaScriptを動かせる夢のようなサービス」です。 一応時間制限はあるものの、無料で24時間いつでもJavaScriptが動きます。 サーバーを持たなくても、Googleアカウントさえあれば簡単なBotが作れてしまうのです。

GASでSlack向けのBotを作った事例は既に多数あって、ライブラリも存在しています。 GASの具体的な操作などについてはあまり書かないので、以下の記事などをご覧ください。

Slack Web APIを使ってみる

Slackには、とても便利なWeb APIが存在します。 コマンド操作は敷居が高いと思うかもしれませんが、実はWeb上に素晴らしいテスト環境が用意されているので全く心配いりません。

API Methodsページに使えるメソッドの一覧があり、それぞれのページで詳細を閲覧できます。 実行するにはTokenを持っている必要があるので、発行ページで作っておきましょう。

例えば、files.listの動作を試してみます。 “Tester"と書かれたタブでオプション(token以外は空欄でも可)を入力してTest Methodをクリックすると、結果が下のテキストエリアに出てきます。

f:id:lyncs:20170604130311p:plain
APIテスター

親切にURLまで表示してくれています。 これにアクセスすれば自分のチームのファイル一覧が手に入るというわけです。 さて、リスト先頭の"image.png"を削除してみましょう。

f:id:lyncs:20170604130334p:plain
files.listの実行結果

ファイルの削除にはfiles.deleteを使います。 ファイルの"id"が必要になるので、files.listで出てきたものをコピペします。

f:id:lyncs:20170604130327p:plain
files.deleteの実行結果

"ok": trueとしか返ってきませんが、これで削除が完了しています。 とっても手軽ですね。

なお、削除に限らずSlackのデータを変更するメソッドをテストすると実際に変更されてしまうので、試す場合は十分気をつけてください!!!!!

削除を行うプログラムの流れ

APIの使い方が分かったので、一連の流れをプログラムにしていきます。 基本的には

  1. ファイル一覧を取得する(files.list)
  2. 見つけたファイルの数だけファイル削除を繰り返す(files.delete)

だけですが、いくつか問題があるので実際はもう少し複雑です。

不要なファイルを絞り込む

チームのファイルを全部消してしまっては、必要なものまで消えてしまい混乱が起こるので、不要なものだけに絞ります。

幸い、LYNCSのSlackは業務系のチャンネル(#generalなど)と雑談系のチャンネル(#randomなど)が分かれているので、雑談系にあがったファイルだけを削除すればよさそうです。 それでも、写真を貼った瞬間に消されては雑談にならないので、特定日数以前のファイルだけを対象にします。

チャンネルを指定する

特定チャンネルのファイル一覧を取り出すには、files.list"channel"を指定します。 チャンネルの指定はファイルと同様IDで行います。 チャンネルのIDなんて普通は覚えていないので、これもAPIで調べることにしますが、少し面倒です。

Slackのチャンネルには、公開(Public)のものと非公開(Private)のものがあります。 APIでは前者はchannel・後者はgroupという扱いで、メソッドを使い分けねばなりません。 そこで、チャンネルの名称からIDを得る方法はこうなります:

  1. チャンネル一覧を取得する(channels.list)
  2. 探している名称と一致するチャンネルがあるかどうか調べる
  3. 一致しなかったら、グループ一覧を取得する(groups.list)
  4. 探している名称と一致するグループがあるかどうか調べる

以下はchannelを探す関数です。 groupの方も流れは全く同じなので省略します。 UrlFetchApp.fetch()はGASの機能で、渡したURLにアクセスしてくれるものです。

結果がJSONで送られてくるので、パースしてsomeループで名称が一致するまで調べます。

/* チャンネル名を検索してIDを取得 */
function channelNameToId(name) {
  var res = UrlFetchApp.fetch('https://slack.com/api/channels.list?token=' + SLACK_ACCESS_TOKEN);
  var channelsList=JSON.parse(res.getContentText());
  var foundChannelsId = '';
  var isFound = channelsList.channels.some(function(channels){
    if (channels.name.match(name)){
      foundChannelsId = channels.id;
      return true;
    } 
  });
  return foundChannelsId;
}

経過日数を指定する

files.listts_toオプションを使うと、ある時刻までのファイルだけを取得できます。 この時刻はUNIX timeで指定しなければなりません。 そこで、経過日数からts_toに変換する関数を作りました。

JavaScriptではDate.getTime()で現在時刻をUNIX時間で取得できます。 これはミリ秒単位なので1000で割る必要があるのに注意です。 あとは、日数を秒数に換算して引き算するだけですね。

function elapsedDaysToUnixTime(days){  
  var date = new Date();
  var now = Math.floor(date.getTime()/ 1000); // unixtime[sec]
  return now - 8.64e4 * days + '' // 8.64e4[sec] = 1[day] 文字列じゃないと動かないので型変換している
}

ファイル削除プログラム

事前準備

スクリプトのプロパティ

APIのtokenは、流出するとチームに好き勝手されてしまうので扱いに注意が必要です。 外部の人に見せないものでも、コードに直接書くのは考えものです。 幸い、GASには「スクリプトのプロパティ」という任意の値を保存しておける機能があるので、そこにtokenなどを格納することにします。

取得は次のようにすれば可能です。

var SLACK_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty("TOKEN");

また、削除するファイルを探すチャンネルもOLDFILEというプロパティにカンマ区切りで入力することにします。 これで、ソースコードを公開してもどんなチャンネルがあるかバレません。

f:id:lyncs:20170604130247p:plain
スクリプトのプロパティの入力例

APIを叩く

SlackのAPIを使うには、各種メソッドを叩く仕組みが必要です。 GASにはsoundTricker/SlackAppというライブラリがあり、これをインポートするだけでAPIが使えちゃいます。 ただ、最近これを使うより自前で書いたものの方がなぜか数倍速いのに気づいてしまったので、自前実装に切り替えました。

自分の環境で動けばいいので、かなり適当に書いてます。 他の環境でライブラリをお使いの方は、適宜読み替えてください。

例:

/* ファイルのリスト */
function filesList(data){
  var params = {
    'token': SLACK_ACCESS_TOKEN,
    'channel': data.channel,
    'ts_to': data.ts_to,
    'count': data.count
  }
  var options = {
    'method': 'POST',
    'payload': params
  }
  var res = UrlFetchApp.fetch('https://slack.com/api/files.list',options);
  return JSON.parse(res.getContentText());
}

ファイルを削除するスクリプト

まずはコードを。

/* 雑談チャンネル・グループの名称を検索して古いファイルを削除 */
function oldFileExecutioner(){
  const targetChannels = PropertiesService.getScriptProperties().getProperty("OLDFILE").split(",");    
  targetChannels.forEach(deleteOldFile);
}

/* 指定チャンネル内・特定日数より以前のファイルを削除 */
function deleteOldFile(channelName) {
  const days = 21;  // 遡る日数(ユーザが指定)
  
  var channelId = channelNameToId(channelName) || groupNameToId(channelName);
  if(!channelId){
    Logger.log('Not found "' + channelName + '". Skipping.');
    return -1; //見つからなければ終了
  }
  Logger.log('Found "' + channelName + '"(' + channelId + ')');
  var options = {
    channel: channelId,
    ts_to: elapsedDaysToUnixTime(days),
    count: 1000
  }
  filesList(options).files.forEach(function(val){
    data = filesDelete(val.id);
    if (data.error) Logger.log('  Failed to delete file ' + val.name + ' Error: ' + data.error);
    else Logger.log('  Deleted file "' + val.name + '"(' + val.id + ')');
  });
}

oldFileExecutioner()でプロパティから取得したチャンネル名それぞれについて、deleteOldFile(channelName)を実行しています。

チャンネルはchannels.listgroups.listの両方から探して、どちらでもなければログにエラーメッセージを残して終了します。 見つけたらchannelts_toを指定してfiles.listを叩くのですが、実はこれだけではファイル100件までしか取得できません。 そこで、countオプションに適当に大きめの値を入れておきます。

あとは各ファイルのIDをfiles.deleteに放り込むだけです。 実在するIDを入れるので普通は成功しますが、一応エラーが出たら内容をログに記録するようにしてあります。

トリガーの設定

GASには、指定したタイミングで関数を自動実行する「トリガー」という機能があります。 LYNCSでは、oldFileExecutioner()が毎日昼頃に実行されるように設定しています。 使い方はググればたくさん記事が出てくるので省略させてください。

実行例

チームのSlackでやると危ないので、テスト用の一人Slackにいろんなファイルをアップロードした上でoldFileExecutioner()を実行してみます。

f:id:lyncs:20170604130338p:plain
テスト用にアップロードしたファイルたち

スクリプトのプロパティにはtokenと、巡回してほしいチャンネルを指定しますが、わざと存在しないチャンネル名も混ぜておきます。

プロパティ
TOKEN {チームのtoken}
OLDFILE random,ch1,ch2

oldFileExecutioner()を実行して数秒待つとログを閲覧できます。

f:id:lyncs:20170604130306p:plain
実行後のログ

無事削除してくれたようです。 ch2というチャンネルは存在しないのでエラーになっています。 Slackの方を見てみるとファイルが減っていますが、#generalに上がっていたファイルは残っています。

f:id:lyncs:20170604130342p:plain
削除の対象外のファイル

展望

アーカイブ済みのチャンネルのファイルを削除する

channels.listis_archivedという項目があるので、アーカイブ済みなら中のファイルを消す、といったこともできます。

特定タイプのファイルだけを削除する

Slackでは、作成したスニペットやポストもファイル扱いです。 これらは文章データでストレージ容量にはほとんど影響ないのですが、自動削除の対象となってしまいます。

files.listで取得するファイルオブジェクトのプロパティにfiletypeという項目がある(一覧)ので、これを見て分類すれば、ファイルの種類によって動作を変えられそうです。

また、ファイルオブジェクトのsizeを見ればサイズがわかるので、一定サイズを超えていたら削除、という方針でもいいかもしれません。

(Webシステム担当 И)

LYNCSの新歓情報!

皆さんこんにちは!新年度が始まりましたね!いよいよ始まる大学生生活に夢を膨らませる新入生や,新年度の授業に期待を寄せる上級生も全力でキャンパスライフを送ってください!

さて今日の更新では宇宙科学総合研究会LYNCSの新歓企画についてご紹介します!オリエンテーション期間は終了してしまいましたが,4月末までLYNCSでは新歓活動をしています!

  • 天文本部

捉えよ、宇宙を。(公式HPより)

いやぁかっこいいですね(自画自賛).天文本部では新歓期間中に観望会(月,木星)やプラネタリウム鑑賞会を予定しています.f:id:lyncs:20170120202928j:plain 写真は昨年の観望会の様子です.日吉キャンパスで行いますが,皆さんが思っている以上に星を見ることができます.ぜひご参加ください.(詳細日程は下記をご参照ください.)

  • 衛星ミッション本部

乗り出せ、宇宙に。(公式HPより)

いやぁこちらもかっこいいですね(2回目).衛星ミッション本部では新歓期間中に機械設計入門講座(3DCADの使用法)や回路設計入門講座を予定しています.また宇宙エレベータのクライマ実験の見学も予定しています.f:id:lyncs:20170310115809j:plainf:id:lyncs:20170406234656p:plain 画像は開発しているCanSatと宇宙エレベータの写真です.初心者の方でも1から教えますのでご安心ください!

  • 理学本部

3つ目は理学本部!こちらは今年度より設立した新規本部ですが内容は保証いたします!新歓期間中はチャンドラセカール限界やε-δ論法に関する講演会を予定しています.ぜひご参加ください!(写真は新規本部であることと会員の顔が写ってしまっているためありません.ご了承ください)

  • 全体予定

長々と書きましたが詳細は以下の画像をご参照ください!f:id:lyncs:20170406233715j:plain

そしてLINE@にご連絡いただければいつでも会員が対応いたします!f:id:lyncs:20170406235514j:plain

では新歓企画でお会いしましょう! 新代表あっきーでした!

このすばらしい経験に反省を!(あっきーのPM体験記)

LYNCSは2017年種子島ロケットコンテストの部門5(CanSat部門)にすゝめプロジェクトとして出場しました. そこでPMをやらせていただいたのでその時の反省事項や自分で褒めたいところを振り返ろうと思います.

続きを読む

基礎の数学 (1)

僕が大学生になって初めて買った数学の書籍は数論〇説でした. 意気揚々と取り組んだところ読み始めて間もなく,

写像Cは\mathbb{R}-\mathbb{Q}から\displaystyle\sum_{2}^\infty\mathbb{N}への全単射である.

という定理を示すとか出てきやがって,先輩の「大学の数学の本は前提知識の導入もしてくれている」という言葉を思い出しながら悶絶しました.(この書籍は証明の行間も省きまくりでかなりしんどかったです)

数学を学ぶにしても,掛け算が分からなければ因数分解なんて理解出来やしないように,それなりの前提知識は必要なわけですたぶん.

知らない概念が出てくる度に調べるのも悪くはないですが,どうせならはじめにまとめて導入してしまいましょう,ということで大学数学を勉強し始めるにあたって必要そうな前提知識について数回に分けて生意気に記事なんかを書いてみます.


1 命題・論理

数学における命題(proposition)とは,それが正しい(真)かあるいは正しくない(偽)かのどちらに定まる主張のことである.

命題のうち,特に重要なものは定理(theorem)と呼ばれる.

命題Pに対して,\lnot PP否定(negation)という.\lnot Pは,「Pでない」ということを表し,P\lnot Pでは真と偽が正反対になっている.

2つの命題P,Qについて,「PまたはQ」という命題をP \lor Q,「PかつQ」という命題をP \land Qと書く.\lor論理和選言(disjunction),\land論理積連言(conjunction)と呼ばれる.

PまたはQが真である」というのは「PQの少なくともどちらか一方が真である」ということであり,

PかつQが真である」というのは「PQがともに真である」ということである.

PならばQ」 (P \Rightarrow Qと書く)は「Pが真であるときは必ずQも真である」という事を主張している.

Pが偽であるとき,これは何も主張していないという事になる.

また,P \Rightarrow Qが成り立つことを

  • QP必要条件(necessary condition)である.

  • PQ十分条件(sufficient condition)である.

と表現することもある.

P \Rightarrow QQ \Rightarrow Pが同時に成り立つとき,PQ必要十分条件(necessayr and sufficient condition)であるといい,P \Leftrightarrow Qと書く.

このとき,QP必要十分条件という事も出来るので,P \Leftrightarrow Qとも書ける.

P \Leftrightarrow Qが成り立つとき,「PQ同値(equivalent)である」と言う.

Q \Rightarrow P,\lnot P \Rightarrow \lnot Q,\lnot Q \Rightarrow \lnot PをそれぞれP \Rightarrow Q(converse),(inverse),対偶(contrapositive)という.

対偶と元の命題は同値である.

「任意の(全ての)nで……」(\forall n (for all)を用いる)とは「どのようなnを持ってきても……が成り立つ」という意味であり,日常で使われる用法とは少し異なる.

「……nが存在する」(\exists n (exists)を用いる)とは「……を満たすようなnが存在する」という意味である.

例えば\forall n \in \mathbb N \mspace{5mu} P(n) (任意の自然数nP(n)が真)は,「自然数のうちどんなnを持ってきてもPは真である」という事を主張している.


\mathbb N自然数全体の集合を表す際によく用いられる.

他にも,

  • 実数全体の集合を表すのに\mathbb R

  • 整数全体の集合を表すのに\mathbb Z

  • 有理数全体の集合を表すのに\mathbb Q

  • 複素数全体の集合を表すのに\mathbb C

がよく用いられる.

\inは集合について用いられる記号であるが,詳しくはまた別に説明する.


\forall n \in \mathbb N \mspace{5mu} P(n)の否定を考える.

これは「Pが偽になる自然数が少なくとも1つでもある」ことなので,「Pが偽になるような自然数nは存在する」と言い換えることができる.

則,\exists n \in \mathbb N \mspace{5mu} \lnot P(n)と書くことができ,\forall\existsが入れ替わっていることが分かる.


参考

斎藤正彦(2002)『数学の基礎 集合・数・位相』東京大学出版会.

中島匠一(2000)『代数と数論の基礎』共立出版.

(おにゃ)

MATLABで生成したFigureを画像として自動で保存する

MATLABくんでちょっとしたシミュレーションやら何やらを行う時、毎回コード上の数値をちょこっと変えてみて何度もスクリプトを実行……なんてケースがあると思いますが、plotしたグラフをFigureウィンドウからいちいち保存するのが面倒ですよね。そんな時に自動で保存出来るようにする何かです。

saveas

特定のファイル形式への Figure の保存 - MATLAB saveas - MathWorks 日本

公式チュートリアルにも出てると思いますが、Figureを保存する時はsaveasを使えばOKです。以下のコードをplotが完了した直後に入れてみてください。保存先はスクリプトがあるのと同じフォルダになります。

saveas(gcf,'chart.png')

gcfについてはこちらを参照ください。現在のFigureを示すもの、くらいに思っていただければ。

現在の Figure ハンドル番号の取得 - MATLAB gcf - MathWorks 日本

で、1回だけならこのコードで大丈夫なんですが、実行の度に"chart.png"というファイル名で保存を試みると(当たり前ですが)ファイル名が重複してしまうので以下のようなエラーが出ます。

エラー: your_matlab_file (line 50)
saveas(gcf,'chart.png')

「毎回コード上の数値をちょこっと変えてみて何度もスクリプトを実行」というケースには使えないので工夫が必要です。

ファイル名に日時を入れる

連番入れてあげるのもいいですが、手っ取り早く日時を入れてあげましょう。
MATLABdatetime('now')で現在日時を取得できますが、こいつには:が含まれていてファイル名にはできないのでその辺を置換します。 (ついでにスペースとかも個人的な好みでアンダーバーに置換えてます。)

FileName = strrep(strrep(strcat('chart_',datestr(datetime('now')),'.png'),':','_'),' ','_')
saveas(gcf,FileName)

これを実行するとchart_07-Nov-2016_01_54_45.pngといった形式でファイルが保存されます。

(ゆん)