Line bot範例

大家如果有仔細觀察,可以發現當使用者對店家的LINE Bot發出訊息時,有些除了回應文字訊息外,還會回應貼圖、影片或樣板訊息等,來提高使用者體驗,這些就是LINE Messaging API所提供的訊息型態(Message types),讓開發人員可以依據需求,來客製化回應的訊息。

按鈕樣板訊息(Buttons template message)就是一個樣板類型的訊息,其中可以包含圖片、標題、文字及多顆按鈕,讓使用者可以進行點選,如下圖:

取自LINE Developers Documents

為了想要讓LINE Bot的回覆訊息擁有這樣的效果,所以開啟Django應用程式(foodlinebot)下的views.py檔案,如下圖:

callback(檢視函式)中,假設當使用者輸入「哈囉」時,想要LINE Bot回覆選擇地區的按鈕樣板訊息(Buttons template message),就需引用TemplateSendMessage、ButtonsTemplateMessageTemplateAction,如下範例第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

開啟愛食記網站,選擇搜尋地點後,可以看到網址結構為:

接著,選擇美食分類,可以看到網址結構為:

從上圖可以知道,餐廳分類就是接在網址結構中的list後面,所以,在scraper.py檔案的IFoodie類別中,在網址的地方修改為如下範例第5

# 愛食記爬蟲 class IFoodie(Food): def scrape(self): response = requests.get( "//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有興趣的朋友。

如果您喜歡我的文章,請幫我按五下Like(使用GoogleFacebook帳號免費註冊),支持我創作教學文章,回饋由LikeCoin基金會出資,完全不會花到錢,感謝大家。

Toplist

最新的帖子

標籤