tag:crieit.net,2005:https://crieit.net/tags/cheerio/feed 「cheerio」の記事 - Crieit Crieitでタグ「cheerio」に投稿された最近の記事 2020-08-12T11:33:36+09:00 https://crieit.net/tags/cheerio/feed tag:crieit.net,2005:PublicArticle/16026 2020-08-09T13:31:47+09:00 2020-08-12T11:33:36+09:00 https://crieit.net/posts/spreadsheet-scraping Googleスプレッドシートでスクレイピング <p>スクレイピングがやりたかったんだけど、サーバの管理がめんどくさくなったので、Googleスプレッドシートの上でスクレイピングを出来るようにした。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2fb0bc27cbc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2fb0bc27cbc.png?mw=700" alt="image.png" /></a></p> <h1 id="やりたいこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8">やりたいこと</a></h1> <p>献血で、400mlAB型の血液が不足してます、A型は今大丈夫です、みたいな情報が、献血センターのwebサイトに掲載されるようになった。<br /> たとえば <a target="_blank" rel="nofollow noopener" href="https://www.bs.jrc.or.jp/th/miyagi/">宮城県赤十字血液センター</a>の今週の献血状況。AB型の人は成分献血にするか、次の機会にしたほうが良いらしい。<br /> <a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f346e54241.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f346e54241.png?mw=700" alt="image.png" /></a></p> <p>これ以外の県も、<a target="_blank" rel="nofollow noopener" href="http://www.jrc.or.jp/search/bloodcenter/index.html">各県の献血センター</a>に掲載されている<br /> こういう情報をオープンにしてくれたのはとてもうれしいことなんだけど、webページに掲載されたって、ぼくらがわざわざ見に行かない限り気が付かない。できればこう、SNSで「今週の献血状況」みたいなことを発表してくれるといいよね。そしたら、「あ、AB型足りない?今週は余裕があるからじゃあぼく行くね」みたいなことが出来ていいと思うのよ。</p> <p>ないなら作れの精神で、とりあえずWebページ上にあるデータを毎日取得して蓄積してみることにした。あとでグラフとか需要の波が見られたら面白そうじゃない?</p> <p>でもそのためにサーバとか管理するのもなあ。もうちょっと気軽にやれる方法はないの?</p> <h1 id="GoogleAppsScript"><a href="#GoogleAppsScript">GoogleAppsScript</a></h1> <p>Googleスプレッドシートには、GoogleAppsScript(gas)というスクリプト機能があって、JavaScriptでスプレッドシートのセルの値を読み書きしたり、ボタンを押すとメソッドを実行するようにしたりすることが出来る。<br /> <a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app">UrlFetchApp</a>というライブラリがあってWebページをダウンロードすることも出来る。あと時刻を設定して指定した関数を起動したりする機能もある。ということは、タイマーで定期的にWebページを取得させることが出来る。サーバレスでスクレイピングができるとかなり嬉しい。</p> <p>ただDomパーサーがないので、取ってきたHTMLは正規表現で切り出さないといけない。サーバの管理すらしたくない駄目人間にそれはちょっと厳しい。</p> <h1 id="ImportXML"><a href="#ImportXML">ImportXML</a></h1> <p>スプレッドシートには、<a target="_blank" rel="nofollow noopener" href="https://support.google.com/docs/answer/3093342?hl=ja">importXML</a>というメソッドがある。<br /> これを使うと、URLとXPathを書くだけでそのページの該当箇所を取ってきてセルに入れてくれるすごいメソッド。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f4e4747b9c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f4e4747b9c.png?mw=700" alt="image.png" /></a></p> <p>ただこれ簡単メソッドだけに色々問題があって、<br /> ・厳密にXMLとして解釈しようとするので、乱暴なHTMLのページは取得できないことが多い<br /> ・JSの解釈もしてくれないぽい<br /> ・XPath書くの面倒w<br /> ・取得タイミングが制御できない</p> <p>最後のがわりと致命的で、キャッシュ制御がブラックボックスなので、ページが更新されても内容が更新されなかったり、予想されないタイミングでキャッシュが破棄されて内容が更新されていたりする。</p> <p>現時点の内容がわかれば十分な用途には便利なんだけど、毎日データを取ってくるような用途にはちょっと使いにくい。</p> <h1 id="cheeriogs"><a href="#cheeriogs">cheeriogs</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/cheeriojs/cheerio">cheerio</a>は、jQueryぽい文法でHTMLをパースすることが出来るnode.jsライブラリ。ブラウザじゃなくてもjQueryぽくHTMLを解析できるので、スクレイピングのときに便利。<br /> 残念ながらgasにはサイズ制限があるので、npm install cheerio みたいに気軽に使うことは出来ないんだけど、代わりにつかえるgas library でcheerioを使えるようにしてくれた <a target="_blank" rel="nofollow noopener" href="https://github.com/tani/cheeriogs">cheeriogs</a> という神がいるので、これを使うとスプレッドシートのスクリプト内でもHTMLをパースして内容を取ってくることが出来る。これですべての材料は揃った。</p> <h1 id="実装"><a href="#%E5%AE%9F%E8%A3%85">実装</a></h1> <p>スプレッドシートのメニューからスクリプトエディタを立ち上げる。<br /> <a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f759697cfa.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f759697cfa.png?mw=700" alt="image.png" /></a></p> <p>リソース- ライブラリ でライブラリ画面を開いて、<a target="_blank" rel="nofollow noopener" href="https://github.com/tani/cheeriogs">cheeriogs</a> に書いてあるIDを入力してcheerioライブラリをインポート。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f7639e92dd.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f7639e92dd.png?mw=700" alt="image.png" /></a></p> <p>あとは、こんな感じで書けばページの内容を取得できる。</p> <pre><code>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(); }) : </code></pre> <p>まんまjQueryでしょ?</p> <p>このあと、スプレッドシートに載っている前回の結果を右にずらして左端に今回の結果を追加したりする処理があるんだけど、そこはたんなるgasのスプレッドシート操作なので省略。</p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/tani/cheeriogs">cheeriogs</a> をつかうことで、献血の需給状況をスクレイピングすることが出来るようになった。タイマーで毎日起動するようにしたので、現在は放置しておいても毎日夜中にその日の情報をとってきてくれるようになっている。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f78ff6c048.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f78ff6c048.png?mw=700" alt="image.png" /></a></p> <p>単なるスプレッドシートなので、条件付き書式(セルの色塗り)とかはスクリプトと無関係に出来てしまうのも便利。</p> <p>gasはrubyでいうirbみたいな即時実行ツールがないし、デバッガも貧弱なので、そんなに開発体験が良いわけではない。<br /> でも無料で使えるし、タイマーとか便利だし、なによりスプレッドシートという強力なアウトプット窓口を使えるので、サービスの管理画面なんかはこれで作るのもありだなあ、と思った。</p> <p>あとはこれをtwitter botにすれば良いんだけど、例の感染症騒ぎで献血が逼迫した結果、今はどのエリアもみんな「成分献血:非常に困っています」しか出なくなってしまっている。この状況だと、献血を知っている人は献血したら次回の予約をして行く感じになっていて、Twitterで状況を知らせてくれると嬉しいとかいう感じじゃない。<br /> 今必要なのは、献血についてあんま知らない人が「えーそんな足りないの?」ということで献血にチャレンジしてくれることなので、この記事を見た人、ぜひご協力をお願いします。献血は不要不急じゃなくて必要なタスクです!</p> daisuke furukawa