Kanto transit chatbot với Node.js và mongoDb
Bài đăng này đã không được cập nhật trong 4 năm
Chatbot
là khái niệm không mới nhưng gần đây trở thành hot trend khi kết hợp cùng machine learning
và AI
. Mình sẽ có một bài viết cụ thể hơn về kỹ thuật cũng như những cơ hội mà chatbot
đem lại trong một bài khác. Trong khuôn khổ bài viết này xin phép mỳ ăn liền chatbot đơn giản mà mình mới làm trong một buổi tối gió và lạnh ở Tokyo.
Lấy cảm hứng từ việc ngày thứ 2 vừa rồi Tokyo có trận tuyết khủng bố nhất 30 năm trở lại đây và chiến thằng của U23 Việt Nam (=))) mình có xây dựng một chatbot
đơn giản để gửi tin nhắn cảnh báo mỗi khi tàu điện trên các line (vùng Kanto) mình quan tâm nếu line có thay đổi trạng thái.
Đây cũng được hi vọng là viên gạch đầu tiên cho một chatbot hoàn chỉnh hơn với nhiều chức năng hỗ trợ cá nhân (auto dự báo thời tiết, viết mail report, xem giá cổ phiếu, bitcoin, rss,...) mà mình (nếu có thời gian và không lười) định sẽ xây dựng.
Ý tưởng
-
Flowchart cho chatbot đơn giản này.
-
Khi người dùng nhập tên line,
chatbot
sẽ lấy thông tin về line đó, gửi trả lại thông tin và từ đó người dùng sẽ subscribe line này, nếu dữ liệu có thay đổichatbot
sẽ gửi thông báo tới messenger của khách hàng qua việc thực hiện 1 cronjob để liên tục check dữ liệu. -
Mình chọn
facebook messenger platform
cùngnode.js
vàmongodb
để mọi thứ trở nên đơn giản hơn =))
Ok, đã xong phần ý tưởng và công cụ, bắt tay vào làm.
Dữ liệu đầu vào
- Đầu tiên chúng ta cần có một nguồn dữ liệu về traffic tàu điện ở vùng Kanto. Mình có thử search traffic API của Tokyo nhưng không tìm được nên mình chọn giải pháp sử dụng dữ liệu từ Yahoo transit. Như vậy cần một html parser để có thể lấy dữ liệu phục vụ cho
chatbot
.
Facebook chatbot
-
Chúng ta sẽ tạo một chatbot sử dụng
Facebook messenger platform
, sử dụng test-drive để mọi thứ trở nên đơn giản. -
Các bạn có thể làm theo đến bước 5.
Facebook
có recommend sử dụnglocaltunnel
, tuy nhiên mình thấylocaltunnel
thiếu ổn định và bị delay khá nhiều nên mình chuyển qua dùng một công cụ khác làngrok
. Các bạn có thể down tại đây, giải nén ra và dùng liền. -
Sau khi chạy server (
node app.js
) chúng ta sẽ chạy lệnh./ngrok http 5000
cổng local 5000 đã có thể kết nối với
facebook
thông quangrok
service. -
Copy link tại giao diện ngrok (https method) và paste vào
facebook
test-drive, đến đây chúng ta đã công đoạn setupchatbot
. -
Bây giờ bắt đầu vọc code của
fb
, dữ liệu đầu vào sẽ là text (message) nên cần sửa hàmreceivedMessage
để trả về kết quả mong muốn. Bỏ qua các text đặc biệt, trường hợp default của vòng lặp switch chính là phần cần tuỳ biến.app.js
default: parseInfo(senderID, subcribeLine, msgFlg) // senderID: fbID của người dùng, subcribeLine: tên line người dùng nhập vào, msgFlg: 0:cronJob, 1: tin nhắn của người dùng.
Input của người dùng được lấy tại
app.js
var messageText = message.text;
và chatbot cần phải tìm ra line chính xác từ đống này
và chỗ này nữa
Yahoo transit
web parser
-
'nodejs html parser' trên
google
vàcheerio
(info) là lựa chọn có vẻ tốt nhất, tiện thể import thêmmongodb
và để lưu dữ liệu vànode-cron
(info) để tạo cron-job chochatbot
.npm install cheerio npm install mongo npm install node-cron npm install cron
app.js
var mongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; var cheerio = require('cheerio'); var CronJob = require('cron').CronJob;
-
Nắng đã có mũ, mưa đã có ô, không biết parse thế nào để ra cái mình cần đã có
selectorgadget
(info).Với extension này mình tìm ra được
'#mdAreaMajorLine td:nth-child(1)'
là css selector của mục cần lấy, inspect thêm 1 lần thì mình đã lấy đầy đủ các thông tin sau:- tên line (lineName) tại children[0].children[0].data
- link info page của line children[0].attribs.href
- tình trạng line (lineStatus) tại $('dt')[0].children[1].data hoặc $('dt')[0].children[2].data
- thông tin line (lineTrouble) tại $('#mdServiceStatus p')[0].children[0].data
-
Bóc tách được thông tin rồi chúng ta sẽ viết hàm
parseInfo
để lấy tất cả thông tin của line theo yêu cầu của người dùng.app.js
function parseInfo(senderID, subcribeLine , msgFlg) { request('https://transit.yahoo.co.jp/traininfo/area/4/', function (error, response, html) { if (!error && response.statusCode == 200) { var found = false; var $ = cheerio.load(html); $('td:nth-child(1)').each(function (idx, elem) { var lineName = elem.children[0].children[0].data; if (lineName.includes(subcribeLine)) { found = true; request(elem.children[0].attribs.href, function (error, response, html) { if (!error && response.statusCode == 200) { var $ = cheerio.load(html); var lineStatus = $('dt')[0].children[1].data || $('dt')[0].children[2].data; var lineTrouble = $('#mdServiceStatus p')[0].children[0].data handleRequest(senderID, lineName, lineStatus, lineTrouble, msgFlg); } }); } }); if (!found){ sendTextMessage(senderID, subcribeLine + "の情報がありません。"); } } }); }
-
Tiện tay viết luôn hàm xử lý việc gửi tin nhắn reply cho người dùng
app.js
function handleRequest(senderID, lineName, lineStatus, lineTrouble, msgFlg){ mongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("chatbot"); var query = { userId: senderID, subcribeLine: lineName }; dbo.collection("rosen").find(query).toArray(function(err, result) { if (err) throw err; if (result[0] && result[0].lineStatus == lineStatus && result[0].lineTrouble == lineTrouble) { // line info did not change console.log("Did not change"); // if user send the msg (nếu là người dùng gửi thì sẽ gửi lại trạng thái dù không thay đổi) if (msgFlg == 1){ var msg = '[' + result[0].subcribeLine.toString() + ']: ' + result[0].lineStatus.toString() + '\n' + result[0].lineTrouble.toString(); sendTextMessage(senderID, msg); } } else if (result[0]) { // line info has been changed var newVal = { $set: { lineStatus: lineStatus, lineTrouble: lineTrouble } }; dbo.collection("rosen").updateOne(query, newVal, function(err, res) { if (err) throw err; console.log(senderID + lineName + " updated"); db.close(); }); var msg = '[' + lineName.toString() + ']: ' + lineStatus.toString() + '\n' + lineTrouble.toString(); sendTextMessage(senderID, msg); } else { // user subcribe to new line var myobj = { userId: senderID, subcribeLine: lineName, lineStatus: lineStatus, lineTrouble: lineTrouble }; dbo.collection("rosen").insertOne(myobj, function(err, res) { if (err) throw err; console.log(senderID + lineName + "inserted"); }); var msg = '[' + lineName.toString() + ']: ' + lineStatus.toString() + '\n' + lineTrouble.toString(); sendTextMessage(senderID, msg); } db.close(); }); }); }
-
Test thử nào
https://gfycat.com/ifr/UnhealthyWeightyIndusriverdolphin
Cron-job
-
Việc cuối cùng chúng ta cần tạo cronjob để
chatbot
của chúng ta (bản chất lànode.js
app) check trạng thái liên tục (mỗi phút một lần) và nếu có thay đổi sẽ gửi thông báo đến cho người dùngapp.js
new CronJob('1 * * * * *', function() { mongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("chatbot"); dbo.collection("rosen").find({}).toArray(function(err, result) { if (err) throw err; result.forEach(function (res) { parseInfo(res.userId, res.subcribeLine, 0); }); db.close(); }); }); }, null, true, 'Asia/Tokyo');
-
Test hoạt động lần cuối
https://gfycat.com/ifr/SmartOrdinaryBluebreastedkookaburra
Vậy là chúng ta đã hoàn thành xong một chatbot với chức năng tự động báo cáo tình trạng các line. Hiện tại thì chức năng của chatbot
giao thông này vẫn còn rất đơn giản và ở mức tạm dùng được và đã được mình deploy tại https://chatbot.qmau.me (update: đã bị dỡ xuống) . Một số chức năng đơn giản như xem và xoá subscribeLine sẽ được mình hoàn thiện nốt trong thời gian tới.
Để lại một like
và comment
để buổi tối của mình không lãng phí nhé. :3
Source code của project có tại đây. Feel free to use!
Ps: Tầm này chỉ muốn ở Hà Nội sang chơi với cháu rồi đi ăn bún đậu cùng đồng bào thôi
Refs
Link bài viết gốc có tại blog cá nhân https://qmau.me/blog/post/kanto-transit-chatbot-voi-yahoo-transit-va-node-js
All rights reserved