ใน Codelab นี้คุณจะได้เรียนรู้การสร้าง LINE Chatbot ที่ช่วยรายงานราคาทองคำด้วย Firebase และเทคนิค Web Scraping ในการดึงราคาทองคำจากหน้าเว็บมาแสดง รวมถึงการตั้ง Cronjob เพื่อคอยอัพเดทข้อมูลทุกๆชั่วโมง
จุดเริ่มต้นของการพัฒนา LINE Chatbot คือคุณจะต้องสร้าง LINE OA(LINE Official Account) และเปิดใช้งาน Messaging API
หลังจากที่เรามี LINE OA เรียบร้อยแล้ว ขั้นตอนนี้จะพาทุกคนไปเพิ่มความสามารถให้ LINE OA ของเรากลายเป็น LINE Chatbot ได้
ขั้นตอนนี้เราจะเข้าไปใช้งาน LINE Developers Console ซึ่งเป็นเว็บไซต์สำหรับการบริหารจัดการ LINE Chatbot(LINE OA ที่เปิดใช้งาน Messaging API แล้ว) ในส่วนของนักพัฒนา
เบื้องหลังของ Chatbot ตัวนี้ เราจะใช้บริการใน Firebase อย่าง Cloud Functions for Firebase และ Cloud Firestore ดังนั้นขั้นตอนนี้เราจะมาสร้างโปรเจค Firebase เพื่อใช้งานกัน
เนื่องจาก Cloud Functions for Firebase มีเงื่อนไขว่า หากต้องการไป request ตัว APIs ที่อยู่ภายนอก Google คุณจำเป็นจะต้องใช้ Blaze plan(เราจะต้องไปเรียก Messaging API ของ LINE)
เนื่องจาก Chatbot ตัวนี้จะเก็บข้อมูลราคาทองคำไว้ใน Cloud Firestore เพื่อเปรียบเทียบกับราคาทองคำล่าสุดว่าเหมือนหรือแตกต่าง ดังนั้นให้เราจะมาเปิดใช้งาน database ตัวนี้กัน
Firebase CLI เป็นเครื่องมือที่จำเป็นสำหรับการ deploy ตัวฟังก์ชันที่เราพัฒนาขึ้น อีกทั้งยังสามารถจำลองการทำงานฟังก์ชัน(Emulate) ภายในเครื่องที่เราพัฒนาอยู่(Locally) ได้
npm install -g firebase-tools
firebase --version
firebase login
mkdir bot
cd bot
firebase init functions
ขั้นตอนนี้เราจะสร้างฟังก์ชันสำหรับตั้งเวลาและดึงข้อมูลราคาทองคำจากหน้าเว็บ สมาคมค้าทองคำ
functions
ให้เปิดไฟล์ package.json
ขี้นมาaxios
และ cheerio
ลงไปใน dependencies"dependencies": {
"firebase-admin": "^9.5.0",
"firebase-functions": "^3.13.2",
"axios": "^0.21.1",
"cheerio": "^1.0.0-rc.5"
}
functions จากนั้น
install ตัว dependencies ที่เพิ่มเข้ามาใหม่ด้วยคำสั่งnpm install
axios
และ cheerio
เข้ามาconst functions = require("firebase-functions");
const axios = require("axios");
const cheerio = require("cheerio");
ขั้นตอนนี้จะสร้างฟังก์ชันชื่อ gold
ในไฟล์ index.js
โดยฟังก์ชันดังกล่าวจะเป็นแบบที่สามารถตั้งเวลาให้ทำงานอัตโนมัติได้ ซึ่งในตัวอย่างนี้จะตั้งเวลาให้ฟังก์ชันทำงานชั่วโมงละครั้ง เนื่องจากราคาทองอัพเดททั้งวัน แต่ก็ไม่ได้อัพเดทถี่ระดับนาที
exports.gold = functions.pubsub.schedule("0 */1 * * *").timeZone("Asia/Bangkok").onRun(async context => {
// ...
});
ขั้นตอนนี้เราจะไปดึงข้อมูลราคาทองจาก สมาคมค้าทองคำ ด้วยเทคนิค Web Scraping(การแกะข้อมูลจาก HTML DOM)
gold
ให้ใช้ axios
ดูด HTML ของเว็บ สมาคมค้าทองคำ ออกมาconst response = await axios.get("https://goldtraders.or.th");
const html = response.data;
cheerio
ในการแปลง HTML ทั้งหมดมาเป็น DOM Modelconst $ = cheerio.load(html);
ลำดับถัดไปเราจะสร้างตัว selector กัน โดยให้เราเปิดหน้าเว็บ สมาคมค้าทองคำ ในเบราว์เซอร์ แล้วมองหาส่วนของข้อมูลที่ต้องการก่อน ซึ่งตัวอย่างนี้เราต้องการราคา ซื้อ-ขาย ในกรอบสีแดงตามรูปด้านล่าง
จากนั้นให้คลิกขวาแล้ว Inspect ตำแหน่งที่วงสีแดงเอาไว้ แล้วหา identity ที่เป็นเหมือน parent ของส่วนข้อมูลที่เราสนใจให้เจอ และหา element ชั้นสุดท้ายที่ครอบราคา ซื้อ-ขาย ทั้ง 4 ไว้
index.js
ในบรรทัดล่างสุดของฟังก์ชัน gold
ให้กำหนด selector ในฟังก์ชัน แบบนี้const selector = $("#DetailPlace_uc_goldprices1_GoldPricesUpdatePanel font[color]");
gold
ให้เราจะเพิ่มเงื่อนไขว่าข้อมูลราคา ซื้อ-ขาย นั้นยังสามารถดึงได้นะ หากไม่ได้เราจะให้ฟังก์ชันนี้หยุดทำงานทันทีif (selector.length !== 4) {
return null;
}
gold
ก็มาสกัดเอาราคา ซื้อ-ขาย ทั้ง 4 ออกมา จากนั้นก็เอามาต่อกันและคั่นด้วย pipelet 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 ดังรูป
index.js
ให้ import ตัว dependency ชื่อ firebase-admin
เข้ามาที่จุดบนสุดของไฟล์ และให้ initial มันให้เรียบร้อยconst admin = require("firebase-admin");
admin.initializeApp();
gold
ให้ดึงข้อมูลราคาทองคำล่าสุดใน database ออกมา(priceLast
) แล้วสร้างเงื่อนไขว่าหากยังไม่มีข้อมูล หรือ ข้อมูลไม่ตรงกับข้อมูลล่าสุด(priceCurrent
) ก็ให้อัพเดทข้อมูลล่าสุดลงไปใน databaselet 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
ที่จุดล่างสุดของไฟล์ 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"
}
]
}
]
}
]
}
}
}]
})
})
}
gold
แล้วให้เรียกใช้งานฟังก์ชัน broadcast()
หลังจากที่อัพเดท database แล้วif (!priceLast.exists || priceLast.data().price !== priceCurrent) {
await admin.firestore().doc('line/gold').set({ price: priceCurrent });
broadcast(priceCurrent);
}
firebase deploy --only functions
หาก deploy สำเร็จ เราจะเห็นฟังก์ชันของเราแสดงอยู่ในเมนู Functions ใน Firebase console
และผลลัพธ์ที่ได้ก็จะมีหน้าตาประมาณนี้
ยินดีด้วยครับ ถึงตรงนี้คุณก็มี LINE Chatbot สำหรับการรายงานราคาทองคำเป็นของคุณเองแล้ว!!!