SolitonNKを使ってオープンデータを活用するぞ ステップ1


近年、誰もが自由に使える公共的なデータが、種々の官民組織から公開されています。最近のトピックスで言えば、厚労省が「新型コロナウイルス感染症の現在の状況と厚生労働省の対応について」として、日本国内のPCR検査状況、患者数、退院・療養解除者数、死者数を日々公開しています。

これらのデータの多くはWebページないしWeb APIで公開されているので、「httpでデータを取得し、SolitonNKのインデクサーに蓄積し、分析にかけられるようにしたい」、という要求に応えるべく、そのやり方を探ってみました。SolitonNK初級の筆者には、トライ&エラーとなって苦戦することになってしまいましたが、その分難所を押さえつつステップバイステップで解説できるものと思います。ということで、本記事はその第1ステップです。

httpでデータを取得し、SolitonNKのインデクサーに蓄積」するなら、当然 httpインジェスターの出番だねと思えたのですが、httpインジェスターには「他のウェブサイトからデータを取ってくる」という機能は備わっていません。httpインジェスターが公開しているWebインターフェースに、POSTメソッドで入力されたデータを、インデクサーに蓄積するのが、httpインジェスターの機能なのです。

とういうことは、、、、どうやって他サイトでWeb公開しているデータをとってくればよいの?という問題から取り組むことになります。SolitonNKに内蔵されているgravwellエンジンのブログの解説を見ると、

#!/bin/sh
curl "https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00_rct.csv" | curl SolitonNK.example.jp:80/snk -X POST -d @-

という具合にやればデータを取り込めるので、cronでこのコマンドを定期的に走らせばよいと記述されています。

が、上記の方法を実行するためには、SolitonNK以外にもう1ホスト常時稼働サーバを作らねばならないということになり、それでは運用コストが跳ね上がってしまいます。そこで、SolitonNKAnkoスクリプトで、上記curlコマンドの機能を作り、スケジュール検索機能で定期的にそれを起動させるという方針でやってみることにしました。

Ankoスクリプト

さて、気象庁が公開している「最新の気象データ」CSVの 「時間降水量 最新データ」: https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00_rct.csv をとってきて、httpインジェスターの受け口として用意した solitonNK.example.jp:80/snkにデータを http POSTするAnkoスクリプトは次のように書けます。

最初に書いたスクリプト(一見動作しますが、……)

var http = import("net/http")
var strings = import("strings")
var buf = import("bytes")
var url1 = "https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00_rct.csv"
var url2 = "http://solitonNK.example.jp/snk"
func Process() {
    req, _ = http.NewRequest("GET", url1, nil)
    req.Header.Add("Content-Type", "application/octet-stream")
    resp, _ = http.DefaultClient.Do(req)
#    resp.Body.Close()
    setEnum("Status1", resp.Status)
    req, _ = http.NewRequest("POST", url2, resp.Body)
    req.Header.Add("Content-Type", "application/octet-stream")
    resp, _ = http.DefaultClient.Do(req)
    resp.Body.Close()
    setEnum("Status2", resp.Status)
    return true
}

url1で指定したサイト(気象庁サイト)からGETで取ってきたデータのresp.Bodyは、io.Reader型のデータなのですが、SolitonNKからはAnko"io/ioutil"モジュールも、"io"モジュールも使えないため、ioutil.ReadAll()も、buf.ReadFrom()も、io.Copy()も使えないことから、resp.Bodyは直接POSTbodyに持っていく他はありません。

【ちょっと待て!1】 resp.Body.Close() が1回しかできてないのは?

goのnet/httpでのやらかし事例などを読んで考えるに、2カ所に対してコネクションを張りながら、1回しかコネクションを閉じてないということは、反復使用しているうちにリソースを使い尽くしてしまうはずです。2つのサイトへのコネクションはそれぞれ閉じないといけません。

2回目に書いたスクリプト(やっと使えるようになりました)

http.DefaultClient.Do(req)のレスポンスを格納する変数を2つ用意して、それぞれ別々に閉じられるようにスクリプトを次のように書き直しました。

var http = import("net/http")
var strings = import("strings")
var buf = import("bytes")
var url1 = "https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00_rct.csv"
var url2 = "http://solitonNK.example.jp/snk"
func Process() {
    req, _ = http.NewRequest("GET", url1, nil)
    req.Header.Add("Content-Type", "application/octet-stream")
    resp_get, _ = http.DefaultClient.Do(req)
    setEnum("Status1", resp_get.Status)
    req, _ = http.NewRequest("POST", url2, resp_get.Body)
    req.Header.Add("Content-Type", "application/octet-stream")
    resp_post, _ = http.DefaultClient.Do(req)
    resp_post.Body.Close()
    setEnum("Body", resp_post.Body)
    resp_get.Body.Close()
    setEnum("Status2", resp_post.Status)
    return true
}

上記のAnkoスクリプトをエキストエディタで記述したら、適当な名前(図例ではGetJMA4.ank.txt)で保存します。そして、SolitonNKの「リソース」に手元に保存したスクリプトファイルを適当な名前をつけて(例ではgetjma2)追加します。


図1:メニューから「リソース」を選択

図2:リソース管理画面の右上の追加ボタン(初回は真ん中の「リソース追加」ボタンでも良い)からリソース追加

図3:新規リソース追加の際は、リソースの名前と、リソースの説明(使用方法など)を記述する

図4:リソース登録後の画面

httpインジェスター

Ankoスクリプトが用意できたら、次にデータの受け口となるhttpインジェスターを稼働させます。

各種インジェスターの設定は、SolitonNKの管理サイトの方にログインします。

図5:管理サイトのログイン画面

管理サイト画面左上の「サービス」タブによって開いた「データ収集/分析」画面の、「HTTPサーバー」という欄がhttpインジェスターの設定欄です。httpインジェスターが上記のAnkoスクリプトからのhttp POSTによって受け取るURLをここで設定します。AnkoスクリプトでデータをPOSTさせた"SolitonNK.example.jp:80/snk"の、ポート番号と/以下のURLをここに記述します(デフォルトはポート80、URLは/snkです)。

図6:httpインジェスターの設定画面

HTTPサーバーを「起動する」の欄にチェックを入れたら、左下の「適用する」ボタンを押して、SolitonNKのサービスを再起動させると、httpインジェスターが稼働するようになります。再起動後、画面右上の「設定を保存」をクリックして、設定保存するのも忘れないようにしてください。

図7:設定の反映と設定の保存

ここで試しに、検索欄で tag=* limit 1 | anko getjma2 | tableを実施すると、Status1の欄とStatus2の欄が両方とも200 OKになっています。これは、気象庁サイトからのhttp GETの実行ステータスも、httpインジェスターへのhttp POSTの実行もステータス「正常」だったことを示しています。

図8:Ankoスクリプトの実行確認

定期実行(検索のスケジュール)

さて、気象庁の「時間降水量 最新データ」 https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00_rct.csvは直近1時間のデータです。このデータを1時間毎に定期的に取得してSolitonNKに蓄積すれば、降水量の変化データが蓄積できることになります。上で作ったAnkoスクリプトを1時間に1回実行する、スケジュール検索を設定しましょう。

まずSolitonNKのメニュー画面から「スケジュール検索」を選択します。

図9:メニュー画面から「スケジュール検索」を選択

「スケジュール検索」画面で、右上または中央の「追加」ボタンで、新規のスケジュール検索を設定できます。

図10:「スケジュール検索」画面

定期的に実行させたい検索文(例では、tag=http limit 1 | anko getjma2 | table)を記入し、「スケジュール」欄で検索の起動スケジュール(例では毎時0分に実行)を設定します。この設定記述方法はcronの方法と同じです。「時間範囲」は、検索を起動してから終えるまでの時間の指定、その他にこの登録しようとしてるスケジュールについて名前と説明文を記述することができます。検索スケジュールの設定が記入できたら、「保存」ボタンで登録できます。登録とともにこの検索スケジュールを実施する場合には、「保存後に実行」にチェックを入れます。


図11:検索のスケジュール登録画面

検索スケジュールを登録すると、図のような画面になります。

図12:検索のスケジュール登録後画面

検索のスケジュール登録後画面の、「最終実行」の欄の"Results"欄をクリックすると実行された検索の結果が表示されます。

【ちょっと待て!2】:検索文だけではなく、ここではスクリプトを登録することができます。ここで登録されるスクリプトは検索から呼び出すスクリプトと同じ制限なのでしょうか?ひょっとしてもっと自由度高く簡便にスクリプト記述できるのではないでしょうか?

「スケジュール検索」でスクリプトを登録する場合、スクリプト記述方法が異なります。まず、func Process(){return} を使わなくて良くなります。検索結果を返さなくて良い(表示インターフェースがない)ので、setEnum()は不要です。そして、httpGet(url), httpPost(url, content-type, body), sendMail(hostname, port, username, password, from, to, subject, message)が使えるようになります。なお、"io/ioutil"モジュールも、"io"モジュールも使えないという制限は変わりません。そこで、スクリプトを「スケジュール検索」に登録することを前提に、書き直してみましょう。

3回目に書いたスクリプト(「スケジュール検索」でスクリプトを登録する場合用)

var url1 = "https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00_rct.csv"
var url2 = "http://solitonNK.example.jp/snk"
body, err = httpGet(url1)
httpPost(url2, "text/html; charset=shift_jis", body)
# sendMail("localhost", 25, "", "", "[email protected]", ["[email protected]"], "テスト", "成功"+body)

とってもシンプルに書けてしまいます。上で苦労したのは何だったんでしょう……。なお、httpGET()で得た内容をメールで確認したい場合は上のスクリプトで、コメントアウトを外していただければ、メール送信してくれます。

なお、smtpサーバにlocalhostを指定してメール送信にすることについては、特に管理設定は要りません。


図13:メールの管理画面とメール送信ログ

【ちょっと待て!3】:インデクサーに蓄積された内容を確認してみると……

気象庁の時間降水量 最新データは図のように日本全国各地の気象データがcsv形式で記載されています。

図14:気象庁の時間降水量 最新データ

これがSolitonNKのインデクサーにきちんと蓄積されているかtag=http limit 1 | table
として確認してみましょう。あれれ?酷い文字化けです。(メール送信した場合もデータ部分は文字化けしています)


図15:文字化けしたデータ

せめて、csvの元の表の形に整形できないかと思ってtag=http limit 1 | csv [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25] [26] [27] [28] [29] | table 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 とやってみましたが、最初の行が表示されるだけです。


図16:csv表示もうまくいかず……

【ステップ2への課題】:文字コードのハンドリングはどうすればよいのか。

SolitonNKはマルチバイト文字を内部的にはUnicodeで、入出力はUTF-8でハンドリングしています。一方、気象庁サイトのデータはS-JISで記述されているので、コード変換が必要となります。

ということで、ここまで外部のWebの情報をインデクサーに蓄積するまでをステップ1の本記事とします。そして、文字化けを修正し、インデクサーから分析できる形で抽出するのが、次回記事以降の課題ということにして、ステップ2の記事にご期待ください。

レファレンス

Soliton NKを試したい人はの下の Soliton NK 公式サポートサイトでお申し込み下さい。

Soliton NK について、今後記事を追加していく予定です。