讓LINE幫忙提醒PChome口罩可購買狀態 – Heroku篇

利用Heroku的雲端伺服器架設web與python環境,定時關注網站商品是否開始販售或特價,並利用LINE的即時推撥功能隨時通知,讓我們能順利買到特價品


在開始之前要先了解Heroku的免費方案有2個很重要的限制:每月免費dyno(主機)執行時間只有550小時的額度,以及30分鐘內沒有對dyno送出請求訊息的話,就會進入休眠。尤其是第2項的30分鐘限制會讓這篇文的LINE的主動推撥功能失效,所以除非使用購買方案,不然較不建議用免費的Heroku方案執行這篇教學(但還是可以往下看看如何設定Heroku喔)。

觀念篇中(如果沒有看過請務必前往後再回到這篇教學)有提到整個運作的原理跟流程

  1. 用flask建立網站框架並建立與linebot API間的通訊
  2. 收到指令後,使用selenium開啟PChome的網頁,再利用BeautifulSoup爬蟲資料
  3. 比對網頁內容是否有變動,若是有變動即代表商品正在開賣或價格有變動
  4. 利用linebot的 REPLY_MESSAGE 與 PUSH_MESSAGE 在LINE端進行推播提醒

Heroku是一個支援多種程式語言的雲平台及服務,安裝流程都基本上參考觀念篇,主要工程會是在環境的設定。

設定Heroku環境

1.建立專案:如上所述,Heroku是一個支援多種程式語言的雲平台及服務,所以可以安裝許多的套件跟服務,在完成Heroku的註冊後就可以進入到設定介面,點擊右上角的”New”選單,再選擇”Create new app”就可以建立一個專案,這裡我們建立一個名為「dayofflinebot」的專案。

2.新增Buildpacks:完成Buildpacks的設定就會安裝程式在Heroku的環境,因為我們需要chromedriver與 Google Chrome這些程式,因此管理後台的Settings裡,點選”Add bulidpack”按鈕。

在輸入視窗中依序輸入下列的網址並按下”save changes”按鈕:

https://github.com/heroku/heroku-buildpack-chromedriver

https://github.com/heroku/heroku-buildpack-google-chrome.git

完成輸入後就下方的Buildpacks就會出現上面我們輸入的網址,這樣就等於安裝了這兩個套件程式了。

程式碼撰寫

程式的部分基本上還是使用在觀念篇中的完整程式碼,但有做以下的調整:

  • WebDriver的位置可以不用指定
  • 最後一行改為app.run(host='0.0.0.0', port=os.environ['PORT'])
from flask import Flask, request, abort
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import time, os
from linebot import (
	LineBotApi, WebhookHandler
)
from linebot.exceptions import (
	InvalidSignatureError
)
from linebot.models import *


HTMLs=[ 
    ["https://24h.pchome.com.tw/prod/DABCE5-1900AQKMT","ul","class","fieldset_box orignbutton",""],
    ["https://24h.pchome.com.tw/prod/DYAM2M-A900AHXUD","div","id","PaymentContainer",""]
]


app = Flask(__name__)

# Channel Access Token
line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
# Channel Secret
handler = WebhookHandler('YOUR_CHANNEL_SECRET')


chrome_options = Options()
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument("--disable-notifications")
chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"])
chrome_options.add_argument('--disable-extensions')
chrome_options.add_argument('start-maximized')
chrome_options.add_argument('disable-infobars')
chrome_options.add_argument('disable-blink-features=AutomationControlled')
chrome_options.add_argument('user-agent=Type user agent here')
chrome = webdriver.Chrome(options=chrome_options)

doGrap = 0

def pageview(url, view_item, view_ID, view_contain):
	global chrome
	chrome.get(url)
	time.sleep(3)
	
	chrome.execute_script("window.scrollTo(0,document.body.scrollHeight)")
	soup = BeautifulSoup(chrome.page_source, 'html.parser')
	mainBlock = soup.find(view_item,{view_ID: view_contain})
	return mainBlock

@app.route("/callback", methods=['POST'])
def callback():
	# get X-Line-Signature header value
	signature = request.headers['X-Line-Signature']
	# get request body as text
	body = request.get_data(as_text=True)
	app.logger.info("Request body: " + body)
	
	# handle webhook body
	try:
		handler.handle(body, signature)
	except InvalidSignatureError:
		print("Invalid signature. Please check your channel access token/channel secret.")
		abort(400)        
	return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
	global doGrap
	msg = event.message.text
	if msg == "Go":		
		doGrap = 1
		line_bot_api.reply_message(event.reply_token, TextSendMessage(text='start grapping!\n(with '+str(len(HTMLs))+' websites...)'))
		while doGrap:
			for thePage in HTMLs:
				print(thePage[0])
				hpage = pageview(thePage[0], thePage[1], thePage[2], thePage[3])
				if thePage[4]!='' and hpage != thePage[4]:
					line_bot_api.push_message('YOUR_USER_ID', TextSendMessage(text='Page changed!\n'+str(thePage[0])))
					print("change!!!!!")
				thePage[4] = hpage
				time.sleep(1)
			time.sleep(30)
	elif msg == "Stop":
		line_bot_api.reply_message(event.reply_token, TextSendMessage(text='stop!'))
		print("stop!")
		doGrap = 0		

if __name__ == "__main__":
	app.run(host='0.0.0.0', port=os.environ['PORT'])

我們將上面的程式碼存成app.py,並存放在D:\My Works的資料夾內。

登入與操作Heroku

接下來我們對於Heroku的操作、環境設定與佈署等方式會使用 Heroku Git 配合CLI的方式,整個流程會有3個步驟:安裝Heroku CLI建立容器(repository)佈署(deploy)檔案:

1.安裝Heroku CLI:這裡會需要安裝2個程式,分別是 The Heroku CLI 以及 Git 。安裝完成後開啟”命令提示字元”,並輸入指令:

heroku login

會出現登入訊息並按下任何一鍵後會開啟瀏覽器進入Heroku的 login頁面。

完成login後會出現

Logging in… done
Logged in as XXXXXX@XXXXX.XXXX

的資訊便可以關掉瀏覽器。

2.建立容器:每一個新專案這個步驟只要做一次,目的是初始化這個新專案。一樣在剛剛的”命令提示字元視窗”下,利用CMD指令將移到D:\My Works並輸入以下指令:

git init

heroku git:remote -a dayofflinebot

這樣就完成本機端的資料夾文件跟Heroku端間的遠端檔案鏈結。

如果沒有使用過git,要先設定名字、信箱,指令如下:

git config –global user.name “你的名字”

git config –global user.email “你的信箱”

ref:g919233的小屋

3.佈署(上傳)檔案:要上傳的檔案除了py檔外,另外還有兩個重要的檔案需要另外新增,分別是:

  • requirements.txt: 裡面列出要安裝的python模組
  • Procfile : 啟動web服務

這兩個檔案內容分別如下:

# requirements.txt
flask
line-bot-sdk
selenium==3.141.0
freeze
beautifulsoup4
# Procfile
web: python app.py

這裡會用到3種的指令,以下分別大意解說:

  • git add : 打包D:\My Works內的檔案,只要檔案有修改,就需要執行這個指令
  • git commit : 註解說明這次的上傳目的
  • git push : 上傳到Heroku

根據上述的指令原則,我們就可以在”命令提示字元視窗”下輸入以下指令:

git add .

git commit -am "make it better"

git push heroku master

約會執行1分鐘,完成後會出現remote: Verifying deploy… done. 這個訊息,到這個步驟就算是大功告成,剩下就是到LINE Developers網頁,在Messaging API中的Webhook settings裡的Webhook URL 改為:

https://XXXXXXXX.herokuapp.com/callback

XXXXX是填入專案名稱就可以串接到Heroku。

最後Heroku的運作訊息或錯誤可以在”View logs”裡可以看到,方便我們除錯與確認。

ref: Michael Browne, 卡米狗系列 第 18 篇, 資料工程師的日常, 賴田捕手系列 第 10 篇,