在LINE通訊軟體普遍的使用下,許多店家紛紛透過建立LINE Bot,來自動和使用者互動,瞭解消費的需求,進而推播相關的優惠訊息。
大家如果有仔細觀察,可以發現當使用者對店家的LINE Bot發出訊息時,有些除了回應文字訊息外,還會回應貼圖、影片或樣板訊息等,來提高使用者體驗,這些就是LINE Messaging API所提供的訊息型態(Message types),讓開發人員可以依據需求,來客製化回應的訊息。
所以本文延續[Python+LINE Bot教學]建構具網頁爬蟲功能的LINE Bot機器人文章,將原本的文字回應訊息,調整為按鈕樣板訊息(Buttons template message),在對談互動的過程中,讓使用者透過按鈕選擇的方式,提供所在的地區及想要吃的美食分類,接著,在LINE Bot中利用Python網頁爬蟲取得符合需求的前五個最高人氣且營業中的餐廳。
2020/11/01補充說明
由於電腦板LINE尚未支援按鈕樣板訊息(Buttons template message),所以要測試本文的實作結果需使用手機板LINE唷。
本文的重點包含:
- LINE Bot按鈕樣板訊息(Buttons template message)
- LINE Bot按鈕樣板訊息(Buttons template message)回傳值
- LINE Bot重構Python網頁爬蟲
一、LINE Bot按鈕樣板訊息(Buttons template message)
按鈕樣板訊息(Buttons template message)就是一個樣板類型的訊息,其中可以包含圖片、標題、文字及多顆按鈕,讓使用者可以進行點選,如下圖:
取自LINE Developers Documents
為了想要讓LINE Bot的回覆訊息擁有這樣的效果,所以開啟Django應用程式(foodlinebot)下的views.py檔案,如下圖:
在callback(檢視函式)中,假設當使用者輸入「哈囉」時,想要LINE Bot回覆選擇地區的按鈕樣板訊息(Buttons template message),就需引用TemplateSendMessage、ButtonsTemplate及MessageTemplateAction,如下範例第11~13行:
from django.shortcuts import render from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from django.conf import settings from linebot import LineBotApi, WebhookParser from linebot.exceptions import InvalidSignatureError, LineBotApiError from linebot.models import ( MessageEvent, TextSendMessage, TemplateSendMessage, ButtonsTemplate, MessageTemplateAction ) from .scraper import IFoodie line_bot_api = LineBotApi(settings.LINE_CHANNEL_ACCESS_TOKEN) parser = WebhookParser(settings.LINE_CHANNEL_SECRET) @csrf_exempt def callback(request): if request.method == 'POST': signature = request.META['HTTP_X_LINE_SIGNATURE'] body = request.body.decode('utf-8') try: events = parser.parse(body, signature) # 傳入的事件 except InvalidSignatureError: return HttpResponseForbidden() except LineBotApiError: return HttpResponseBadRequest() for event in events: if isinstance(event, MessageEvent): # 如果有訊息事件 if event.message.text == "哈囉": line_bot_api.reply_message( # 回復傳入的訊息文字 event.reply_token, TemplateSendMessage( alt_text='Buttons template', template=ButtonsTemplate( title='Menu', text='請選擇地區', actions=[ MessageTemplateAction( label='台北市', text='台北市' ), MessageTemplateAction( label='台中市', text='台中市' ), MessageTemplateAction( label='高雄市', text='高雄市' ) ] ) ) ) else: food = IFoodie(event.message.text) line_bot_api.reply_message( # 回應前五間最高人氣且營業中的餐廳訊息文字 event.reply_token, TextSendMessage(text=food.scrape()) ) return HttpResponse() else: return HttpResponseBadRequest()
執行結果
範例中第39行,LINE Bot判斷使用者發送「哈囉」訊息時,在replay_message(回覆訊息)的API中,使用TemplateSendMessage(樣板傳送訊息),並且指定為Buttons Template(按鈕樣板),如第45行。其中就可以自訂標題、文字及按鈕。
當使用者選擇地區後,第39行判斷使用者發送的訊息不是「哈囉」,所以就會執行第66行將地區傳入Python網頁爬蟲中取得資料。
這時候,如果想要再增加一個步驟,當使用者選擇地區後,LINE Bot能夠接著回覆按鈕樣板訊息(Buttons template message),讓使用者選擇想吃的美食分類,像是火鍋、早午餐或約會餐廳等,完成後再呼叫Python網頁爬蟲進行資料的取得,該怎麼做呢?
想必大家最直覺的做法是,再增加一個if判斷式吧?這樣雖然能夠解決問題,但是使用者第二次選擇餐廳分類時,LINE Bot所收到的訊息將會是餐廳分類,那該如何知道使用者在第一次選擇地區時,是選擇什麼?
二、LINE Bot按鈕樣板訊息(Buttons template message)回傳值
要解決這樣的問題,就需要在使用者進行選擇前,在按鈕樣板訊息(Buttons template message)的每個選項背後夾帶自訂的資料,當使用者選擇後,就能夠將其中夾帶的資料發送給LINE Bot。
舉例來說,在使用者發送「哈囉」訊息給LINE Bot時,為了要標記接下來回覆的選擇地區按鈕樣板訊息(Buttons template message)為第一步驟,就能夠引用「PostbackEvent」及「PostbackTemplateAction」,如下範例第14~15行,在每個選項中增加一個回傳值(data),其中夾帶自訂的資料A(代表第一步驟)以及該選項的資料,如下範例第53~67行:
from django.shortcuts import render from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from django.conf import settings from linebot import LineBotApi, WebhookParser from linebot.exceptions import InvalidSignatureError, LineBotApiError from linebot.models import ( MessageEvent, TextSendMessage, TemplateSendMessage, ButtonsTemplate, MessageTemplateAction, PostbackEvent, PostbackTemplateAction ) from .scraper import IFoodie line_bot_api = LineBotApi(settings.LINE_CHANNEL_ACCESS_TOKEN) parser = WebhookParser(settings.LINE_CHANNEL_SECRET) @csrf_exempt def callback(request): if request.method == 'POST': signature = request.META['HTTP_X_LINE_SIGNATURE'] body = request.body.decode('utf-8') try: events = parser.parse(body, signature) # 傳入的事件 except InvalidSignatureError: return HttpResponseForbidden() except LineBotApiError: return HttpResponseBadRequest() for event in events: if isinstance(event, MessageEvent): # 如果有訊息事件 if event.message.text == '哈囉': line_bot_api.reply_message( # 回復「選擇地區」按鈕樣板訊息 event.reply_token, TemplateSendMessage( alt_text='Buttons template', template=ButtonsTemplate( title='Menu', text='請選擇地區', actions=[ PostbackTemplateAction( label='台北市', text='台北市', data='A&台北市' ), PostbackTemplateAction( label='台中市', text='台中市', data='A&台中市' ), PostbackTemplateAction( label='高雄市', text='高雄市', data='A&高雄市' ) ] ) ) ) else: food = IFoodie(event.message.text) line_bot_api.reply_message( # 回復傳入的訊息文字 event.reply_token, TextSendMessage(text=food.scrape()) ) return HttpResponse() else: return HttpResponseBadRequest()
這時候,當使用者選擇地區後,LINE Bot就能夠收到傳值(data)中的資料,讓開發人員可以更有彈性的應用,其中一個就是可以再新增第二個步驟-「選擇美食分類」,而在其中的每個選項夾帶第一步驟中使用者所選擇的地區資料,如下範例:
from django.shortcuts import render from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from django.conf import settings from linebot import LineBotApi, WebhookParser from linebot.exceptions import InvalidSignatureError, LineBotApiError from linebot.models import ( MessageEvent, TextSendMessage, TemplateSendMessage, ButtonsTemplate, MessageTemplateAction, PostbackEvent, PostbackTemplateAction ) from .scraper import IFoodie line_bot_api = LineBotApi(settings.LINE_CHANNEL_ACCESS_TOKEN) parser = WebhookParser(settings.LINE_CHANNEL_SECRET) @csrf_exempt def callback(request): if request.method == 'POST': signature = request.META['HTTP_X_LINE_SIGNATURE'] body = request.body.decode('utf-8') try: events = parser.parse(body, signature) # 傳入的事件 except InvalidSignatureError: return HttpResponseForbidden() except LineBotApiError: return HttpResponseBadRequest() for event in events: if isinstance(event, MessageEvent): # 如果有訊息事件 if event.message.text == "哈囉": line_bot_api.reply_message( # 回復傳入的訊息文字 event.reply_token, TemplateSendMessage( alt_text='Buttons template', template=ButtonsTemplate( title='Menu', text='請選擇地區', actions=[ PostbackTemplateAction( label='台北市', text='台北市', data='A&台北市' ), PostbackTemplateAction( label='台中市', text='台中市', data='A&台中市' ), PostbackTemplateAction( label='高雄市', text='高雄市', data='A&高雄市' ) ] ) ) ) elif isinstance(event, PostbackEvent): # 如果有回傳值事件 if event.postback.data[0:1] == "A": # 如果回傳值為「選擇地區」 area = event.postback.data[2:] # 透過切割字串取得地區文字 line_bot_api.reply_message( # 回復「選擇美食類別」按鈕樣板訊息 event.reply_token, TemplateSendMessage( alt_text='Buttons template', template=ButtonsTemplate( title='Menu', text='請選擇美食類別', actions=[ PostbackTemplateAction( # 將第一步驟選擇的地區,包含在第二步驟的資料中 label='火鍋', text='火鍋', data='B&' + area + '&火鍋' ), PostbackTemplateAction( label='早午餐', text='早午餐', data='B&' + area + '&早午餐' ), PostbackTemplateAction( label='約會餐廳', text='約會餐廳', data='B&' + area + '&約會餐廳' ) ] ) ) ) elif event.postback.data[0:1] == "B": # 如果回傳值為「選擇美食類別」 result = event.postback.data[2:].split('&') # 回傳值的字串切割 food = IFoodie( result[0], # 地區 result[1] # 美食類別 ) line_bot_api.reply_message( # 回復訊息文字 event.reply_token, # 爬取該地區正在營業,且符合所選擇的美食類別的前五大最高人氣餐廳 TextSendMessage(text=food.scrape()) ) return HttpResponse() else: return HttpResponseBadRequest()
一般在傳送文字訊息時,都是MessageEvent(訊息事件),如第16行,而如果有回傳值(data),就會是PostbackEvent(回傳值事件),如第48行。
所以第一次使用者發送「哈囉」訊息時,沒有回傳值,第39行判斷為MessageEvent,回覆選擇地區的按鈕樣板訊息(Buttons template message),第二次當使用者選擇後,由於選項中有回傳值(data),因此第70行判斷PostbackEvent就會成立。
接著,利用Python的字串切割,第72行判斷為A(第一步驟),所以將回傳值(data)中使用者選擇的地區附加到B(第二步驟)的回傳值(data)中,並且回覆選擇餐廳分類的鈕樣板訊息(Buttons template message)。
使用者在選擇餐廳分類後,同樣為PostbackEvent,所以LINE Bot就可以從回傳值(data)中取得使用者在第一及第二步驟所選擇的資料了。
三、LINE Bot重構Python網頁爬蟲
由於我們增加了一個美食分類,所以在scraper.py網頁爬蟲檔案中,Food抽象類別的建構式,需增加category屬性,如下範例:
# 美食抽象類別 class Food(ABC): def __init__(self, area, category): self.area = area # 地區 self.category = category # 美食類別 @abstractmethod def scrape(self): pass
開啟愛食記網站,選擇搜尋地點後,可以看到網址結構為:
# 愛食記爬蟲 class IFoodie(Food): def scrape(self): response = requests.get( "https://ifoodie.tw/explore/" + self.area + "/list/" + self.category + "?sortby=popular&opening=true") soup = BeautifulSoup(response.content, "html.parser") # 爬取前五筆餐廳卡片資料 cards = soup.find_all( 'div', {'class': 'jsx-1776651079 restaurant-info'}, limit=5) content = "" for card in cards: title = card.find( # 餐廳名稱 "a", {"class": "jsx-1776651079 title-text"}).getText() stars = card.find( # 餐廳評價 "div", {"class": "jsx-1207467136 text"}).getText() address = card.find( # 餐廳地址 "div", {"class": "jsx-1776651079 address-row"}).getText() content += f"{title} \n{stars}顆星 \n{address} \n\n" return content
最後,就可以執行LINE Bot和它對話了,如下範例:
四、小結
以上就是延續[Python+LINE Bot教學]建構具網頁爬蟲功能的LINE Bot機器人文章,使用鈕樣板訊息(Buttons template message)來提升使用者的互動體驗,大家可以依循這樣的邏輯,練習再增加一個步驟,讓使用者選擇平均消費價格,再利用Python網頁爬蟲取得餐廳資料。希望本文有幫助到您,歡迎分享給身邊對LINE Bot有興趣的朋友。
有想要看的教學內容嗎?歡迎利用以下的Google表單讓我知道,將有機會成為教學文章,分享給大家😊
版大 我作到 二、LINE Bot按鈕樣板訊息 的 前面
回覆刪除第一次測式"哈囉"
就出現
Buttons template
請至智慧手機上確認訊息內容。
直接復制版大的程式碼也一樣,想請問以下這三個直接引入就能用嗎?
需引用TemplateSendMessage、ButtonsTemplate及MessageTemplateAction
有要打指令安裝嗎?
您好:
刪除由於電腦板LINE尚未支援按鈕樣板訊息(Buttons template message),所以要測試本文的實作結果需使用手機板LINE唷。已經在文章中補充說明,謝謝您 :)