ใน Codelab นี้คุณจะได้เรียนรู้การสร้าง LINE Chatbot ที่ช่วยรายงานราคาทองคำด้วย Firebase และเทคนิค Web Scraping ในการดึงราคาทองคำจากหน้าเว็บมาแสดง รวมถึงการตั้ง Cronjob เพื่อคอยอัพเดทข้อมูลทุกๆชั่วโมง

สิ่งที่คุณจะได้เรียนรู้


สิ่งที่คุณต้องเตรียมพร้อมก่อนเริ่ม Codelab

สร้าง LINE Official Account

จุดเริ่มต้นของการพัฒนา LINE Chatbot คือคุณจะต้องสร้าง LINE OA(LINE Official Account) และเปิดใช้งาน Messaging API

  1. เข้าไปที่ https://manager.line.biz แล้วเลือก Log in with LINE account(สีเขียว) เพื่อเข้าสู่ระบบ

  1. เข้าสู่ระบบด้วยบัญชี LINE ของคุณให้เรียบร้อย
  2. กดสร้าง LINE OA จากปุ่ม Create LINE official account สำหรับผู้ทีสร้าง LINE OA ครั้งแรก หรือกด Create new ทางด้านซ้ายสำหรับผู้ที่เคยสร้าง LINE OA แล้ว

  1. ให้ระบุข้อมูลต่างๆลงไปในฟอร์ม แล้วกด ตกลง

  1. จากนั้นให้ยืนยันรายละเอียดในการสร้าง LINE OA เป็นอันเสร็จสิ้น


เปิดใช้งาน Messaging API

หลังจากที่เรามี LINE OA เรียบร้อยแล้ว ขั้นตอนนี้จะพาทุกคนไปเพิ่มความสามารถให้ LINE OA ของเรากลายเป็น LINE Chatbot ได้

  1. เข้าไปที่ https://manager.line.biz ในกรณีที่เรามีบัญชี LINE OA ที่สร้างไว้แล้ว หน้านี้จะแสดงบัญชี LINE OA ต่างๆที่เรามี ก็ให้เรากดเลือกบัญชี LINE OA ที่เราต้องการ

  1. ให้เราไปทีเมนู Settings > Messaging API แล้วให้กดปุ่ม Enable Messaging API

  1. หากเป็นการ Enable Messaging API ครั้งแรกของบัญชี LINE Business ID จะเจอหน้าให้ลงทะเบียน Developer info ก็ให้กรอก ชื่อ และ อีเมล

  1. จากนั้นให้สร้าง Provider ใหม่ หรือเลือก Provider เดิมกรณีที่เคยสร้างไปแล้ว

  1. ระบุ URL ของ Privacy Policy และ Terms of Use (ถ้ามี) หากยังไม่มีก็สามารถกดปุ่ม ok ข้ามไปได้

  1. ยืนยันการเปิดใช้งาน Messaging API ด้วยการกด Ok

  1. เมื่อเจอหน้านี้ ก็แปลว่าคุณได้เปิดใช้งาน Messaging API ให้กับบัญชี LINE OA เรียบร้อยแล้ว

เพิ่ม Chatbot เป็นเพื่อนและตั้งค่า Channel

ขั้นตอนนี้เราจะเข้าไปใช้งาน LINE Developers Console ซึ่งเป็นเว็บไซต์สำหรับการบริหารจัดการ LINE Chatbot(LINE OA ที่เปิดใช้งาน Messaging API แล้ว) ในส่วนของนักพัฒนา

  1. เข้าไปที่ https://developers.line.biz/console/
  2. ให้กดเลือก Provider ที่ต้องการ

  1. เราจะพบกับบัญชี LINE OA ที่เราได้เปิดใช้งาน Messaging API ไว้ ซึ่งในที่นี้เราจะเรียกมันว่า Channel(Channel จะเปรียบเสมือน Chatbot หรือ App) ก็ให้กดเลือก Channel ที่ต้องการ

  1. ให้ไปที่ Tab ชื่อ Messaging API และทำการแสกน QR code ด้วยแอป LINE เพื่อเพิ่ม Chatbot เป็นเพื่อน

  1. ให้ปิด Auto-reply messages เนื่องจากฟีเจอร์นี้จะเป็น default การตอบกลับของ Chatbot ซึ่งไม่จำเป็นต้องใช้ฟีเจอร์นี้

  1. กลับมาที่ Channel ที่เราสร้างใน Tab ชื่อ Messaging API ตรงส่วนของ Channel access token ให้กดปุ่ม Issue

เบื้องหลังของ Chatbot ตัวนี้ เราจะใช้บริการใน Firebase อย่าง Cloud Functions for Firebase และ Cloud Firestore ดังนั้นขั้นตอนนี้เราจะมาสร้างโปรเจค Firebase เพื่อใช้งานกัน

สร้างโปรเจคใน Firebase

  1. ให้ Sign in ใน Firebase console ด้วย Google account
  2. ในหน้า Firebase console ให้คลิก Add project จากนั้นตั้งชื่อโปรเจคตามต้องการ

  1. เมื่อกด Continue แล้วให้ข้ามการตั้งค่า Google Analytics ไป เพราะคุณจะไม่ได้ใช้มันในโปรเจคนี้


เปลี่ยนแพลนจาก Spark ไปเป็น Blaze (Pay as you go)

เนื่องจาก Cloud Functions for Firebase มีเงื่อนไขว่า หากต้องการไป request ตัว APIs ที่อยู่ภายนอก Google คุณจำเป็นจะต้องใช้ Blaze plan(เราจะต้องไปเรียก Messaging API ของ LINE)


เปิดใช้งาน Cloud Firestore

เนื่องจาก Chatbot ตัวนี้จะเก็บข้อมูลราคาทองคำไว้ใน Cloud Firestore เพื่อเปรียบเทียบกับราคาทองคำล่าสุดว่าเหมือนหรือแตกต่าง ดังนั้นให้เราจะมาเปิดใช้งาน database ตัวนี้กัน

  1. ในหน้าโปรเจคที่เราสร้างจะเลือกเมนูชื่อ Firestore ที่อยู่ทางซ้ายมือ
  2. ในหน้า Cloud Firestore ให้คลิก Create database

  1. ให้เลือก Start in production mode เพื่อที่จะป้องกันไม่ให้ client สามารถเขียนและอ่านข้อมูลได้

  1. เลือก location ของ database แล้วคลิก done

การติดตั้ง Firebase CLI

Firebase CLI เป็นเครื่องมือที่จำเป็นสำหรับการ deploy ตัวฟังก์ชันที่เราพัฒนาขึ้น อีกทั้งยังสามารถจำลองการทำงานฟังก์ชัน(Emulate) ภายในเครื่องที่เราพัฒนาอยู่(Locally) ได้

  1. เปิด Terminal ขึ้นมาแล้ว run คำสั่ง
npm install -g firebase-tools
  1. ตรวจสอบว่า Firebase CLI ได้ติดตั้งเรียบร้อยแล้วโดย run คำสั่ง (หากสำเร็จจะเห็นเลขเวอร์ชัน)
firebase --version


Initialize โปรเจค

  1. รันคำสั่งนี้ จากนั้นตัว browser จะเปิดขึ้นมาให้เราเข้าสู้ระบบด้วย Google account เดียวกันกับที่สร้างโปรดจคใน Firebase
firebase login
  1. สร้างโฟลเดอร์เปล่า(ตัวอย่างโฟลเดอร์ชื่อ bot) แล้วให้ shell เข้าไปในนั้น
mkdir bot
cd bot
  1. เมื่อเข้ามาในโฟลเดอร์แล้ว ให้ Initial โปรเจคด้วยคำสั่ง
firebase init functions
  1. เลือก Use an existing project จากนั้นจะเห็นเชื่อโปรเจคที่เราสร้างไว้ ก็กด enter ต่อไป

  1. ถัดไปจะมีตัวเลือกภาษา 2 ตัวคือ JavaScript และ TypeScript โดยตัวอย่างนี้ให้เลือก JavaScript
  2. จากนั้นมันจะถามว่าจะให้ติดตั้ง ESLint ไหม ตรงนี้แนะนำให้ตอบ N ไปก่อน(สำหรับมือใหม่)
  3. สุดท้ายมันจะถามว่าจะให้ติดตั้ง dependencies เลยไหมก็ให้ตอบว่า Y ไป

ขั้นตอนนี้เราจะสร้างฟังก์ชันสำหรับตั้งเวลาและดึงข้อมูลราคาทองคำจากหน้าเว็บ สมาคมค้าทองคำ

เพิ่ม Dependencies ที่จำเป็นสำหรับโปรเจคนี้

  1. ในโฟลเดอร์ functions ให้เปิดไฟล์ package.json ขี้นมา
  2. เพิ่ม axios และ cheerio ลงไปใน dependencies
"dependencies": {
  "firebase-admin": "^9.5.0",
  "firebase-functions": "^3.13.2",
  "axios": "^0.21.1",
  "cheerio": "^1.0.0-rc.5"
}
  1. ใน Terminal ให้เรา shell เข้าไปในโฟลเดอร์ชื่อ functions จากนั้น install ตัว dependencies ที่เพิ่มเข้ามาใหม่ด้วยคำสั่ง
npm install
  1. เปิดไฟล์ index.js แล้วเริ่มจากการ import ตัว axios และ cheerio เข้ามา
const functions = require("firebase-functions");
const axios = require("axios");
const cheerio = require("cheerio");


สร้างฟังก์ชันสำหรับตั้งเวลาด้วย Cloud Scheduler (Cron Job)

ขั้นตอนนี้จะสร้างฟังก์ชันชื่อ gold ในไฟล์ index.js โดยฟังก์ชันดังกล่าวจะเป็นแบบที่สามารถตั้งเวลาให้ทำงานอัตโนมัติได้ ซึ่งในตัวอย่างนี้จะตั้งเวลาให้ฟังก์ชันทำงานชั่วโมงละครั้ง เนื่องจากราคาทองอัพเดททั้งวัน แต่ก็ไม่ได้อัพเดทถี่ระดับนาที

exports.gold = functions.pubsub.schedule("0 */1 * * *").timeZone("Asia/Bangkok").onRun(async context => {
  // ...
});


ดึงข้อมูลราคาทองด้วยเทคนิค Web Scraping

ขั้นตอนนี้เราจะไปดึงข้อมูลราคาทองจาก สมาคมค้าทองคำ ด้วยเทคนิค Web Scraping(การแกะข้อมูลจาก HTML DOM)

  1. ภายในฟังก์ชัน gold ให้ใช้ axios ดูด HTML ของเว็บ สมาคมค้าทองคำ ออกมา
const response = await axios.get("https://goldtraders.or.th");
const html = response.data;
  1. ถัดมาให้ใช้ cheerio ในการแปลง HTML ทั้งหมดมาเป็น DOM Model
const $ = cheerio.load(html);

ลำดับถัดไปเราจะสร้างตัว selector กัน โดยให้เราเปิดหน้าเว็บ สมาคมค้าทองคำ ในเบราว์เซอร์ แล้วมองหาส่วนของข้อมูลที่ต้องการก่อน ซึ่งตัวอย่างนี้เราต้องการราคา ซื้อ-ขาย ในกรอบสีแดงตามรูปด้านล่าง

จากนั้นให้คลิกขวาแล้ว Inspect ตำแหน่งที่วงสีแดงเอาไว้ แล้วหา identity ที่เป็นเหมือน parent ของส่วนข้อมูลที่เราสนใจให้เจอ และหา element ชั้นสุดท้ายที่ครอบราคา ซื้อ-ขาย ทั้ง 4 ไว้

  1. จากภาพด้านบน กลับมาที่ไฟล์ index.js ในบรรทัดล่างสุดของฟังก์ชัน gold ให้กำหนด selector ในฟังก์ชัน แบบนี้
const selector = $("#DetailPlace_uc_goldprices1_GoldPricesUpdatePanel font[color]");
  1. เพื่อความรอบคอบในกรณีที่หน้าเว็บ สมาคมค้าทองคำ มีการเปลี่ยนแปลง ในบรรทัดล่างสุดของฟังก์ชัน gold ให้เราจะเพิ่มเงื่อนไขว่าข้อมูลราคา ซื้อ-ขาย นั้นยังสามารถดึงได้นะ หากไม่ได้เราจะให้ฟังก์ชันนี้หยุดทำงานทันที
if (selector.length !== 4) {
  return null;
}
  1. คราวนี้ในบรรทัดสุดท้ายของฟังก์ชัน gold ก็มาสกัดเอาราคา ซื้อ-ขาย ทั้ง 4 ออกมา จากนั้นก็เอามาต่อกันและคั่นด้วย pipe
let priceCurrent = ""
selector.each((index, element) => {
  if (index === 0) {
    priceCurrent = $(element).text()
  } else {
    priceCurrent = priceCurrent.concat("|", $(element).text())
  }
});

// ผลลัพธ์จะได้ประมาณนี้ 26,400.00|26,300.00|26,900.00|25,832.64

ขั้นตอนนี้เราจะใช้ Cloud Firestore ซึ่งเป็น database มาเก็บข้อมูลราคา และนำมาตรวจสอบความเปลี่ยนแปลงกับข้อมูลในปัจจุบัน ก่อนจะส่งข้อความไปหาผู้ใช้กัน

ตัวอย่างนี้ผมจะออกแบบ database ให้มี collection ที่ชื่อ line, document ชื่อ gold และ field ชื่อ price ใน Firebase console ดังรูป

  1. ในไฟล์ index.js ให้ import ตัว dependency ชื่อ firebase-admin เข้ามาที่จุดบนสุดของไฟล์ และให้ initial มันให้เรียบร้อย
const admin = require("firebase-admin");
admin.initializeApp();
  1. ในฟังก์ชัน gold ให้ดึงข้อมูลราคาทองคำล่าสุดใน database ออกมา(priceLast) แล้วสร้างเงื่อนไขว่าหากยังไม่มีข้อมูล หรือ ข้อมูลไม่ตรงกับข้อมูลล่าสุด(priceCurrent) ก็ให้อัพเดทข้อมูลล่าสุดลงไปใน database
let priceLast = await admin.firestore().doc('line/gold').get()
if (!priceLast.exists || priceLast.data().price !== priceCurrent) {
  await admin.firestore().doc('line/gold').set({ price: priceCurrent });
}

สร้างฟังก์ชันเพื่อ Broadcast ข้อมูลราคาทองคำไปยังผู้ติดตามทุกคน

  1. สร้างฟังก์ชันชื่อ broadcast ที่จุดล่างสุดของไฟล์ index.js พร้อมรับตัวแปร priceCurrent แล้วนำแยกออกเป็น 4 ส่วนด้วยการ split() จากนั้นก็เขียนโค้ดสำหรับ Broadcast ข้อมูลราคาทองคำไปให้ผู้ติดตาม Chatbot ทุกคน
const broadcast = (priceCurrent) => {
  const prices = priceCurrent.split("|");
  return axios({
    method: "post",
    url: "https://api.line.me/v2/bot/message/broadcast",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer xxxxx"
    },
    data: JSON.stringify({
      messages: [{
        "type": "flex",
        "altText": "Flex Message",
        "contents": {
          "type": "bubble",
          "size": "giga",
          "body": {
            "type": "box",
            "layout": "vertical",
            "paddingAll": "8%",
            "backgroundColor": "#FFF9E2",
            "contents": [
              {
                "type": "text",
                "text": "ราคาทองคำ",
                "weight": "bold",
                "size": "xl",
                "align": "center"
              },
              {
                "type": "box",
                "layout": "vertical",
                "margin": "xxl",
                "spacing": "sm",
                "contents": [
                  {
                    "type": "box",
                    "layout": "baseline",
                    "spacing": "sm",
                    "contents": [
                      {
                        "type": "text",
                        "text": "ราคารับซื้อ",
                        "wrap": true,
                        "color": "#E2C05B",
                        "flex": 5,
                        "align": "end"
                      },
                      {
                        "type": "text",
                        "text": "ราคาขาย",
                        "flex": 2,
                        "color": "#E2C05B",
                        "align": "end"
                      }
                    ]
                  },
                  {
                    "type": "box",
                    "layout": "baseline",
                    "spacing": "sm",
                    "contents": [
                      {
                        "type": "text",
                        "text": "ทองคำแท่ง",
                        "flex": 3
                      },
                      {
                        "type": "text",
                        "text": prices[0],
                        "wrap": true,
                        "size": "sm",
                        "flex": 2,
                        "align": "end"
                      },
                      {
                        "type": "text",
                        "text": prices[1],
                        "flex": 2,
                        "size": "sm",
                        "align": "end"
                      }
                    ]
                  },
                  {
                    "type": "separator"
                  },
                  {
                    "type": "box",
                    "layout": "baseline",
                    "spacing": "sm",
                    "contents": [
                      {
                        "type": "text",
                        "text": "ทองรูปพรรณ",
                        "flex": 3
                      },
                      {
                        "type": "text",
                        "text": prices[2],
                        "wrap": true,
                        "size": "sm",
                        "flex": 2,
                        "align": "end"
                      },
                      {
                        "type": "text",
                        "text": prices[3],
                        "flex": 2,
                        "size": "sm",
                        "align": "end"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        }
      }]
    })
  })
}
  1. กลับมาที่ฟังก์ชัน gold แล้วให้เรียกใช้งานฟังก์ชัน broadcast() หลังจากที่อัพเดท database แล้ว
if (!priceLast.exists || priceLast.data().price !== priceCurrent) {
  await admin.firestore().doc('line/gold').set({ price: priceCurrent });
  broadcast(priceCurrent);
}
  1. เมื่อทุกอย่างพร้อมแล้ว ก็ deploy ฟังก์ชันผ่าน terminal ด้วยคำสั่ง
firebase deploy --only functions


ผลลัพธ์

หาก deploy สำเร็จ เราจะเห็นฟังก์ชันของเราแสดงอยู่ในเมนู Functions ใน Firebase console

และผลลัพธ์ที่ได้ก็จะมีหน้าตาประมาณนี้

ยินดีด้วยครับ ถึงตรงนี้คุณก็มี LINE Chatbot สำหรับการรายงานราคาทองคำเป็นของคุณเองแล้ว!!!

สิ่งที่คุณได้เรียนรู้ใน Codelab นี้

เรียนรู้เพิ่มเติม

Reference docs

บอกเราหน่อยว่า Codelab ชุดนี้เป็นอย่างไรบ้าง