iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0
Modern Web

職缺資訊平台—Jobscanner系列 第 8

[前置作業] Cloud Functions 實作 — 今日放映

  • 分享至 

  • xImage
  •  

目標

建立 cloud functions 抓取電影時刻表,讓 cloud scheduler 可以定期觸發,並將今日放映的電影資訊顯示在 Slack 上。


包成 Cloud Functions

將先前寫的抓取電影時刻的程式碼使用 @google-cloud/functions-framework 包成 cloud functions 形式,選擇 HTTP Trigger,並勾選 「需要驗證」

index.js 內容如下:

'use strict';

const functions = require("@google-cloud/functions-framework");
const axios = require("axios");
const cheerio = require("cheerio");

const url = "https://meet.eslite.com/tw/tc/gallery/movieschedule/201803020001"; // 目標網址

// getTimetable 為 entry point
functions.http("getTimetable", async(req, res) => {
  let externalRes = await axios.get(url);
  let $ = cheerio.load(externalRes.data);
  let list = [];

  $(".film_list .box").each(function(i, elem) {
    let name = $(this).find(".intro .left > p").text();
    let timetable = [];
    $(this).find(".time-swiper .swiper-slide").each(function (j, slide) {
      let date = $(slide).find("p").text();
      let time = [] ;

      $(slide).find("ul li").each(function(k, text) {
        time.push($(text).text());
      })
      timetable.push({date, time});
    })

    list.push({name, timetable});
  });

  res.status(200).send(list);
})

搭配 Cloud Scheduler

Cloud Scheduler 可以用來呼叫需驗證的 HTTP targets,例如:Cloud Functions or Cloud Run
Function URL 即為要執行的目標(HTTP targets)。

注意事項:

  • 建立一個服務帳戶 (service account)
    • 必須和 Cloud Scheduler 在同一個專案中
    • 不要使用 Cloud Scheduler 預設的服務帳戶
  • 如果要執行 Google Cloud 中的目標對象,可透過 IAM (Identity and Access Management) 管理服務帳戶
    • 替服務帳戶增加 Cloud Functions Invoker

*補充:服務帳戶 Service account?
利用服務帳戶來呼叫需驗證的 API,服務帳戶可作為 IAM 主體/使用者,管理服務帳戶對 Google Cloud 資源的存取權限。
服務帳戶類似於在 Google Cloud 建立一個虛擬使用者。

操作流程: (以 Google Cloud console 為例)

  • 進入IAM 與管理 - 服務帳戶,點擊建立服務帳戶

    • 填寫服務名稱 (例如:movie-service)
    • 選擇角色 Cloud Function 叫用者
  • 進入 「Cloud Scheduler」 點擊建立工作

    • 填寫排程名稱
    • 選擇地區
    • 執行頻率 0 8 * * * 、選擇台北時區
    • 執行目標 HTTP
    • 網址填入 Functions URL
    • 選擇 HTTP method
    • 驗證選擇 OIDC
    • 服務帳戶選擇先前建立好的 movie-servic
    • 目標一樣填 Functions URL

*gcloud 操作可參考 Google Cloud 官方文件 - Use authentication with HTTP targets


送訊息到 Slack

假設需要將 Functions 執行的結果送到 Slack 通知,可依 Sending messages using Incoming Webhooks 說明操作。

  1. 建立一個 Slack app (發話者)
  2. 啟用 Incoming Webhooks
  3. 新建 Webhook,選擇 channel
  4. 利用 Webhook URL 發送訊息
  5. 客製化訊息格式 (optional)

透過 Slack 的 Block Kit Builder 客製訊息的樣式,或是直接選擇現成的 Template 進行調整


使用 try...catch 處理錯誤,index.js 建立 notifySlack 函式傳送 slack 訊息。

// index.js
// 略
const notifySlack = async (payload) => {
  await axios.post(slackUrl, payload)
}

functions.http('getTimetable', async(req, res) => {
  try {
    // 略
    notifySlack('成功取得電影時刻');
  } catch (e) {
    notifySlack('無法取得電影時刻');
  }
})

使用 Slack 的 Block Kit Builder 將欲顯示的電影資訊做樣式排版,製作 Slack 訊息的樣板,如下圖:
https://ithelp.ithome.com.tw/upload/images/20230922/20128122fXonWYBVYt.png


完整 Cloud Functions 內容

'use strict';

const functions = require('@google-cloud/functions-framework');
const axios = require("axios");
const cheerio = require("cheerio");
const url = "https://meet.eslite.com/tw/tc/gallery/movieschedule/201803020001";
const slackUrl = "https://hooks.slack.com/services/xxxxxxxxxxxxxx";
const movieResultTemplate = require("./slack-movie-card");

const notifySlack = async (payload) => {
  await axios.post(slackUrl, payload)
}

functions.http('getTimetable', async(req, res) => {
  try {
    let externalRes = await axios.get(url);
    let $ = cheerio.load(externalRes.data);
    let list = [];

    $(".film_list .box").each(function(i, elem) {
      // 電影名稱
      let name = $(this).find(".intro .left > p").text();
      // 電影介紹連結
      let link = `https://meet.eslite.com${$(this).find(".intro .right .btn-detail").attr("href")}`;
      // 電影縮圖連結
      let thumbUrl = `https://meet.eslite.com${$(this).find(".img img").attr("src")}`;
      // 電影縮圖文字
      let thumbAlt = $(this).find(".img img").attr("alt");
      // 電影資訊
      let infos = [];
      // 時刻表
      let timetable = [];

      // 取得電影資訊 (級別、片長...)
      $(this).find(".intro .right > ul > li").each(function (index, li) {
        let info = $(li).text();
        infos.push(info);
      });

      // 取得電影每日時刻
      $(this).find(".time-swiper .swiper-slide").each(function (j, slide) {
        let date = $(slide).find("p").text();
        let time = [];

        $(slide).find("ul li").each(function(k, text) {
          time.push($(text).text());
        })
        timetable.push({date, time});
      })

      list.push({name, link, thumbUrl, thumbAlt, infos, timetable})
    });

    // ---- 轉換成 slack template 格式 ----
    let movieResult = {...movieResultTemplate.result},
        cardResult = [],
        today = `${new Date().getMonth() + 1}/${new Date().getDate()}`;

    // 遍歷每一部電影資訊
    list.forEach((item) => {
      const regex = /\d+\/\d+/;
      // 判斷第一個放映日期是否為今日
      let movieDate = item.timetable[0].date.match(regex)[0];
      if (movieDate !== today) return;

      let time = item.timetable[0].time.join(' | ');
      let card = JSON.parse(JSON.stringify(movieResultTemplate.card));
      let infos = item.infos.join('\n');

      card[0].text.text = `*<${item.link}|${item.name}>*\n${infos}\n時刻: ${time}`;
      card[0].accessory.image_url = item.thumbUrl;
      card[0].accessory.alt_text = item.thumbAlt;
      cardResult.push(...card);
    })

    movieResult.blocks[0].text.text = `${today} 今日放映 :movie_camera:`
    movieResult.blocks.push(...cardResult);
    notifySlack((movieResult));
    res.status(200).send(list);
  } catch (e) {
    console.log(e);
    notifySlack({text:`:bangbang: 無法取得電影時刻`, emoji: true});
  }
})


結果

每日 8:00 排程會執行 Cloud Functions,到電影時刻表網頁抓取資訊,將相關資訊整理成 Slack 訊息格式,透過 Slack Webhook 發送訊息到 Channel 中。

*Slack 訊息
https://ithelp.ithome.com.tw/upload/images/20230923/20128122r1IB1eEUyV.png

*排程執行狀態
https://ithelp.ithome.com.tw/upload/images/20230923/20128122k5xhDCBv8Y.png


參考資料

Use authentication with HTTP targets
Using-scheduler-invoke-private-functions-OIDC
Sending messages using Incoming Webhooks


上一篇
[前置作業] 建立 Cloud Functions
下一篇
[前置作業] Firestore ?
系列文
職缺資訊平台—Jobscanner31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言