0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GASでDevelopers.IOのAWS新着記事を毎日通知する

Last updated at Posted at 2021-01-24

はじめに

最近仕事でAWSを触る機会が多くなってきたので、インプットのために様々な関連記事を読んでいます。
その中でもよく読んでるのがDevelopers.IOさんの記事です。
投稿されるAWSの記事には「こんなサービスがあるんだ!」「こういう使い方もできるんだ!」という発見がよくあるので、毎日軽く目を通すようにしてます。

ただ、新着記事が投稿されてるかをブラウザで毎日確認するなんてこと、怠惰な僕にはできません。
なので、日次で新着記事の通知がSlackに飛ぶように自動化しました。

#やりたいこと/前提
Developers.IOに投稿されるAWS関連の新着記事を日次でslackに通知する

利用するツール/ライブラリ
 ・GAS
 ・Parser(GASライブラリ)
 ・Slack

事前準備
 ・Slackにて、Incoming WebhookURLと通知先チャンネルを作成しておく

作っていく

新しいGASプロジェクトを作成する

GASとは、Googleが提供しているJacaScriptをベースにしたプログラミング言語です。
Googleが提供する様々なクラウドサービス(Google Calendar, Spread Sheetなど)を操作する目的で作られてます。
実行環境(プログラムを実行する場所)もgoogleが用意してくれているので、すぐに使い始めることができます。
今回のプログラムにGoogleサービスを利用する場面は1つも出てこないのですが、以下の理由からGASで作ることにしました。
 ・標準で利用できる「トリガー」が便利
 ・実装&利用までのスピードが速い

ではGASプロジェクトを作成します。プロジェクト名は適当につけちゃいます。
image.png

GASプロジェクトにParserライブラリを追加する

今回は、htmlを解析・スクレイピングするためにParserというライブラリを使用します。
プログラムを作るときに外部ライブラリを使うことは、言語を問わずよくあることだと思います。GASも例外ではありません。
GASでライブラリを追加するには、追加したいライブラリのスクリプトIDを調べておく必要があります。
ParserライブラリのスクリプトIDは「1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw」です。
では追加します。
image.png

コードを実装していく

下準備は終わったので、あとはゴリゴリ実装していくだけです。
コードを記載する前にプログラムの流れを簡単に説明しておきます。
大まかなプログラムの流れ

  1. トリガーによってプログラム実行開始
  2. スクレイピング対象の複数のサイトページに対して、繰り返し以下の処理を行う
    1. 対象のサイトページからhtmlを取得
    2. htmlをスクレイピングして記事の一覧を取得
    3. 昨日投稿された記事のみ抽出
  3. 「2」で得た記事情報全てをslackに通知

では一つ一つコードの説明をしていきます。(注意:見やすさのため、コードブロックの拡張子はgsでなくjsにしてます)

main.gs

main()はトリガーに実行される関数で、このプログラムのエントリーポイントです。
getNewAWSTopics()(後述)で記事一覧を取得しており、その情報をpostSlack()(後述)で1つ1つslackに通知してます。

code.js
function main() {
  let topics = getNewAWSTopics();
  topics.forEach(topic => {
  	postSlack(topic);
  });
}

topic_developersio.gs

Developsers.IOの指定サイトページから新着記事を取得するために必要な関数群です。
Developers.IOではhttps://dev.classmethod.jp/tags/<tag-key>にアクセスすると、<tag-key>がついてる記事一覧を見ることができます(参考ページ)。
コードの実装では、見たいタグのurl1つ1つにアクセスして記事一覧を取得するという形を取っています。

getNewAWSTopics()がこの関数群の大元で、AWS_TAGsが付いてる新着記事の情報を取得してメッセージに整形してから一覧を返します。
_createNewURLs(AWS_TAGs)は、やってることは単純でhttps://dev.classmethod.jp/tags/<tag-key>のリストを返します。
_extractYesterdayTopics(url)は、urlにアクセスして得たhtmlを解析し、昨日投稿された記事情報(タイトル/記事url/投稿日)だけを取得して返します。

Parser.data(html).from('<from-str>').to('<to-str>').iterate();の部分にだけParserライブラリを使っています。
htmlの<from-str>から<to-str>に挟まれた(注意:非貪欲)文字列を抜き出せすことができ、iterate()はhtmlの中に存在するこの複数のセットをリストで返すことをしています。
例えば以下のhtmlに対して、Parser.data(html).from('<p class="hoge">').to('</p>').iterate();を実行すると、[aaa, ccc]が返されます。

sample.html
<p class="hoge">aaa</p>
<p class="fuga">bbb</p>
<p class="hoge">ccc</p>
topic_developersio.js
DEVELOPERSIO_DOMAIN = "https://dev.classmethod.jp";
DEVELOPERSIO_TAG_BASE_URL = DEVELOPERSIO_DOMAIN + "/tags/";
AWS_TAGs = [
  'aws', 'ec2', 'lambda', 'Route53'
]

function getNewAWSTopics() {
  let topics = [];
  _createTagURLs(AWS_TAGs).forEach(url => {
    topics = topics.concat(_extractYesterdayTopics(url));
  });
  return [...new Set(topics.map(x => x.getMsg()))];
}

class Topic {
  constructor(title, url, date) {
    this.title = title;
    this.url = url;
    this.date = date;
  }
  getMsg() {
    return this.title + '\n' + this.url;
  }
  isYesterday() {
    let date = new Date(this.date);
    let yesterday = new Date();
    yesterday.setDate(yesterday.getDate()-1);
    if (yesterday.getFullYear() != date.getFullYear()) return false;
    if (yesterday.getMonth() != date.getMonth()) return false;
    if (yesterday.getDate() != date.getDate()) return false;
    return true;
  }
}

function _extractYesterdayTopics(url) {
  let res = UrlFetchApp.fetch(url);
  let html = res.getContentText('utf-8');
  let topic_urls = Parser.data(html).from('<div class="post-container" data-v-0c1f62df><a href="').to('"').iterate();
  let topic_dates = Parser.data(html).from('<p class="date" data-v-0c1f62df>').to('</p>').iterate();
  let topic_titles = Parser.data(html).from('<h3 class="post-title" data-v-0c1f62df>').to('</h3>').iterate();
  
  let yesterdayTopics = [];
  for (let [url, date, title] of zip(topic_urls, topic_dates, topic_titles)) {
    let topic = new Topic(title.trim(), DEVELOPERSIO_DOMAIN + url.trim(), date);
    if (topic.isYesterday()) yesterdayTopics.push(topic);
  }
  return yesterdayTopics;
}

function _createTagURLs(tags) {
  let urls = tags.map(tag => {
    return DEVELOPERSIO_TAG_BASE_URL + tag + '/';
  });
  return urls;

senders.gs

postSlack(msg)で、msgをSLACK_CHANNEL_NEWS_AWSチャンネルに送信しています。
通知するメッセージ内容を変えたい場合はpayloadの中身をいじってみてください。
Reference: Message payloads | Slack

sender.js
SLACK_CHANNEL_NEWS_AWS = "news_aws";
SLACK_WEBHOOK_URL = "<webhook-url>";

function postSlack(msg) {
  let options = {
    "method"  : "POST",
    "headers" : {"Content-type":"application/json"},
    "payload" : JSON.stringify({
      "text": msg,
      "channel": SLACK_CHANNEL_NEWS_AWS,
      "username": "news_deliver",
      "icon_emoji": ":truck:"
    })
  };
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options); 
}

utils.gs

僕は普段pythonを使っていることからjsでもzip関数を使いたい!となったんですが、標準では存在しなかったためutils.jsとして実装しています。
このコードは以下を丸パクリしております。作者さんありがとうございます。
JavaScript で2つ以上の複数の配列を同時に for 文で回す。 | 民主主義に乾杯

utils.js
function* zip(...args) {
  const length = args[0].length;
  
  // 引数チェック
  for (let arr of args) {
    if (arr.length !== length){
       throw "Lengths of arrays are not eqaul.";
    }
  } 
  for (let index = 0; index < length; index++) {
    let elms = [];
    for (arr of args) {
      elms.push(arr[index]);
    }
    yield elms;
  }
}    

トリガーで定期起動時間を設定する

GASには作成したプログラムを定期的に実行してくれる「トリガー」という大変便利な仕組みがあります。
これを使って毎日0~1時にmain関数を実行してもらいます。
以下のように追加すれば完了です。
image.png

最終的なファイル構成

image.png

動作イメージ

関数とトリガーがうまく動いていれば、以下のように毎日0~1時に新着記事のお知らせが届きます。
image.png

最後に

自動化ってやる前は絶対価値あると思ってたのに、いざ作ってみたらそんな必要なかったなていう現象よくある気がする。(僕だけ?)
javascript/GASはテンで素人なので、そこのところはご了承ください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?