Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
203
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

[サーバレス] Googleスプレッドシートでスクレイピング

スクレイピングがやりたかったんだけど、サーバの管理がめんどくさくなったので、Googleスプレッドシートの上でスクレイピングを出来るようにした。

image.png

やりたいこと

献血で、400mlAB型の血液が不足してます、A型は今大丈夫です、みたいな情報が、献血センターのwebサイトに掲載されるようになった。
たとえば 宮城県赤十字血液センターの今週の献血状況。AB型の人は成分献血にするか、次の機会にしたほうが良いらしい。
image.png

これ以外の県も、各県の献血センターに掲載されている
こういう情報をオープンにしてくれたのはとてもうれしいことなんだけど、webページに掲載されたって、ぼくらがわざわざ見に行かない限り気が付かない。できればこう、SNSで「今週の献血状況」みたいなことを発表してくれるといいよね。そしたら、「あ、AB型足りない?今週は余裕があるからじゃあぼく行くね」みたいなことが出来ていいと思うのよ。

ないなら作れの精神で、とりあえずWebページ上にあるデータを毎日取得して蓄積してみることにした。あとでグラフとか需要の波が見られたら面白そうじゃない?

でもそのためにサーバとか管理するのもなあ。もうちょっと気軽にやれる方法はないの?

GoogleAppsScript

Googleスプレッドシートには、GoogleAppsScript(gas)というスクリプト機能があって、JavaScriptでスプレッドシートのセルの値を読み書きしたり、ボタンを押すとメソッドを実行するようにしたりすることが出来る。
UrlFetchAppというライブラリがあってWebページをダウンロードすることも出来る。あと時刻を設定して指定した関数を起動したりする機能もある。ということは、タイマーで定期的にWebページを取得させることが出来る。サーバレスでスクレイピングができるとかなり嬉しい。

ただDomパーサーがないので、取ってきたHTMLは正規表現で切り出さないといけない。サーバの管理すらしたくない駄目人間にそれはちょっと厳しい。

ImportXML

スプレッドシートには、importXMLというメソッドがある。
これを使うと、URLとXPathを書くだけでそのページの該当箇所を取ってきてセルに入れてくれるすごいメソッド。

image.png

ただこれ簡単メソッドだけに色々問題があって、
・厳密にXMLとして解釈しようとするので、乱暴なHTMLのページは取得できないことが多い
・JSの解釈もしてくれないぽい
・XPath書くの面倒w
・取得タイミングが制御できない

最後のがわりと致命的で、キャッシュ制御がブラックボックスなので、ページが更新されても内容が更新されなかったり、予想されないタイミングでキャッシュが破棄されて内容が更新されていたりする。

現時点の内容がわかれば十分な用途には便利なんだけど、毎日データを取ってくるような用途にはちょっと使いにくい。

cheeriogs

cheerioは、jQueryぽい文法でHTMLをパースすることが出来るnode.jsライブラリ。ブラウザじゃなくてもjQueryぽくHTMLを解析できるので、スクレイピングのときに便利。
残念ながらgasにはサイズ制限があるので、npm install cheerio みたいに気軽に使うことは出来ないんだけど、代わりにつかえるgas library でcheerioを使えるようにしてくれた cheeriogs という神がいるので、これを使うとスプレッドシートのスクリプト内でもHTMLをパースして内容を取ってくることが出来る。これですべての材料は揃った。

実装

スプレッドシートのメニューからスクリプトエディタを立ち上げる。
image.png

リソース- ライブラリ でライブラリ画面を開いて、cheeriogs に書いてあるIDを入力してcheerioライブラリをインポート。

image.png

あとは、こんな感じで書けばページの内容を取得できる。


const content = UrlFetchApp.fetch(url).getContentText();
const $ = Cheerio.load(content);
const title = $("title").first().text();
ret["title"] = title;
ret["description"] = $("meta[name='description']").first().attr("content");
ret["pref"] = title.replace("赤十字血液センター|日本赤十字社","")
ret["demand_date"] = $(".center-main-today-timedate").first().text();
ret["demands"] = [];
$(".center-main-today-types-state").each((index, element) => {
   ret["demands"][index] = $(element).text();
})
:

まんまjQueryでしょ?

このあと、スプレッドシートに載っている前回の結果を右にずらして左端に今回の結果を追加したりする処理があるんだけど、そこはたんなるgasのスプレッドシート操作なので省略。

まとめ

cheeriogs をつかうことで、献血の需給状況をスクレイピングすることが出来るようになった。タイマーで毎日起動するようにしたので、現在は放置しておいても毎日夜中にその日の情報をとってきてくれるようになっている。

image.png

単なるスプレッドシートなので、条件付き書式(セルの色塗り)とかはスクリプトと無関係に出来てしまうのも便利。

gasはrubyでいうirbみたいな即時実行ツールがないし、デバッガも貧弱なので、そんなに開発体験が良いわけではない。
でも無料で使えるし、タイマーとか便利だし、なによりスプレッドシートという強力なアウトプット窓口を使えるので、サービスの管理画面なんかはこれで作るのもありだなあ、と思った。

あとはこれをtwitter botにすれば良いんだけど、例の感染症騒ぎで献血が逼迫した結果、今はどのエリアもみんな「成分献血:非常に困っています」しか出なくなってしまっている。この状況だと、献血を知っている人は献血したら次回の予約をして行く感じになっていて、Twitterで状況を知らせてくれると嬉しいとかいう感じじゃない。
今必要なのは、献血についてあんま知らない人が「えーそんな足りないの?」ということで献血にチャレンジしてくれることなので、この記事を見た人、ぜひご協力をお願いします。献血は不要不急じゃなくて必要なタスクです!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
203
Help us understand the problem. What are the problem?