環境省の花粉情報取得APIを使ってみた

# はじめに

（2022年1月23日追記：環境省が2022年からは花粉情報の観測・提供を停止することをアナウンスしておりました。https://www.env.go.jp/press/110339.html
そのため、下記記事のAPIは動作しないかもしれません。）

花粉症の方なら皆さんご存知のサイト「[環境省花粉観測システム（愛称：はなこさん）](http://kafun.taiki.go.jp/index.aspx)」が2021年3月29日に[新たなサイト](https://kafun.env.go.jp/)にリニューアルされていました。

よくよく見ると、「[環境省花粉観測システムのAPI](https://kafun.env.go.jp/apiManual)」なるものが公開されていましたので、早速使ってみました。

追記：上記APIは2021年4月13日から障害を起こしているようで、503エラーを返すようになってしまいました。窓口にメールしてみたのですが、2021年4月18日現在、復旧されていません。他の取得方法についても追記しておきました。

# APIの概要

実は[APIの説明ページ](https://kafun.env.go.jp/apiManual)は存在するのですが、JSONで取得できることと下記のパラメータ説明以外はあまり詳しい説明がありません。

##パラメータ

|パラメータ名称|URLに入力する名前|必須|値の例|備考|
|:-:|:-:|:-:|:-:|:-:|
|取得開始年月|Start_YM|○|202003|
|取得終了年月|End_YM||202005|指定しない場合は現在の年月|
|都道府県コード|TDFKN_CD|○|03|01～47のいずれか|
|測定局コード|SKT_CD||03201070,03201550|複数の場合はカンマで区切る|

##URLの例

「2020年3月」から「2020年6月」までの「東京都」に於けるデータを取得するURL例：

[https://kafun.env.go.jp/hanako/api/data_search?Start_YM=202003&End_YM=202006&TDFKN_CD=13]
(https://kafun.env.go.jp/hanako/api/data_search?Start_YM=202003&End_YM=202006&TDFKN_CD=13)

##都道府県コード

説明はありませんが、どうも[こちら](https://nlftp.mlit.go.jp/ksj/gml/codelist/PrefCd.html)の数字のようです。行政関連ではよく使われるコードなのですかね。

##測定局コード

一覧情報は見つかりませんでしたが、[最新時報値の全局データ表](https://kafun.env.go.jp/newStationDataTable)のページで各測定局の当日の表をクリックすると別タブで表示される表のページのURLに含まれています。

例）https://kafun.env.go.jp/preview/table/50110100/today


#APIレスポンス

##JSON形式

APIを呼び出してみると確かにJSONっぽいものが返ってきます。

しかしこれが少々曲者で、下記のように形式になっています。

* 文字コードが今どきSHIFT_JIS
* key: valueのvalueの文字列がクォーテーションで囲まれていない

このままではJSON文字列としてパースできないので、少し加工が必要です。

##データの種類

こちらも詳しい説明はありませんが、各観測所の表データと見比べてみると下記のようです。

|key名|意味|value例|
|:-:|:-:|:-:|
|SKT_CD|測定局コード|52910100|
|AMeDAS_CD|アメダスコード|64036|
|SKT_NNGP|年月日|20210301|
|SKT_HH|時刻|01|
|SKT_NM|測定局名|奈良県産業振興総合センター|
|SKT_TYPE|測定局のタイプ|1|
|TDFKN_CD|都道府県コード|29|
|TDFKN_NM|都道府県名|奈良県|
|SKCHSN_CD|市町村コード|29201|
|SKCHSN_NM|市町村名|奈良市|
|KFN_NUM|花粉数|45|
|AMeDAS_WD|風向|11|
|AMeDAS_WS|風速|1|
|AMeDAS_TP|気温|9.0|
|AMeDAS_PR|降水量|0|
|AMeDAS_RDPR|レーダー降雨降雪の有無|0|


# 花粉情報の取得処理例

Javaの処理例：

```Java
final DateFormat start_df = new SimpleDateFormat("yyyyMM");
final Date date = new Date(System.currentTimeMillis());
String startDateStr = start_df.format(date);

DefaultHttpClient httpClient = new DefaultHttpClient();
String url = "https://kafun.env.go.jp/hanako/api/data_search?Start_YM=" + startDateStr + "&End_YM=" + startDateStr + "&TDFKN_CD=29&SKT_CD=52910100";
HttpGet request = new HttpGet( url );
HttpResponse response = httpClient.execute(request);
if ( response.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
	// エラー
}

BufferedReader br = new BufferedReader( new InputStreamReader(response.getEntity().getContent() ,"SHIFT_JIS") );
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
	sb.append(line);
}
String badJsonStr = sb.toString();
br.close();

response.getEntity().consumeContent();

String goodJsonStr = badJsonStr.replace("{", "{\"").replace(":", "\":\"").
		replace( ",", "\",\"").replace( "}", "\"}").replace( "}\",\"{", "},{");

JSONArray jarray = new JSONArray(goodJsonStr);
for (int i = 0; i < jarray.length(); i++ ) {
	JSONObject json = jarray.getJSONObject(i);
	String dataDate   = json.getString("SKT_NNGP");
	String hour   = json.getString("SKT_HH");
	String kafunNum   = json.getString("KFN_NUM");
	String fuusoku   = json.getString("AMeDAS_WS");
	String kion   = json.getString("AMeDAS_TP");
	String kousui   = json.getString("AMeDAS_PR");
}
```

Google App Scriptの処理例：

```javascript
let todayDate = new Date();
let year = todayDate.getFullYear();
let month = todayDate.getMonth() + 1;
let startEndDate;

if ( month < 10 ) {
	startEndDate = "" + year + "0" + month;
} else {
	startEndDate = "" + year + month;
}
if ( day < 10 ) {
	todayDateStr = startEndDate + '0' + day;
} else {
	todayDateStr = startEndDate + day;
}

let url = "https://kafun.env.go.jp/hanako/api/data_search?Start_YM=" + startEndDate + "&End_YM=" + startEndDate + "&TDFKN_CD=29&SKT_CD=52910100";

var response = UrlFetchApp.fetch(url);

let badJsonStr = response.getContentText("shift-jis");
let goodJsonStr = '{ "data": ' + badJsonStr.replace( /{/g, '{"' ).replace( /:/g, '":"' ).replace( /,/g, '","' ).replace( /}/g, '"}' ).replace( /}","{/g, '},{' ) + ' }';

let kafunInfoJson = JSON.parse( goodJsonStr );

for (let i = 0; i < kafunInfoJson.data.length; i++ ) {
	let json = kafunInfoJson.data[i];
	let dataDate   = json.SKT_NNGP;
	Hour   = json.SKT_HH;
	KafunNum   = json.KFN_NUM;
	let fuusoku   = json.AMeDAS_WS;
	Kion   = json.AMeDAS_TP;
	let kousui   = json.AMeDAS_PR;
}
```

#追加情報

「はじめに」に記載した通り、上記APIは2021年4月18日現在、障害のため、使えません。

他にデータ取得方法がないか調べたところ、下記のようなURLで花粉情報が取得できることがわかりました。

[https://kafun.env.go.jp/data/sokutei/kafunTime/52910100/today.csv?_=1618720784260](https://kafun.env.go.jp/data/sokutei/kafunTime/52910100/today.csv?_=1618720784260)

「52910100」は測定局コード、末尾の数字は現在時刻のミリ秒で表した数字を入れればよいようです。

「today」で今日、「7day」にすると直近７日間、「season」にすると今年のシーズンのデータが取得できるようです。

呼び出すと、下記のようなCSV形式のデータがBOM無しUTF-8で返ってきます。

```csv
測定局コード,アメダス測定局コード,年月日,時,測定局名称,都道府県コード,都道府県名,市区町村コード,市区町村名,花粉飛散数,風向,風速,気温,降水量,レーダー降雨降雪の有無
52910100,64036,20210418,01,奈良県産業振興総合センター,29,奈良県,29201,奈良市,4,南西,2,13.0,0,無し
52910100,64036,20210418,02,奈良県産業振興総合センター,29,奈良県,29201,奈良市,20,西,3,12.8,0,無し
```

こちらもある種APIと呼んでよいかと思います。というか、こちらの方が今日のデータだけを取るだけならデータ量も小さくて使い勝手よいです。こちらはHPで表示に使われているからか、きちんとメンテされているようです。

#所感

実は私は花粉量を通知する[「花粉チェッカー」というAndroidアプリ](https://play.google.com/store/apps/details?id=jp.gr.java_conf.mhirosawa.kafunchecker&hl=ja&gl=US)を開発してGoogle Playで公開しています。

2021年3月29日の10時からデータが更新されないなと思って、はなこさんのページを確認したところ、2021年3月29日の10時から新しいドメインの新しいサイトに移行された旨が記載されていました。

「聞いてないよー」と思いましたが対応策を調べてみたところ、APIが公開されていることがわかりました。取り急ぎ、それを使うように急ぎ改修しました。

以前は各観測所の当日のデータを取得するために表を表示するページからスクレイピング処理していました。その表はURLを直接叩いても取得できず、セッションcookieなどを上位のページで取得してからでないと表を取得することができず、処理が面倒でした。

今回、制約の無いAPIが無償で公開されたのは非常にありがたいです。環境省の皆様、ありがとうございます。

ただ、データ取得範囲指定を月単位だとデータ量が多くて通信に時間がかかるので、できれば日単位でも指定できるようにしていただけますと幸いです。

なお、はなこさんのページだと毎時40分ころに表が更新されるのですが、このAPIだと10-20分ぐらい早く取得できるようです。よりリアルタイムな値が取得できるのは嬉しいところです。