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

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

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

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

とういうことは、、、、どうやって他サイトでWeb公開しているデータをとってくればよいの？という問題から取り組むことになります。`SolitonNK`に内蔵されている[`gravwell`エンジンのブログの解説](https://www.gravwell.io/blog/using-the-http-post-ingester-to-build-a-weather-dashboard)を見ると、

	#!/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`以外にもう１ホスト常時稼働サーバを作らねばならないということになり、それでは運用コストが跳ね上がってしまいます。そこで、`SolitonNK`の`Anko`スクリプトで、上記`curl`コマンドの機能を作り、スケジュール検索機能で定期的にそれを起動させるという方針でやってみることにしました。

# `Anko`スクリプト

さて、気象庁が公開している[「最新の気象データ」`CSV`](https://www.data.jma.go.jp/obd/stats/data/mdrr/docs/csv_dl_readme.html)の 「時間降水量 最新データ」：  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`は直接`POST`の`body`に持っていく他はありません。

### 【ちょっと待て！１】　`resp.Body.Close()` が１回しかできてないのは？
[goのnet/httpでのやらかし事例](https://qiita.com/stk0724/items/dc400dccd29a4b3d6471)などを読んで考えるに、2カ所に対してコネクションを張りながら、1回しかコネクションを閉じてないということは、反復使用しているうちにリソースを使い尽くしてしまうはずです。2つのサイトへのコネクションはそれぞれ閉じないといけません。

## ２回目に書いたスクリプト（やっと使えるようになりました）

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

	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.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/8f9c042f-837d-a7c4-c860-0140cae435b6.png "メニューから「リソース」を選択")
図１：メニューから「リソース」を選択
![リソースの追加2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/9fc7ac43-db22-ea34-fc05-dcd99839fd21.png "リソース追加")
図２：リソース管理画面の右上の追加ボタン（初回は真ん中の「リソース追加」ボタンでも良い）からリソース追加
![リソースの追加3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/41232489-2014-6850-0f44-484cd16f94ee.png)
図３：新規リソース追加の際は、リソースの名前と、リソースの説明（使用方法など）を記述する
![リソースの追加4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/501336a5-f4b0-618a-4fbe-673cc274491b.png "リソース登録後の画面
")
図４：リソース登録後の画面


# `http `インジェスター

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

各種インジェスターの設定は、`SolitonNK`の管理サイトの方にログインします。
![httpインジェスターの開始1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/b94b9020-8452-c562-dcbf-0f2ed65cdb43.png "管理サイトのログイン画面")
図５：管理サイトのログイン画面

管理サイト画面左上の「サービス」タブによって開いた「データ収集/分析」画面の、「HTTPサーバー」という欄が`http`インジェスターの設定欄です。`http`インジェスターが上記の`Anko`スクリプトからの`http POST`によって受け取るURLをここで設定します。`Anko`スクリプトでデータを`POST`させた`"SolitonNK.example.jp:80/snk"`の、ポート番号と`/`以下のURLをここに記述します（デフォルトはポート`80`、URLは`/snk`です）。
![httpインジェスターの開始2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/8c2dbead-62f3-1ac0-1a7a-775310d6ad92.png "httpインジェスターの設定画面")
図６：`http`インジェスターの設定画面


`HTTP`サーバーを「起動する」の欄にチェックを入れたら、左下の「適用する」ボタンを押して、`SolitonNK`のサービスを再起動させると、`http`インジェスターが稼働するようになります。再起動後、画面右上の「設定を保存」をクリックして、設定保存するのも忘れないようにしてください。
![httpインジェスターの開始3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/7d1be476-6fad-40e3-67d4-d5a76e5d5c53.png "設定の反映と設定の保存")
図７：設定の反映と設定の保存

ここで試しに、検索欄で `tag=* limit 1 | anko getjma2 | table`を実施すると、Status1の欄とStatus2の欄が両方とも`200 OK`になっています。これは、気象庁サイトからのhttp GETの実行ステータスも、httpインジェスターへのhttp POSTの実行もステータス「正常」だったことを示しています。
![スクリプト実行結果確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/b545a3e3-04c2-b113-c26a-6aa6348f8a1f.png "Ankoスクリプトの実行確認")
図８：`Anko`スクリプトの実行確認


# 定期実行（検索のスケジュール）

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

まずSolitonNKのメニュー画面から「スケジュール検索」を選択します。
![スケジュール検索1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/33df7b9d-22ad-2f13-6dd2-fc0912663e3c.png "メニュー画面から「スケジュール検索」を選択")
図９：メニュー画面から「スケジュール検索」を選択

「スケジュール検索」画面で、右上または中央の「追加」ボタンで、新規のスケジュール検索を設定できます。
![スケジュール検索2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/8a9fb5de-63e5-679a-24ba-c4232f7502dc.png "「スケジュール検索」画面")
図１０：「スケジュール検索」画面

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

![スケジュール検索3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/60b70b38-5f60-1cf5-05f0-ad72ebd647e8.png "検索のスケジュール登録画面")
図１１：検索のスケジュール登録画面

検索スケジュールを登録すると、図のような画面になります。
![スケジュール検索4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/c4f7cc0d-9626-76eb-a590-12fd081bb838.png "検索のスケジュール登録後画面")
図１２：検索のスケジュール登録後画面

検索のスケジュール登録後画面の、「最終実行」の欄の"Results"欄をクリックすると実行された検索の結果が表示されます。
![スケジュール検索5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/dede9e1c-ff51-8485-ac50-4f5d37785e19.png)

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

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

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

	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, "", "", "sender@example.com", ["receiver@example.com"], "テスト", "成功"+body)

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

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

![SolitonNKメール設定なし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/ea8615c7-1749-f0d3-d99e-ae799d69cabe.png "メールの管理画面とメール送信ログ")
図１３：メールの管理画面とメール送信ログ

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

気象庁の時間降水量 最新データは図のように日本全国各地の気象データが`csv`形式で記載されています。
![気象庁時間降水量 最新データ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/5403bd84-b384-fc87-1dd5-572b49eea5bd.png "気象庁の時間降水量 最新データ")
図１４：気象庁の時間降水量 最新データ

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

![文字化けしたデータ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/9157798b-9b09-a3a9-6673-f09509ef2bad.png)
図１５：文字化けしたデータ

せめて、`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` とやってみましたが、最初の行が表示されるだけです。

![うまくいかないcsv表示.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480441/d220f611-65ab-6a04-cd36-88f8afda31a4.png)
図１６：`csv`表示もうまくいかず……


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

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

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


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

https://www.solitonnk.com/

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