自動降價通知系統

使用 Google Apps Script 實作的即時商品價格監控系統,整合 LINE Bot 實現自動化降價通知,畢竟價格下降,就是降價了。

系統特色

  • 自動監控:Google Apps Script 定時執行器全天監控價格
  • 即時通知:LINE Bot 整合,降價第一時間推播通知
  • 商品追蹤:MOMO 、 PChome 商品價格追蹤
  • Flex Message:降價通知訊息設計
  • 完全免費:無需付費訂閱或伺服器費用

系統架構

使用者 (LINE Bot)
     ↓
┌─────────────────┐
│ Google Apps     │ → 定時執行器 → 商品價格監控
│ Script          │
└─────────────────┘
       ↓
┌─────────────────┐
│ Google Sheets   │ ← 使用者資料與價格紀錄
└─────────────────┘
       ↓
┌─────────────────┐
│ LINE Messaging  │ ← 降價通知發送
│ API             │
└─────────────────┘

專案結構

price-alert/
├── Code.gs            # LINE Bot 主要邏輯
├── updata.gs          # 價格監控與通知
└── List.xlsx          # Google Sheets

核心技術實作

1. 商品資訊擷取

function fetchData(iCode) {
  var url = "https://www.momoshop.com.tw/goods/GoodsDetail.jsp?i_code=" + iCode;
  var response = UrlFetchApp.fetch(url);
  var content = response.getContentText();

  // 產品名稱
  var titleRegex = /<meta property="og:title" content="([^"]+)">/;
  var titleMatch = content.match(titleRegex);
  var productName = titleMatch ? titleMatch[1] : "未找到產品名稱";

  // 產品價格
  var priceRegex = /<meta property="product:price:amount" content="([^"]+)"/;
  var priceMatch = content.match(priceRegex);
  var productPrice = priceMatch ? priceMatch[1] : "未找到產品價格";

  Logger.log("產品名稱: " + productName);
  Logger.log("產品價格: " + productPrice);

  var goods = [];
  goods.push(productName);
  goods.push(productPrice);
  Logger.log(goods);

  return goods
}

2. 價格比較與監控

function compareData() {
  var msg = "";
  for (var i=1;i<LastRowU;i++){
    var userid = sheetU.getRange(i+1,1).getValue();
    var sheetP = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(userid);
    var LastRowP = sheetP.getLastRow();
    for(var j=1;j<LastRowP;j++){
      var iCode = sheetP.getRange(j+1,1).getValue();
      var product = fetchData(iCode);
      if(product[1] != sheetP.getRange(j+1,3).getValue()){
        if(product[1] > sheetP.getRange(j+1,3).getValue()){
          msg = "漲價了"
          sheetP.getRange(j+1,3).setValue(product[1]);
        }
        else if(product[1] < sheetP.getRange(j+1,3).getValue()){
          msg = "降價了"
          sheetP.getRange(j+1,3).setValue(product[1]);
          sendLine(userid,iCode,msg,product);
        }
      }
    }
  }
}

3. LINE Bot 訊息處理

function doPost(e) {
  let event = JSON.parse(e.postData.contents).events[0];
  var userid = event.source.userId;
  var profile = client.getProfile(userid);
  var name = profile.displayName;
  var message = event.message.text;
  var date = new Date(event.timestamp);

  sheet.getRange(LastRow+1,1).setValue(name);
  sheet.getRange(LastRow+1,2).setValue(message);
  sheet.getRange(LastRow+1,3).setValue(date);
  sheet.getRange(LastRow+1,4).setValue(event);

  if(message == "加入會員"){
    var users = [];
    var count = 0;
    var text = "";
    for (var i=1;i<LastRowU;i++){
      users.push(sheetU.getRange(i+1,1).getValue());
      if(userid == sheetU.getRange(i+1,1).getValue()){
        count = count + 1;
      }
    }
    if(count >= 1){
      text = "已加入,無須再次點擊";
    }
    else{
      sheetU.getRange(LastRowU+1,1).setValue(userid);
      text = "加入成功";
      ss.insertSheet(userid);
      var sheetP = ss.getSheetByName(userid);
      sheetP.getRange(1, 1).setValue("iCode").setBackground("#ffe599").setFontWeight("bold").setHorizontalAlignment("center");
      sheetP.getRange(1, 2).setValue("productName").setBackground("#ffe599").setFontWeight("bold").setHorizontalAlignment("center");
      sheetP.getRange(1, 3).setValue("productPrice").setBackground("#ffe599").setFontWeight("bold").setHorizontalAlignment("center");
    }

    var echo = {
      type: 'text',
      text: text
    };

    client.replyMessage(event.replyToken, echo);
  }
  else if (message.includes("momoshop.com.tw")){
    var text = "";
    var iCode = extractParameterFromUrl(message, "i_code");

    try{
      var sheetP = ss.getSheetByName(userid);
      var LastRowP = sheetP.getLastRow();
      var rangeP = sheetP.getRange(1, 1, LastRowP, 1);
      var valuesP = rangeP.getValues();

      // 檢查是否有重複的 iCode
      var isDuplicate = false;
      for (var i = 0; i < valuesP.length; i++) {
        if (valuesP[i][0] == iCode) {
          isDuplicate = true;
          break;
        }
      }

      if (isDuplicate) {
        text = "重複商品";
      } else {
        sheetP.getRange(LastRowP + 1, 1).setValue(iCode);

        var goods = fetchData(iCode);
        sheetP.getRange(LastRowP + 1, 2).setValue(goods[0]);
        sheetP.getRange(LastRowP + 1, 3).setValue(goods[1]);

        text = "商品添加成功\n商品名稱 : " + goods[0] + "\n商品價格 : " + goods[1];
      }
    }
    catch(e){
      text = "尚未加入會員";
    }

    var echo = {
      type: 'text',
      text: text
    };
  }

  client.replyMessage(event.replyToken, echo);
}

4. Flex Message 降價通知

function sendLine(userid,iCode,msg,product){
  var uuu = "https://www.momoshop.com.tw/goods/GoodsDetail.jsp?i_code=" + iCode;
  var date = Utilities.formatDate(new Date(), SpreadsheetApp.getActive().getSpreadsheetTimeZone(), "yyyy/MM/dd HH:mm");

  var url = 'https://api.line.me/v2/bot/message/push';
  UrlFetchApp.fetch(url, {
    'headers': {
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
        'to':  userid,
        'messages': [{
          "type": "flex",
          "altText": msg.substring(0,2) + "通知",
          "contents":{
            "type": "bubble",
            "body": {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": product[0] + msg,
                  "weight": "bold",
                  "size": "xl"
                },
                {
                  "type": "box",
                  "layout": "vertical",
                  "margin": "md",
                  "contents": [
                    {
                      "type": "text",
                      "text": "價格 : " + product[1],
                      "size": "sm",
                      "margin": "md",
                      "flex": 0,
                      "weight": "bold"
                    }
                  ]
                },
                {
                  "type": "box",
                  "layout": "vertical",
                  "margin": "lg",
                  "spacing": "sm",
                  "contents": [
                    {
                      "type": "box",
                      "layout": "baseline",
                      "spacing": "sm",
                      "contents": [
                        {
                          "type": "text",
                          "text": "Time",
                          "color": "#aaaaaa",
                          "size": "sm",
                          "flex": 1
                        },
                        {
                          "type": "text",
                          "text": date,
                          "wrap": true,
                          "color": "#666666",
                          "size": "sm",
                          "flex": 5
                        }
                      ]
                    }
                  ]
                }
              ]
            },
            "footer": {
              "type": "box",
              "layout": "vertical",
              "spacing": "sm",
              "contents": [
                {
                  "type": "button",
                  "style": "link",
                  "height": "sm",
                  "action": {
                    "type": "uri",
                    "label": "商品連結",
                    "uri": uuu
                  }
                },
                {
                  "type": "box",
                  "layout": "vertical",
                  "contents": [],
                  "margin": "sm"
                }
              ],
              "flex": 0
            }
          }
        }]
      })
    }
  )
}

系統展示

降價通知系統示例 也可使用Discord Webhook 也可使用Discord Webhook


相關資料

🔗 加入 LINE Bot (@338zpcoq)

📊 完整原始碼 (Google Sheet 建立副本於擴充功能查看 App Script)