【GAS×RSS×Slack】Qiita・Zenn・はてなブックマークのトレンドページの最上位記事をSlackに通知する

トレンドページの上位の記事はどんな記事があるか気になりますし、後学のためにもチェックしておきたいです。人気記事のチェックは毎日することにも意味がありますが、継続するのはちょっと大変です。

ので、人気記事チェックをGASとRSSを使用した仕組みで解決してみることにしました。Qiita、zenn、はてなブックマークという3つのブログサイトトレンドページの記事を自動でslackに投稿する方法を紹介します。

# 1.やりたいこと
・Qiita・Zenn・はてなブックマークのトレンドページからトレンド1位の記事を取得する
・取得した記事をslackに投稿する

# 2.実現方法
今回は記事を取得するのに、RSSを使用します。RSSとは、「Webサイトの更新情報を配信するためのフォーマット」です。または、更新情報を配信する技術のことを指します。

各サイトでRSSフィード（更新データそのもの）が用意されていたので、この方法を採用しました。また、手軽なためGASを使用したいなと思っていて、RSSフィードをGASで取得する方法もありそうだったのでいけそうと判断できました。slackへの投稿部分では、WebhookURLを使用しました。これもslackへの連携で手軽な方法のようだったのでこの手法にしました。

## 2.1 GASでコードを作成
まず、GASでコードを書いていきます。GASでコードを書いていく時の処理の流れは以下の通りです。

:::note info
1.記事データを取得
 Qiita・Zenn・はてなブックマークそれぞれに対して、以下の処理を行い、記事データを取得する
- 指定されたRSSフィードのURLからデータを取得
- 取得した記事データの中から1つ目の記事のタイトルとURLを取得
- 記事タイトルとURLをSlackに投稿する形に整える

2.slackに投稿する
投稿用に整えた記事情報をWebhookを用いてslackに送信する
:::

コードの全量は以下の通りです。

```javaScript
/**
 * メイン関数: はてなブログ、Zenn、Qiitaのトレンド記事を取得し、Slackに通知する
 */
function main(){
  // 記事を取得
  var hatenaBlogContent = getHatenaBlogTrendArticles();
  var zennContent = getZennTrendTopArticle();
  var qiitaContent = getQiitaTrendTopArticle();

  var message = qiitaContent +'\n'+'\n'+zennContent +'\n'+'\n'+hatenaBlogContent;
  //Logger.log('message:'+message);

// Slackにメッセージを送信
  postToSlack(message);
}

/**
 * Slackにメッセージを送信する
 * @param {string} message 送信するメッセージ
 */
function postToSlack(message) {
    
    var slackWebhookUrl = 'url';//TODO SlackのWebhook URLを設定
    var payload = {
        text: message
    };
   
    var options = {
        method: 'post',
        contentType: 'application/json',
        payload: JSON.stringify(payload)
    };
    // Slackにメッセージを送信
    var response = UrlFetchApp.fetch(slackWebhookUrl, options);
    Logger.log('Slackへの通知が成功しました。');
}


/**
 * はてなブログのトレンドページから最上位の記事を取得する
 * @return {string} 最上位記事のタイトルとURLを含む文字列
 */
function getHatenaBlogTrendArticles() {
  var feedURL = "http://b.hatena.ne.jp/hotentry/it.rss";
  var xml = UrlFetchApp.fetch(feedURL).getContentText();
  var document = XmlService.parse(xml);
  var root = document.getRootElement();
  
  // XMLの名前空間を定義
  var rss = XmlService.getNamespace('http://purl.org/rss/1.0/');

  // RSSフィードから、最初のアイテムのタイトルとURLを抽出
  var items = root.getChildren('item', rss);
  const topArticle = items[0];
  const title = topArticle.getChild('title', rss).getText();
  const link = topArticle.getChild('link', rss).getText();
  
  var hatenaBlogContent ="【タイトル】"+title +'\n'+"【URL】"+link;
  return hatenaBlogContent;
}


/**
 * Zennのトレンドページから最上位の記事を取得する
 * @return {string} 最上位記事のタイトルとURLを含む文字列
 */
function getZennTrendTopArticle() {
  const url = "https://zenn.dev/feed";
  const xml = UrlFetchApp.fetch(url).getContentText();
  const document = XmlService.parse(xml);
  const root = document.getRootElement();
  const channel = root.getChild("channel");
  
  // RSSフィードから、最初のアイテムのタイトルとURLを抽出
  const items = channel.getChildren("item");
  const topArticle = items[0];
  const title = topArticle.getChildText("title");
  const link = topArticle.getChildText("link");
  
  const zennContent ="【タイトル】"+title +'\n'+"【URL】"+link;
  return zennContent;
}


/**
 * Qiitaのトレンドページから最上位の記事を取得する
 * @return {string} 最上位記事のタイトルとURLを含む文字列
 */
function getQiitaTrendTopArticle() {
    var feedUrl = 'https://qiita.com/popular-items/feed.atom';
    var response = UrlFetchApp.fetch(feedUrl);
    var xml = XmlService.parse(response.getContentText());
    
    // XMLの名前空間を定義
    var atom = XmlService.getNamespace('http://www.w3.org/2005/Atom');
    
    // RSSフィードから、最初のアイテムのタイトルとURLを抽出
    var entries = xml.getRootElement().getChildren('entry', atom);
    var topArticle = entries[0];
    var articleTitle = topArticle.getChild('title', atom).getText();
    var articleUrl = topArticle.getChild('link', atom).getAttribute('href').getValue();

    var qiitaContent ="おはようございます。今日の3本です。"+'\n'+"【タイトル】"+articleTitle +'\n'+"【URL】"+articleUrl;
    return qiitaContent;
}

```

記事データを取得をするところで注意すべきは、サイトによってRSSフィードのタグ構成が異なることです。例えば、Qiitaだと「entry」というタグで1つの記事が囲まれていますが、zennだと「item」というタグで記事が囲まれています。やりたいことは同じなのですが、各サイトごとに少しづつ相違点があるので、サイトごとに個別対応をしていきます。

また、QiitaとはてなブックマークのRSSフィードを取得する際に、名前空間を定義しています。名前空間を定義しないとデータを取得できません。

まず、名前空間とは、一意に決まる名前を持つための仕組みのことです。そして、なぜこの名前空間の定義が必要かというと、XMLでは異なる要素が同じ名前を持つことがあり区別するためです。

ちなみに、私がこのコードを書くのに一番時間がかかったのは、はてなブックマークの名前空間の定義の部分でした。何をどう指定すればよいのかわからず、解決に時間がかかりました。よい記事を見つけて解決しました。

＊

このGASのコードを動くには「postToSlackメソッド」の以下の部分に「Webhook URL」を設定する必要があります。このURLはこの後の作業で取得します。

```javascript
    var slackWebhookUrl = 'url';//TODO SlackのWebhook URLを設定
```

## 2.2 Webhook URLの取得
Slack APIページを開きます。

https://api.slack.com/

「Your apps」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/6a601ee7-d25e-9e6f-1d7d-72a7dbc71141.png)

「Create an App」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/33521d9d-de39-1b5f-c4f1-09f3d5ee4f5b.png)

「From scratch」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/5eb95de3-3f04-1fae-1a04-9c34bf4021a8.png)

「App name」を入力し、「workspace」を選択します。(2つの項目を入力すると、「Create App」が活性化します)
「Cteate App」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/544745d9-124f-71f5-3cfd-96c2bc4d2be3.png)

「Incoming Webhooks」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/0b618222-8e55-606b-fbc2-92aa5142e13c.png)

「トグルボタン」をOnにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/a4eee1a0-805e-c954-97e6-cee51505ae70.png)

「Add New Webhook to Workspace」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/2e780ff3-bc4e-e4f4-1c9a-bad3e45bb273.png)

「チャンネル」を指定し、「許可する」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/77f3d364-c4c0-571c-7da7-c5ff1805d77c.png)

これで、webhook URLが使えるようになったので、「copy」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/a3cf38af-179d-8432-eeb8-66dede684706.png)

## 2.3　GASにwebhook URLを貼り付け
「postToSlackメソッド」の'URL'と書いてある場所にwebhook URLを張り付けて、GASのコードを「保存」しましょう。
```javascript
    var slackWebhookUrl = 'url';//TODO SlackのWebhook URLを設定
```

## 2.4動作確認
では、動作確認をしてみましょう。main関数を実行します。slackに以下のようなメッセージが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605915/9e540195-a50f-0979-5edd-f9620031cd07.png)


3つのサイトの記事のURLが表示されていますね。zennだけ画面上一番上の記事でないですが、RSSフィードだと最初の記事です（なので、仕様通りです。）。

# 作ってみた感想
まず、RSSの知識をつけることができました。言葉は知っていたのですが、手を動かすことによって「実体」を掴んだような感覚になり、経験値が増えたような気がします。特に、「名前空間の指定」が必要ということは身に沁みました。はてなブックマークの名前空間の指定がうまくできず、はてなブックマークの記事取得自体をなかば諦めかけていたので。

GASは本当に便利でした。ほしいメソッドがかなり用意されていて、だいたいのことが細かく実現できます。ちょっとした業務改善にかなり向いていると改めて思いました。

＊

slackで情報共有チャンネルをつくって、自動で記事が投稿できたらチームを活性化できて面白いかなと思って作ってみました。記事を投稿して終わりにせず、ちょっと意見交換できたらいいなと思います。このコードでの自動投稿がそのちょっとした意見交換のきっかけになったらうれしいです。自分のイメージでは、GASの定期実行の機能で毎日朝、自動投稿するような運用でした。

今回は、1つのブログサイトで3つ記事を取得するのではなく、3つのブログサイトから記事を1つずつ取得するコードを書きました。幅広く情報を取れるように3つのブログサイトから取得してみました。RSSフィードがあれば同じ要領で自動投稿ができそうです。

最後まで読んでいただいた方、ありがとうございました。

# 参考文献

- はてなブログ『GoogleAppsScriptではてブのRSSをパースする』（2024年5月14日閲覧）
  https://yatta47.hateblo.jp/entry/2019/06/18/210608

- Zennブログ『ZennをRSSフィードで購読する』（2024年5月14日閲覧）
  https://zenn.dev/zenn/articles/zenn-feed-rss

- Qiita『Qiita 記事／ユーザ／Organization／タグ のフィード URL（フォローしたいユーザーやタグの XML/ATOM の URL）』（2024年5月14日閲覧）
  https://qiita.com/KEINOS/items/f0a5bce2fa9cfec85f8b

- Zennブログ『Slack：Webhook URL取得してSlackに通知する』（2024年5月14日閲覧）
  https://zenn.dev/hotaka_noda/articles/4a6f0ccee73a18