🌟

GoogleAppsScript & LINE NotifyでECサイトの在庫をチェックしてLINEに通知するシステム

2022/08/08に公開

背景

4月末に子供が産まれて、ECサイトで使えるギフト券がもらえることになりました。
しかし人気のある商品は軒並み在庫切れ、さらにいつ入荷されるかの案内もなし。
いちいちサイトを見たりするのも面倒なため、欲しい商品の詳細ページをスクレイピングして入荷されたかどうかわかるようにした。

技術について

Google Apps Script

ExcelでいうVBAやマクロ的なやつ。
javascriptのように記述でき、ほかのgoogleのサービスを利用したスクリプトも組めるので便利で使いやすい。
業務でgoogleのサービスを使うとき、ついでにスクリプトでなにか自動化できないか考えると面白いかも
https://workspace.google.co.jp/intl/ja/products/apps-script/

LINE Notify

LINEに通知するサービス
トークンを付けてapiにアクセスするだけでLINEに通知することができる。
サーバー障害時にLINEに通知等、様々な使い方ができると思う。

やったこと

  1. 登録した商品データをもとに、対象サイトの商品詳細ページへアクセス
  2. アクセスした結果、ステータス200のとき入荷しているかどうかをスクレイピングでチェック(カートに入れるボタンの存在チェック)、入荷していれば1、在庫なしなら2
  3. 登録されている商品データのstatusと入荷チェックの返り値を比較して差分があればLINEに通知

商品データ

スプレッドシートのシート1に下記のような商品データを記録

product_id name status
45 ストッケ トリップ トラップ ハイチェア/ナチュラル 2
763 ストッケ トリップ トラップ ハイチェア/ヘイジーグレー 2
281 リッチェル ステップアップマグセット/ライトブルー 2

※商品データを新しく追加するとき、追加した直後にLINEに通知が欲しいのでstatusは明示的に0を入力する

ログ

スクレイピングした結果のログはシート2に記録する

date url code
2021/7/26 15:37:09 https://hoge.jp/products/detail.php?product_id=45 200
2021/7/26 15:37:41 https://hoge.jp/products/detail.php?product_id=763 200
2021/7/26 15:38:11 https://hoge.jp/products/detail.php?product_id=281 200

コード

const sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1');
const sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート2');
const productsData = getProductData();

/**
 * スプレッドシートに記録した商品の在庫が存在するかチェックするプログラム
 */
function myFunction() {
  // Logger.log(productData);

  let row = 2;
  productsData.map(function(product_data) {
    if (row > 2) {
      // 30秒待つ
      Utilities.sleep(30000);
    }
    let productId   = product_data[0];
    let productName = product_data[1];
    let oldStatus   = product_data[2];
    let url         = "https://hoge.jp/products/detail.php?product_id=" + productId;

    let res = urlFetch(url);
    if (res !== false) {
      // カートボタンチェック
      let newStatus = checkCartButton(res);

      // シートのデータと差分があればLINEで通知してシートを書き換える
      if (oldStatus != newStatus) {
        linePush(productName, url, newStatus);
        setNewStatus(row, newStatus);
      }
    }
    
    row++;
  });
}

/**
 * シート1から商品データを取得
 */
function getProductData() {
  // 範囲を指定
  let productDataRange = sheet1.getRange(
    2,    // 開始行番号
    1,    // 開始列番号
    sheet1.getLastRow()-1,   // 最終行まで
    sheet1.getLastColumn()   // 最終列まで
  );
  
  return productDataRange.getDisplayValues();
}

/**
 * 在庫ありなしステータスを上書きする
 */
function setNewStatus(row, newStatus)
{
  sheet1.getRange(row, 3).setValue(newStatus);
}

/**
 * urlにアクセスしたときのステータスコードを記録
 */
function setLog(code, url)
{
  const date = new Date().toLocaleString();
  sheet2.appendRow([date, url, code]);
}

/**
 * サイトにアクセス
 */
function urlFetch(url)
{
  //URLに対しフェッチを行ってHTMLデータを取得する
  try {
    let res = UrlFetchApp.fetch(url, {muteHttpExceptions:true});
    // ログに記録
    setLog(res.getResponseCode(), url);
    if (res.getResponseCode() == 200) {
      return res;
    } else {
      return false;
    }
  } catch(e) {
    // ログに記録
    Logger.log(e.message);
  }
}

/**
 * 商品詳細ページの解析結果
 * return 1 => 在庫あり, 2 => 在庫なし
 */
function checkCartButton(res) {
  let html = res.getContentText("utf-8");
  let $ = Cheerio.load(html);
  let cartbtn_default = $('#cartbtn_default');

  return (cartbtn_default.length > 0) ? "1" : "2";
}

/**
 * LINEに通知
 */
function linePush(productName, url, newStatus) {
  try {
    const token = 'トークン';
    const apiUrl = 'https://notify-api.line.me/api/notify';
    let text = "\n" + productName;
    // 在庫ありのとき
    if (newStatus  == "1") {
      text += "が入荷されています。\n" + url + "\n";
    } else {
      text += "売り切れました。";
    }
    
    const params = {
      method: 'post',
      headers: {
        'Authorization': 'Bearer '+ token
      },
      payload: {
        message : text,
      }
    };

    const res = UrlFetchApp.fetch(apiUrl, params);
  } catch(e) {
    console.log(e);
  }
}

結果

スクレイピングして「カートに入れる」ボタンが存在していることが確認でき、LINEに通知が来た。

エラーについて

ステータス200でスクレイピングできるときもあるが、たまに403エラーが返ってくることもある(なぜかは不明)
複数の商品詳細ページをスクレイピングするのに30秒の待機時間を設けたら403エラーになる確率は減ったが、依然403になることもある。

所感

最初はわからないことが多かったが基本がjavascriptだったためか以外に簡単に実装することができた。

Discussion