【GAS】カルディの半額セールをLINEに通知して家計を救おうじゃないか

Google Apps Script

我が家ではカルディのコーヒー豆を愛用しています。朝起きて、コーヒーメーカーのスイッチを入れて淹れたてのコーヒーを飲むのが至福のときです!

そのコーヒー豆ですが、私は半額セールのときにまとめ買いしています。だって、通常1800円のブルーマウンテンが900円ですよ!マイルドカルディにいたっては、なんと320円!めちゃくちゃコスパ良い!セール情報はこちらのセール開催店舗一覧から見ることができます。

・・・ですが、定期的にチェックするのは面倒。かといって、ちょっと見ないでいると「近くでセールやってたのに見逃した!」なんてことも。そこで、近隣の店舗でセールが開催されたらLINEに通知するようにしようと考えました。

このような通知を、好みの間隔(週一、3日おき、等)にLINEに送信できます。本記事のいちばん下にコピペOKのソースコードを載せておきます。GASやLINE Developersを操作したことのない方も、本記事の手順通りに進めれば問題なく設定できるかと思います。プログラミングのスキルも不要ですので、ぜひ試してみてください♪

全体像

カルディのホームページからセールの情報を取得してLINEに通知する処理は、以下の手順で行います。

対象ページのHTMLを文字列で取得し、それを一件ごとにリストに格納、条件に一致するデータのみ抽出したらメッセージ用に成形します。

通知用のLINEチャネルを作成

まず、通知用のLINEチャネルを作成します。チャネルは「Line Developerサイト」にて作成しますが、ふだんLINEをお使いの方であればログインすればすぐに使えます。

LINEチャネル作成については、以下の記事にて初心者の方向けに解説していますので併せてご確認ください。

Google Apps Script(GAS)の事前準備

次に、Google Apps Script(GAS)のスクリプトを作成します。GASの初期設定についてもこちらの記事にて解説しています。

Parserライブラリのインストール

GASでHTML解析をする場合、「Parserライブラリ」を使うと簡単に行うことができます。Parserライブラリを使用する際はインストールが必要です。

GASのエディタ左側メニューにある「ライブラリ」の+マークを押します。

「スクリプトID」に以下を入力し、「検索」を押します。

1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw

「追加」を押して、メニューにParserが追加されていることを確認できたらOKです。

GASによるスクレイピング

GASを利用してスクレイピング(Webサイトからの情報抽出)を行います。

対象のサイトからHTMLを取得する

まず、先ほどの全体像のうち「1.HTTPレスポンスを文字列で取得」を実装します。

GASでHTMLを取得するには、以下のように記載します。

UrlFetchApp.fetch(URL).getContentText(“[文字コード]”);
※文字コード:headタグ内の <meta charset=”[文字コード]”> にあわせる

この処理によって、HTMLをそのままごっそり文字列として取得することができます。

ParserライブラリでHTMLの特定部分のみ抽出

対象のサイトから取得したHTMLのうち、必要な部分のみ取り出していきます。今回であれば、セール店舗一覧の部分のみ取り出します。

ここで先ほどインストールしたParserライブラリの出番です。Parserライブラリは、HTMLテキストを文字列として操作することができます。Parserライブラリは以下のように使います。

Parser.data(HTML文字列).from(‘開始文字列’).to(‘終了文字列’).iterate();
. Parser.data(res).from(‘<tr>’).to(‘</tr>’).iterate();

セール店舗一覧を取得するには、テーブルになっている部分のみ取り出せばよいので、<tr>~</tr>を指定して取り出します。

itarate()とbuild()のちがい

itarate()とbuild()は、どちらも文字列抽出に使われますが、itarate()は条件に一致する部分すべてを返却し、戻り値はリストです。それに対し、build()は条件に一致する最初の文字列のみ返却し、戻り値は文字列です。

先ほどitarate()で店舗一覧を取得しましたが、build()を使うとテーブルヘッダしか取得されません。

近隣店舗のみ抽出

セール店舗の一覧が取得できたので、続いて近隣の店舗のみに絞ります。

近隣の店舗一覧をあらかじめ指定しておき、先ほど取得した店舗一覧ぶんループを回し、内側のループで通知したいエリアのリストをループさせ、条件に一致したときのみ新しいリストに格納していきます。

// 通知したいエリア
const AREA_1 = '大田区';
const AREA_2 = '横浜市';
const AREA_3 = '川崎市';
var array = [AREA_1, AREA_2, AREA_3]  

// 各要素のうち近隣店舗のみ取り出し
  index = 0;
  let nearbyArr = new Array();
  for (let i = 0; i < saleStoreArr.length; i++) {
    for (let j = 0; j < array.length; j++) {
      if (saleStoreArr[i].indexOf(array[j]) > 0) {
        nearbyArr[index] = saleStoreArr[i];
        index++;
      }
    }
  }

こうして取得した近隣のセール店舗一覧は、以下のようになっています。

LINEメッセージ用に成形する

近隣のセール店舗一覧が取得できましたが、HTML文字列のままでは分かりづらいので成形します。

こちらもParserライブラリを使います。Parserライブラリは、タグを指定しなければいけないわけではなく、以下のように使うことができます。

Parser.data(nearbyArr[i]).from(‘pgret=2″>’).to(‘</a>’).iterate();

このようにして、範囲を一意に特定できるような文字列を指定することで、特定の部分のみ抽出することができます。

上記と同じように、他の要素もParserライブラリを使って取り出していき、それを成形します。今回は以下のようになりました。

  // 近隣店舗のセール情報のうち必要な項目を取り出し
  for (let i = 0; i < nearbyArr.length; i++) {
    let section = '======================';
    let name;
    if (Parser.data(nearbyArr[i]).from('<span class="saleicon">').to('</span>').iterate() == '開催中') {
      name = '【開催中】 ' + Parser.data(nearbyArr[i]).from('pgret=2">').to('</a>').iterate();
    }
    else if (Parser.data(nearbyArr[i]).from('<span class="saleicon_f">').to('</span>').iterate() == '予告') {
      name = '【予告】 ' + Parser.data(nearbyArr[i]).from('pgret=2">').to('</a>').iterate();
    }
    let detail = '【セール詳細】 ' + Parser.data(nearbyArr[i]).from('<p class="saledetail">').to('</p>').iterate();
    let date = '【期間】 ' + Parser.data(nearbyArr[i]).from('<p class="saledate">').to('</p>').iterate();
    let address = '【住所】 ' + Parser.data(nearbyArr[i]).from('<td aria-label="住所" itemprop="address" class="saleadress">').to('</td>').iterate();

    let resultText = '\n' + section + '\n\n' + name + '\n' + date + '\n' + detail + '\n' + address;
    resultArray.unshift(resultText);
  }
  return resultArray;
}

LINEへメッセージを送信

LINEにセール情報を送信します。今回は、GAS側からLINEへメッセージを送信する(=プッシュメッセージ)なので、以下のように実装します。ユーザーIDやトークンの取得方法はこちらの記事にて解説しています。

// LINEへの応答
function linePush(replyText) {

  const headers = {
    "Authorization": "Bearer " + LINE_TOKEN,
    'Content-type': 'application/json'
  }
  const messages = {
    "headers": headers,
    "to": LINE_USERID,
    "messages": [{
      "type": "text",
      "text": replyText
    }]
  };
  const options = {
    "headers": headers,
    "payload": JSON.stringify(messages)
  };

  UrlFetchApp.fetch(LINE_ENDPOINT, options);
}

もし、LINEからユーザーがメッセージを送ったときにセール情報が自動返信されるようにしたい場合は、実装方法が若干変わります。詳しくは以下の記事をご参照ください。

トリガー設定で定期的にメッセージを送信

セール情報について、「週に1回」「3日おき」など、定期的にメッセージを送信するには、GASのトリガー機能を使います。

エディタの左側メニューにある時計のマークを押します。

トリガー設定のモーダルが表示されますので、希望のスケジュールを設定します。以下では週に一度実行するよう設定しています。

トリガーについては以下の記事にて詳しく解説しています。

コピペ用ソースコード

以下をGASのエディタに貼り付けて実行してください。

const LINE_TOKEN = '[LINEトークンを入れる]';
const LINE_ENDPOINT = 'https://api.line.me/v2/bot/message/push';
const LINE_USERID = '[LINEユーザーIDを入れる]';
var today = new Date();
var todayStr = Utilities.formatDate(today, 'JST', 'yyyy-MM-dd');
const URL = 'https://map.kaldi.co.jp/kaldi/articleList?account=kaldi&accmd=1&ftop=1&kkw001='+ todayStr + 'T13%3A20%3A41';
const SALE_URL = 'https://map.kaldi.co.jp/kaldi/articleList?account=kaldi&accmd=1&ftop=1';

// 通知したいエリア
const AREA_1 = '大田区';
const AREA_2 = '横浜市';
const AREA_3 = '川崎市';
var array = [AREA_1, AREA_2, AREA_3]

function saleInfo(e) {
  // LINEにセール情報を流す
  linePush(createReplyMsg());
}

// LINEで送信するテキストを生成
function createReplyMsg() {

  let result = '◆◆カルディセール情報◆◆';
  let saleList = getSaleList();

  // LINEに流すテキスト生成
  for (let i = 0; i < saleList.length; i++) {
    result = result + '\n' + saleList[i];
  }
  return result + '\n\n' + SALE_URL;
}

// セール店舗取得
function getSaleList() {

  let resultArray = new Array();

  // HTTPレスポンスを文字列で取得
  let res = UrlFetchApp.fetch(URL).getContentText("utf-8");

  // 店舗ごとに配列の要素に追加
  let saleStoreArr = new Array();
  let allStoreArr = Parser.data(res).from('<tr>').to('</tr>').iterate();
  let index = 0;
  for (let j = 0; j < allStoreArr.length; j++) {
    let startIndex = allStoreArr[j].trim().indexOf('<td scope="row"');
    if (startIndex >= 0) {
      saleStoreArr[index] = (allStoreArr[j].trim());
      index++;
    }
  }

  // 各要素のうち近隣店舗のみ取り出し
  index = 0;
  let nearbyArr = new Array();
  for (let i = 0; i < saleStoreArr.length; i++) {
    for (let j = 0; j < array.length; j++) {
      if (saleStoreArr[i].indexOf(array[j]) > 0) {
        nearbyArr[index] = saleStoreArr[i];
        index++;
      }
    }
  }

  // 近隣店舗のセール情報のうち必要な項目を取り出し
  for (let i = 0; i < nearbyArr.length; i++) {
    let section = '======================';
    let name;
    if (Parser.data(nearbyArr[i]).from('<span class="saleicon">').to('</span>').iterate() == '開催中') {
      name = '【開催中】 ' + Parser.data(nearbyArr[i]).from('pgret=2">').to('</a>').iterate();
    }
    else if (Parser.data(nearbyArr[i]).from('<span class="saleicon_f">').to('</span>').iterate() == '予告') {
      name = '【予告】 ' + Parser.data(nearbyArr[i]).from('pgret=2">').to('</a>').iterate();
    }
    let detail = '【セール詳細】 ' + Parser.data(nearbyArr[i]).from('<p class="saledetail">').to('</p>').iterate();
    let date = '【期間】 ' + Parser.data(nearbyArr[i]).from('<p class="saledate">').to('</p>').iterate();
    let address = '【住所】 ' + Parser.data(nearbyArr[i]).from('<td aria-label="住所" itemprop="address" class="saleadress">').to('</td>').iterate();

    let resultText = '\n' + section + '\n\n' + name + '\n' + date + '\n' + detail + '\n' + address;
    resultArray.unshift(resultText);
  }
  return resultArray;
}

// LINEへの応答
function linePush(replyText) {

  const headers = {
    "Authorization": "Bearer " + LINE_TOKEN,
    'Content-type': 'application/json'
  }
  const messages = {
    "headers": headers,
    "to": LINE_USERID,
    "messages": [{
      "type": "text",
      "text": replyText
    }]
  };
  const options = {
    "headers": headers,
    "payload": JSON.stringify(messages)
  };

  UrlFetchApp.fetch(LINE_ENDPOINT, options);
}

LINE_TOKEN、LINE_USERIDはご自身の環境に合わせて変更が必要です(こちらの記事にて解説しています)。

近隣エリアの設定(AREA_1~AREA_3)は市区町村を設定してください。

まとめ

今回は、近隣のカルディのセール情報を定期的にLINEに通知する方法について解説しました。

Pythonのほうがスクレイピングに便利なライブラリが多いため、一般的にはPythonが使われることが多いです。しかし、GASは環境構築不要で、誰でも簡単に開発できることと、トリガー機能による定期実行が可能であることが魅力です。

正直、今回HTMLの中身を抽出するにあたり、けっこう大変に感じました。定期実行できる環境が用意できる方やある程度プログラミングに慣れている方はPythonのほうが良いかもしれません。

ここで、まったく関係ないですがおすすめのカルディのコーヒー豆を紹介します。第1位は「イタリアンロースト」。夏は「アイスブレンド」として可愛らしいパッケージで登場します。

市販のコーヒー豆って、ちょっと酸味があって、風味がイマイチだったりしますが、イタリアンローストは酸味がなく苦味とコクが脳天に突き刺さる感じが良い。カフェオレにするとさらにおいしい。ちなみに、コーヒー豆は回転の速い店舗で買うと新鮮なものをゲットできますよ。店員さんに「いつ焙煎したものですか?」と聞くと教えてもらえます。

みなさんも是非、素敵なカルディコーヒーライフを!

¥2,860 (2023/10/25 13:57時点 | Amazon調べ)
chaso

文系出身、数字が苦手な平凡主婦。塾講師、大手企業SE、不動産事務、Webライター、QAエンジニアを経て現在RPAエンジニアとして働いています。機械音痴だけど効率化や自動化をこよなく愛しています!お仕事の依頼・ご相談は問い合わせよりお願いいたします♪

chasoをフォローする

コメント

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