returnえんじにゃー

学び直し中元エンジニアのアウトプットブログ。技術やものづくり。たまに猫。

GASでWEBスクレイピングしてSLACKBOTでドーン!!ボガーンや!!!!~GASでWEBスクレイピング編~

前置き(飛ばしていいやつ)

先日、我が社に「Slack」が導入された。

「あ~~ん???Slack???今更何言ってんだこいつ今時どこのIT企業でも入ってるやろォ???なあにを高らかに宣言しとんねんボッケェ~~あと普通弊社っていうやろカスゥ~~」と思われた方もいるだろう。

そうです。その通り。あなたの言う通り。間違いない。大正解です。どうぞ、スーパーひとし君人形です、ご査収ください。
あと、弊社って言葉はなんか嫌いだからあんまり使いたくないの。分かっていただけるぅ?

ゴホン。話を戻します。

私が所属する会社はIT企業なのになぜか最新の(『最新じゃないけど我が社にとっては最新』の意)ツールや技術等に非常に及び腰なのである。

そんな我が社で「Slack」が導入されるという事は非常に目覚ましい進歩、
はいはいをしていた赤ちゃんが時速60kmではいはいができるようになった、
みたいな感じなのである。いや結局はいはいなんか~~~い。(どんがらがっしゃん)

ゴホンゴホン。

さて、その導入された「Slack」を遊び尽くすべく、無意味なリマインドを大量に入れて同僚に嫌われたり、先輩の顔写真のスタンプを大量生成し先輩に嫌われたりしながら 充実したSlackライフを満喫していたのですが、一つ問題が生じました。

「『記念日』が負担になっている問題」である。

上記、決して「恋人が記念日を各週単位で設定してくるからマジ重たくてつらたんオブザイヤー受賞」というリアルガチ記念日の話ではない。

我が社のSlackのチャンネルに『記念日』というチャンネルがある。
Slackが導入されたことがあまりに記念的出来事だったので、私がおもわずチームメンバーとその他を巻き込んで作成したチャンネルだ。

作成した当初は「なんやんねんこのスレ」「邪魔くさい」と盛り上がっていたものの、 見切り発車で作ったものだったので、数日後には誰も書き込みもしない見もしない過疎チャンネルとなってしまっていたのが、 8月のある日突然メンバーの一人(私の上司)がこのような書き込みを投稿し始めたのだ。

f:id:kurunaki1117:20190930123434p:plain

えなにこれ怖、と思って最初は無視していたが、次の日も次の日も同様の投稿を毎朝書き込んでくる。

どうやら、このチャンネルを再活用すべく(という名目でふざけたいから)、『記念日』にちなんで「本日の記念日」を毎日書き込むようになったらしい。
その結果、おかげでこのチャンネルを通じ「こんな日あるんや」「こんなの記念日にする必要あるんか」というような会話から雑談が始まる、活発なチャンネルに生まれ変わったのだ!

自分が作ったチャンネルが活用されていくのはとても微笑ましくもあり、 これからもこのチャンネルを通して楽しくコミュニケーションを取っていけるのだろう、そう思っていたある日。

上司がぽろっと私にこうこぼしたのだ。

「記念日投稿するの超負担」

衝撃である。

どんなに忙しい時も、毎朝記念日が載っているHPから記念日をコピペし、メモ帳で若干加工したものをSlackに張り付けなくてはいけないという義務感が彼にとってかなりの負担になっていたのだ。

そんな状況を目の当たりにした私は、自ら勝手に始めたおふざけ行為で苦しんでいる上司を救うべく、立ち上がった。

「GASでWEBスクレイピングしてSlackBOTでドーン!!ボガーンや!!!!」

~完~

いや前置き長~~~~~~。

やったこと(本編)

  • GASでWEBスクレイピング(スクレイピング用のライブラリを使用)。
  • GASからSlackにメッセージ送信(SlackAPI)。
  • 上記メッセージは決まった時間に投稿する。(GASのトリガーを使用)

今回は『GASでWEBスクレイピング』の部分をまとめたよ。

GASでWEBスクレイピング

ライブラリの導入

まず、下記HPを参考にHTMLをパースしてくれるライブラリを導入してみる。

Easy data scraping with Google Apps Script in 5 minutes
https://www.kutil.org/2016/01/easy-data-scrapping-with-google-apps.html

下記ブログを参考にしました。導入からメソッドまで丁寧に解説してくださっているので、とても便利!

参考
GASで簡単WEBスクレイピング!HTMLを簡単にパースできるライブラリParserを使ってみた
https://www.kotanin0.work/entry/2019/01/06/200000

取得するHPを確認。

今回取得したいのは、下記HPの今日の記念日のところ。しまくとぅばって何。

参照
雑学ネタ帳 -今日は何の日・明日は何の日-
https://zatsuneta.com/category/anniversary.html

f:id:kurunaki1117:20190930123050p:plain

上記HPをChromeデベロッパーツールにてHTMLの構造を見てみたところ、 下記の様に3つの記事のdiv要素が同じclassが設定されていた。

f:id:kurunaki1117:20190930123119p:plain

<!--▼ 今回取得したいHTML--> 
<div class= article>
    <h3> 今日 9月18日(水)の記念日・年中行事 </h3>
    <p></p>
    <ul>...</ul> 
    <p></p>
</div>
<!--▲ 今回取得したいHTML--> 
<div class= article>
    <h3> 明日 9月19日(木)の記念日・年中行事 </h3>
    <p></p>
    <ul>...</ul> 
    <p></p>
</div>
<div class= article>
    <h3> 9月の記念日・年中行事一覧 </h3>
    <p></p>
    <ul>...</ul> 
    <p></p>
</div>

私が欲しいのは一番上の今日の記念日の部分。上手に取得できるかしら・・と思ったがそこはさすがのライブラリさん。
下記のように記述するとこのようにデータを取得してくれる。

データの取得

  • URLにアクセスし、HTMLを文字列として取得
  • その文字列の中から、<div class= article>で始まり</div>で終わるデータで、 一番最初に見つけたものを文字列として取得

ソースは下記。

 // URLを記述
  var url = "https://zatsuneta.com/category/anniversary.html";
  
  // 対象のURLにアクセス
  var fetch = UrlFetchApp.fetch(url);
  
  //HTML文を取得
  var html = fetch.getContentText();

  // <div class="article">で始まる</div>で終わるHTMLで最初に見つかったものを文字列で返す。
  var data = Parser.data(html).from('<div class="article">').to('</div>'.build();

  // logにてデータ確認
  Logger.log(data);

上記の実行結果はこんな感じ。(ブログを書いている日(9/30日)で実行)

画面 f:id:kurunaki1117:20190930123858p:plain

[19-09-30 11:37:13:039 JST]
<h3>今日 9月30日(月)の記念日・年中行事</h3>
<p>
<ul>
<li><a href="https://zatsuneta.com/archives/109304.html" title="世界翻訳の日(9月30日 記念日)">世界翻訳の日</a></li>
<li><a href="https://zatsuneta.com/archives/109301.html" title="クレーンの日(9月30日 記念日)">クレーンの日</a></li>
<li><a href="https://zatsuneta.com/archives/109302.html" title="くるみの日(9月30日 記念日)">くるみの日</a></li>
<li><a href="https://zatsuneta.com/archives/109303.html" title="紅葉の見頃予想発表日(9月30日頃 記念日)">紅葉の見頃予想発表日</a></li>
<li><a href="https://zatsuneta.com/archives/109305.html" title="交通事故死ゼロを目指す日(2月20日・4月10日・9月30日 記念日)">交通事故死ゼロを目指す日</a></li>
<li><a href="https://zatsuneta.com/archives/109306.html" title="クミンの日(9月30日 記念日)">クミンの日</a></li>
<li><a href="https://zatsuneta.com/archives/109307.html" title="両親の日(9月30日 記念日)">両親の日</a></li>
<li><a href="https://zatsuneta.com/archives/109308.html" title="奥様の日(9月30日 記念日)">奥様の日</a></li>
<li><a href="https://zatsuneta.com/archives/109309.html" title="ニッポン放送 HAPPY FM93の日(9月30日 記念日)">ニッポン放送 HAPPY FM93の日</a></li>
<li><a href="https://zatsuneta.com/archives/10930a1.html" title="翻訳の日(9月30日 記念日)">翻訳の日</a></li>
<li><a href="https://zatsuneta.com/archives/101302.html" title="EPAの日(毎月30日 記念日)">EPAの日</a></li>
<li><a href="https://zatsuneta.com/archives/101305.html" title="サワーの日(毎月30日 記念日)">サワーの日</a></li>
<li><a href="https://zatsuneta.com/archives/10110k5.html" title="キャッシュレスの日(毎月0の付く日 記念日)">キャッシュレスの日</a></li>
<li><a href="https://zatsuneta.com/archives/109217.html" title="秋の全国交通安全運動(9月21日~30日 記念日)">秋の全国交通安全運動</a></li>
<li><a href="https://zatsuneta.com/archives/109246.html" title="結核予防週間(9月24日~30日 記念日)">結核予防週間</a></li>
<li><a href="https://zatsuneta.com/archives/10901c.html" title="歯ヂカラ探究月間(9月1日~30日 記念日)">歯ヂカラ探究月間</a></li>
</ul>
</p>

いい感じに取得できましたね。
今日は世界翻訳の日なんだって。へ~~。

こんな感じで.build()を使って該当するHTML文字列が取得できましたが、今度はその中の「記念日」の文字列たちを全て取得したい。
その場合は.iterate()を使う。これは、文字列の配列が返ってくる。
ソースは下記。

   // さっき取得したdataの中から<a><li>タグ内の記念日を配列として取得する
  var day = Parser.data(data).from('">').to('</a></li>').iterate();

  // logにてデータ確認
  Logger.log(day);

今回の取得の仕方は.from('">').to('</a></li>').とちょっと力業・・。
.from('<a').to('</a></li>').というように指定したところ、下記の様に<aから</a></li>までのすべての文字を持ってきてしまった。

[19-09-30 12:14:52:234 JST] [ href="https://zatsuneta.com/archives/109304.html" title="世界翻訳の日(9月30日 記念日)">世界翻訳の日,  

HTMLとして認識しているわけではなく、ただの文字列の中から探してくれているんだもの、そりゃそうだ。
なので、直前の要素かつ汎用的にデータが取得できるぎりっぎりの所をむりやり探して取得させた。

実行結果はこう!ちゃんと配列でとれてるとれてる!

[19-09-30 12:03:18:154 JST] [世界翻訳の日, クレーンの日, くるみの日, 紅葉の見頃予想発表日, 交通事故死ゼロを目指す日, クミンの日, 両親の日, 奥様の日, ニッポン放送 HAPPY FM93の日, 翻訳の日, EPAの日, サワーの日, キャッシュレスの日, 秋の全国交通安全運動, 結核予防週間, 歯ヂカラ探究月間]

これで取得したデータをday.join('、')で句読点で結合してあげて、logで出力したらほれ!できたできた~~!

[19-09-30 12:08:59:161 JST] 世界翻訳の日、クレーンの日、くるみの日、紅葉の見頃予想発表日、交通事故死ゼロを目指す日、クミンの日、両親の日、奥様の日、ニッポン放送 HAPPY FM93の日、翻訳の日、EPAの日、サワーの日、キャッシュレスの日、秋の全国交通安全運動、結核予防週間、歯ヂカラ探究月間

データ取得側はこれでおーけー。
あとはこれをSlack側に流すぞ。

まとめ

思っていたより長くなってしまったので分ける。完全に前置きのせい。
次は下記についてまとめる。

  • GASからSlackにメッセージ送信(SlackAPI)。
  • 上記メッセージは決まった時間に投稿する。(GASのトリガーを使用)

以上!!!!!!!!!!!!!!!!!!!!!!!!(厚切りジェイソン風)