Google Apps Script (GAS) でスクレイピング~気象庁のページにある過去の気象データをスプレッドシートに書き込む実験~

MAC

こんにちは。

今回は前回の続き。Google Apps Script(GAS)を使って、スクレイピングの実験です。

今日の対象は気象庁のデータ。

気象庁は「過去のデータ検索」から、エリアごとの過去の気象データを調べることができるんですよね。

今回は、特定の地域の「1日ごとのデータ」をGASを使ってスプレッドシートに自動で書き込むようにしてみます。

Google Apps Script(GAS)とメリットについては前回記事をご参照ください。

なお、今回参考にしたサイトはこちら。

[ JavaScript] splitの振る舞いとcsvを配列に格納するコード

[JavaScript] splitの振る舞いとcsvを配列に格納するコード | きほんのき
JavaScriptのsplitメソッドは指定した区切り文字で文字列を分割し、配列に格納してくれる。引数には正規表現も指定できる。以下はテキストエリアに入力されたcsv形式のテキストを、splitを利用し二次元配列に収めるコード。

JavaScriptでHTMLタグを削除する正規表現

JavaScriptでHTMLタグを削除する正規表現 - Qiita
RSSを引っ張ってきて表示する時にテキストだけ取得したくて、データに含まれているHTMLタグを削除したかった。 メモ。 ```js var str = '今日はとても良い天気だと思うよ?...

JavaScriptで配列から特定の要素を削除する方法

JavaScriptで配列から特定の要素を削除する方法 - Qiita
# はじめに 配列から特定の要素を削除するにはいくつか方法があります。 単純に先頭の要素を削除するにはshift()で実現できます: ```js var ary = ; conso...

気象庁のページにある過去の気象データをGoogle Apps Script(GAS)でスプレッドシートに書き込む

使うもの

Google スプレッドシート

Google スプレッドシート: ログイン
Google スプレッドシートには、無料の Google アカウント(個人ユーザー向け)または G Suite アカウント(ビジネス ユーザー向け)でアクセスできます。

今回もお世話になります。

他の形式で吐き出すこともできますが、調査対象を簡易マスタにまとめて使いますので今回のスクリプトには必須です。

Google Apps Script

スプレッドシートの個別編集画面の「ツール」から「スクリプトエディタ」という手順で起動しましょう。

Googleスプレッドシートの構造

以下の通り。

[1]「data」シート

image

見出しだけつけておきましょう。

基本的には、気象庁のテーブル構造に合わせて構成しています。

注意したいのは、エリアごとに微妙に取得できる情報の差異があること。

例えば同じ東京でも東京エリアはこれに加えて気圧や湿度などのデータを取得できるため、列が増えます。

なお、O列の「ブロックID」~「都道府県名」については、気象庁のエリア分けにかかわる場所です。

今回は練馬だけですが、他地域と比較して集計する場合などに有用。

S列のweekdayは曜日を入力する列。T列は「今日から数えて何日前か」という数字を入れます。

曜日集計、前月比較、前週比較などなど他スプレッドシートやGoogle Data Studioとの連携を見越してつけてみました。

[2]「master」シート

image

dataシートに書き込むための設定的なものと、GASの速度向上を狙って作ってみた簡易マスタシートです。

setValueやらgetRangeやら、とにかくスプレッドシートAPI関連の呼び出しが多いと実行速度低下であっという間に5分過ぎてエラーになってしまうため、

スプレッドシート側でできる作業は極力スプレッドシートに任せ、setValuesなどを積極的に使ってAPI呼び出し回数を減らしています。

H2セルをデータ取得の基準日として設定。

黒枠のセル以外は全て計算式、基準日の黄色いセルはGASの中で日付を書き換えることによって全自動での対応を実現しています。

ちなみに、練馬の17年10月の気象情報を見る場合は

http://www.data.jma.go.jp/obd/stats/etrn/view/daily_a1.php?prec_no=44&block_no=1002&year=2017&month=10&day=&view=

こちらのURL。

URLのパラメーターの

・prec_no

・block_no

・year

・month

で、それぞれ書き出す情報を制御しているのが分かりやすく示されています。

「year」「month」をそれぞれ書き出すと、

I2には次のスクリプト起動の際に、過去に遡るか、未来を見ていくかを設定。

今回は1カ月ごとに過去に遡る設定にしてみました。

J2には、端的に言えば「その月は何日あるか?」という数字をもとめる数式です。

H2に取得する月の最初の日を指定することで、その月が何日存在しているか数字で出すことができます。

これは配列で何行目まで書き込むかなど、配列/スプレッドシートの書き込みの制御で使う数字ですね。

1行目・2行目については基礎的な設定。

A列,B列については気象庁から取得するHTMLのURLに関わる設定。

C列~F列についてはdataシートにそのまま書き込む文字列です。

GASの中で作ってもよかったのですが、GASばかりに何でもやらせるよりもこっちの方が今はスクリプトを組む時間が短くなったので今はこのやり方で。

Google Apps Script で検索結果件数を取得するコード

以下、「var bookurl_work = “https://docs.google.com/spreadsheets/●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●”;」の部分は、ご自身のスプレッドシートのURLに変更してください。

また、記録シート側の列数を変えるなどのカスタマイズ時には「range_data = sheet_data.getRange(lastRow_data,1,lastRow_data,4);」の最後の4も見直してください。

変数の役割とか整理しながら書いたので、かなり冗長です。やろうと思えば多分現行の2/3くらいにはまとめられるはず。

以下のスクリプトを、「トリガー」の分タイマーで毎分動かせば、1エリアごとに約8時間30分程度で最近のデータから1976年のデータまで取れます。


function getData_meteologicalAg(){
     //変数宣言(スプレッドシート側) masterが取得したいデータを記載したマスタ、dataが取得したいデータを取っていくデータシート。
     var bookurl_work = "https://docs.google.com/spreadsheets/d/●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●";
     var book_work = SpreadsheetApp.openByUrl(bookurl_work);
     var sheet_data = book_work.getSheetByName("data");
     var sheet_master = book_work.getSheetByName("master");
     var data_startRow = sheet_data.getLastRow()+1;

//変数宣言(HTML取得関係)
     var opt = {"contentType":"text/html;","method":"get"};
     var data_html = "";
     var content_html ="";
     var postText = "";
     var url_gethtml = "http://www.data.jma.go.jp/obd/stats/etrn/view/daily_a1.php?prec_no=";
     var url_par_pref = sheet_master.getRange(2,5).getValue();//都道府県コード
     var url_par_block = sheet_master.getRange(2,3).getValue();//ブロックコード
     var url_par_prefName = sheet_master.getRange(2,6).getValue();//都道府県
     var url_par_blockName = sheet_master.getRange(2,4).getValue();//ブロック
     var url_par_year = sheet_master.getRange(2,1).getValue();
     var url_par_month = sheet_master.getRange(2,2).getValue();
     var standardDate = sheet_master.getRange(2,7).getValue();//日付入力
     url_gethtml = url_gethtml + url_par_pref + "&block_no=" + url_par_block + "&year=" + url_par_year + "&month="+ url_par_month + "&day=&view=";

//変数宣言(分割処理と書き込み関係)
   var Middle_Arr=[];
   var table_Arr = [];
   var rows =2 + sheet_master.getRange(2,10).getValue();
   var data_endRow = table_Arr.length;
   var cols = 17;
   var masterdate;
   var masterblock;
   var date_sabun;
   var nextDate = sheet_master.getRange(2,9).getValue();//次のスクリプト動作のため
 //繰り返し開始箇所
 //html取得・整形
 data_html = UrlFetchApp.fetch(url_gethtml ,opt);
 content_html = data_html.getContentText();
 postText = getStringSlice(content_html, "<table id='tablefix1' class='data2_s'>",'<div class="print" style="margin-top:1em">');
 postText = postText.replace(/<\/th>/g, '</th>,');
 postText = postText.replace(/<\/td>/g, '</td>,');//
 postText = postText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'');
 postText = postText.replace( /\r\n/g , "\n" );        //IE対策 改行コード\r\nを\rに変換
 postText = postText.replace( /^(\n+)|(\n+)$/g , "" );    //文頭と文末の余計な改行を除去
 postText = postText.replace(/\/\/\//g, '');
 Middle_Arr = postText.split( /\n/g );
 table_Arr = [];
 for( var i = 0 , l = Middle_Arr.length ; i < l ; i++ ){
 table_Arr[i] = Middle_Arr[i].split(",");
 for( var j = 0 , m = table_Arr[i].length ; j < m ; j++ ){//trimも同時に行っておく
 table_Arr[i][j] = table_Arr[i][j].replace( /(^\s+)|(\s+$)/g , ""  );
 }
 }
 table_Arr.splice(0,3);
 data_endRow = table_Arr.length;
 sheet_data.getRange(data_startRow,1,data_endRow,cols).setValues(table_Arr);//data_startRow+rows
 masterdate = sheet_master.getRange(4,1,data_endRow,1).getValues();
 masterblock = sheet_master.getRange(4,3,data_endRow,5).getValues();
 date_sabun = sheet_data.getRange(data_startRow,20,data_endRow,1);
 sheet_data.getRange(data_startRow,1,data_endRow,1).setValues(masterdate);
 sheet_data.getRange(data_startRow,15,data_endRow,5).setValues(masterblock);
 sheet_master.getRange(4,8).copyTo(date_sabun);
 sheet_master.getRange(2,8).setValue(nextDate);
 }

function getStringSlice(content, startStr, endStr){
 var indexStart = content.indexOf(startStr);
 if(indexStart == -1){
 return "";
 } else {
 indexStart += startStr.length
 return content.slice(indexStart, content.indexOf(endStr, indexStart));
 }
 }

現状のコードの問題点とまとめ

まあ、実用性は十分ではありますが、完成とはいいがたいですよね。

1976年以前、気象庁のデータがないタイミングでストップさせることが必要です。

また、エリアと見出し部分を整え自動で解析し、変数colsの指定を可変にした方が汎用性は増すでしょう。

データを追加した後の並び替えも自動でできるとなお良いですし、遡ってデータを得た後も最新日付のデータを更新するコードも欲しい。

都道府県とブロックのID一覧を取得すれば、そのブロックIDの数だけスプレッドシートを作って回すこともできます。

とはいえ、そこまで行くと気象庁のサーバーへの負担も考えながらやる必要もありますので、かなり大変です。

しかし、天気のデータは色々なものと結びつけることができる、面白いデータです。

そんなデータを公開してくれている気象庁に感謝の意を示し、今回の締めとさせていただきます。

コメント