[GAS][Webスクレイピング]手軽に島鉄の運行状態をアナウンスするLINE botを作った話

burning stove コーディング
Photo by Pixabay on Pexels.com

経緯

先日積雪があり、通学に使用している交通機関の状態を確認するのに何度も公式サイトを確認するのが不便だと思い、運行状態に変更があればLINEで知らせるbotを作成しようと思いました。

製作手順

以前作成した学校からのお知らせメールをLINEに転送するbotを作成した際、GASを用いたことから、今回も使用することを決定。

ネット上の様々な記事などを参考に製作を始めたものの、各運行部門(鉄道・バス・フェリー)の運行状態の書かれている部分をそれぞれで抜き出す部分に苦戦しましたが、苦戦を重ねた挙句ChatGPTに書きかけのコードとスクレイピング先(運行状態の書かれたページ)の1部を送った結果、あっという間に完成してしまいました。

早い。そしてほぼ完璧。「エラー文をchatGPTに送る→送られてきた修正後のコードをGASに貼り付け実行」を繰り返しただけで作れるもんなんですね。

仕様

このbotは、GASのトリガー機能により、以下の処理を30分に1度実行しています。

  1. Webサイトから運行状況を取得
    • 指定されたWebサイトのHTMLを解析し、鉄道・バス・フェリーの運行情報を抽出します。
  2. 運行状況の変化を判定
    • 取得した最新情報をスプレッドシートに保存し、前回のデータと比較します。
    • もし運行状況が変わっていれば、その情報をまとめます。
  3. LINEに通知を送信
    • 変更があった場合のみ、LINEのAPIを使って通知を送信します。

完成品

// Googleスプレッドシートを開き、データを保存するシートを取得
var ss = SpreadsheetApp.openById('前回取得した内容を保存するスプレッドシートのID');
const sheet = ss.getSheetByName("shimatetsu-status");

// 以前の運行状態を取得
var previousStatus = {
  rail: sheet.getRange("B2").getValue(),
  bus: sheet.getRange("B3").getValue(),
  ferry: sheet.getRange("B4").getValue()
};

// 運行状況をチェックする関数
function checkOperationStatus() {
  var url = "https://www.shimatetsu.co.jp/";
  var response = UrlFetchApp.fetch(url); // ウェブサイトのHTMLを取得
  var html = response.getContentText();
  
  // 各交通手段の運行状況を抽出
  var railStatus = extractStatus(html, "鉄道");
  var busStatus = extractStatus(html, "バス");
  var ferryStatus = extractStatus(html, "フェリー");
  
  // シートの最終更新時刻を更新
  sheet.getRange("B1").setValue(new Date());
  var changes = [];

  // 運行状況が変更された場合、通知リストに追加し、シートの値を更新
  if (railStatus.text + "\n" + railStatus.link !== previousStatus.rail) {
    changes.push("鉄道🚈: \n" + railStatus.text + "\n" + railStatus.link);
    sheet.getRange("B2").setValue(railStatus.text + "\n" + railStatus.link);
  }
  if (busStatus.text + "\n" + busStatus.link !== previousStatus.bus) {
    changes.push("バス🚌: \n " + busStatus.text + "\n" + busStatus.link);
    sheet.getRange("B3").setValue(busStatus.text + "\n" + busStatus.link);
  }
  if (ferryStatus.text + "\n" + ferryStatus.link !== previousStatus.ferry) {
    changes.push("フェリー⛴: \n" + ferryStatus.text + "\n" + ferryStatus.link);
    sheet.getRange("B4").setValue(ferryStatus.text + "\n" + ferryStatus.link);
  }
  
  // 変更があればLINE通知を送信
  if (changes.length > 0) {
    sendLineNotification("【運行状況更新】\n" + changes.join("\n\n"));
  }
}

// 指定されたカテゴリの運行情報を抽出する関数
function extractStatus(html, category) {
  var regex = new RegExp('<p class="sec-top-info-list-ttl">' + category + '<\/p>\\s*<p class="sec-top-info-list-txt">\\s*<a href="([^"]+)"[^>]*>\\s*<span[^>]*>([\\s\\S]*?)<\/span>', 'i');
  var match = html.match(regex);

  if (match) {
    return {
      text: match[2].replace(/<br\s*\/??>/g, "\n").trim(), // <br>タグを改行に置換
      link: match[1] // 運行情報へのリンクを取得
    };
  }
  return { text: "情報の取得に失敗しました。", link: "なし" };
}

// LINEに通知を送信する関数
function sendLineNotification(message) {
  var lineToken = "LINEのトークン";
  var url = "https://api.line.me/v2/bot/message/broadcast";
  var payload = JSON.stringify({
    messages: [{
      type: "text",
      text: message
    }]
  });

  var options = {
    method: "post",
    contentType: "application/json",
    payload: payload,
    headers: {
      Authorization: "Bearer " + lineToken
    }
  };

  UrlFetchApp.fetch(url, options); // LINE APIを使用してメッセージを送信
}

extractStatus関数について

この関数は、ウェブページのHTMLから特定のカテゴリ(鉄道・バス・フェリー)の運行情報を抽出するために使用されます。具体的には、以下の情報を取得します。

  1. 運行状況のテキスト(例: 「通常運行」「運行見合わせ」など)
  2. 該当する情報へのリンク(詳細情報のページURL)

正規表現について

パターン説明
<p class="sec-top-info-list-ttl"> + category + </p>category(鉄道・バス・フェリー)に一致するタイトルを検索
\s*空白や改行をスキップ
<p class="sec-top-info-list-txt">運行状況の説明が書かれた段落を指定
\s*<a href="([^"]+)"[^>]*><a> タグの href 属性(リンク先URL)をキャプチャ
\s*<span[^>]*>([\s\S]*?)</span><span> タグの中の運行情報をキャプチャ

match の処理

var match = html.match(regex);

このコードは、正規表現に一致する部分を match に格納します。

  • match[1]href に指定された運行情報ページのURL
  • match[2]<span> タグ内の運行状況テキスト

戻り値の処理

if (match) {
  return {
    text: match[2].replace(/<br\s*\/??>/g, "\n").trim(), // 改行タグを\nに変換
    link: match[1] // URLを取得
  };
}
  • text: <br> タグを \n に変換し、余分な空白を削除
  • link: 取得したリンクURL

もし正規表現が一致しなかった場合(運行情報が取得できなかった場合)、デフォルトのメッセージを返します。

return { text: "情報の取得に失敗しました。", link: "なし" };

まとめ

本botを製作したことで、わざわざホームページを見に行かなくとも、変更があるたびにメッセージが届くので、運行状態を確認するのがかなり楽になりました。

ただし、LINE公式アカウントの無料プランでは200通が限界であり、すぐに上限に達してしまいそうなので、いずれWebhookを使用してDiscord鯖に転送する方式に変更しようかと思います。

コメント

タイトルとURLをコピーしました