LINE Messaging APIを使って、レンタサイクルの貸出可能数を得るBOT作って見ました。
お盆休みを利用して、LINE BOTで遊んで見ました。 最寄り駅から自宅間でレンタサイクルを利用しているのですが、駅のポートに着いて貸出可能な自転車がなく歩いて帰ることがあります。 貸出可能数はサイト上で確認できるのでそれを見ればいいのですが、LINEで確認、通知できたら若干楽かなと思ったのと、pythonを7月中旬から勉強し始めて何か作って見たかったので、今回挑戦してみました。
作成物
レンタサイクルの貸出可能数取得LINE BOT
作成までの流れ
- LINE Developersでアカウントとチャネルの作成 2.Herokuに新規作成して、LINEチャネルのWebHook URLとHerokuを連係 3.pythonでコーディングしてデプロイ
基本的に公式のチュートリアルに沿ってやっていけばできました。 developers.line.biz
メイン部分のコード
main.py
from flask import Flask, request, abort from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import( InvalidSignatureError ) from linebot.models import ( FollowEvent, MessageEvent, TextMessage, TextSendMessage, ImageMessage, ImageSendMessage, TemplateSendMessage, ButtonsTemplate, PostbackTemplateAction, MessageTemplateAction, URITemplateAction ) import os import scrape as sc app = Flask(__name__) LINE_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"] LINE_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"] line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN) handler = WebhookHandler(LINE_CHANNEL_SECRET) @app.route("/callback", methods=['POST']) def callback(): signature = request.headers['X-Line-Signature'] body = request.get_data(as_text=True) app.logger.info("Request body: " + 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): word = event.message.text result = sc.getPortLeft(word) line_bot_api.reply_message( event.reply_token, TextSendMessage(text=result) ) if __name__ == '__main__': port = int(os.getenv("PORT")) app.run(host="0.0.0.0", port=port, debug=True)
サンプルコードとほぼ同じです。 サンプルではおうむ返ししていた部分をメッセージをさいたま市のポート情報から取得するように変更。
scrape.py
from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By import chromedriver_binary portDict = { '大宮西口' : '#LinkBtn1', '与野本町' : '#LinkBtn2', '土呂' : '#LinkBtn3', '三橋総合公園' : '#LinkBtn4', '大宮東口' : '#LinkBtn5', '与野' : '#LinkBtn6', '北区役所' : '#LinkBtn7', '大宮盆栽美術館' : '#LinkBtn8', 'NACK5スタジアム' : '#LinkBtn9', '旧大宮図書館' : '#LinkBtn10', '合併記念見沼公園' : '#LinkBtn11', '日進' : '#LinkBtn12', '与野公園' : '#LinkBtn13', 'イオン大宮店' : '#LinkBtn14', '北大宮' : '#LinkBtn15', '桜木駐車場' : '#LinkBtn16', '鉄道博物館' : '#LinkBtn17', 'さいたま新都心東口' : '#LinkBtn18', 'ミニストップ鉄道博物館前店' : '#LinkBtn19', '北与野' : '#LinkBtn20', '加茂宮' : '#LinkBtn21', '東宮原' : '#LinkBtn22', '今羽' : '#LinkBtn23', '吉野原' : '#LinkBtn24', } def getPortLeft(word): options = Options() options.add_argument('--headless') driver = webdriver.Chrome(options=options) driver.implicitly_wait(3) driver.get('https://saitama-cycle.jp/port/user_portinfo.aspx?WINTYPE=%27SUB%27') edited_word = word.replace('駅', '') linkbtn = getlinkbtn(edited_word) if linkbtn is None: return 'ポートが認識できません' button_element = driver.find_element_by_css_selector( linkbtn + ' > a') button_element.click() remaing_element = driver.find_element_by_css_selector('#map_detail > div > div > div:nth-child(1) > div:nth-child(3) > div > div:nth-child(4) > div > div > div > div > div > div > table > tbody > tr > td:nth-child(2) > table > tbody > tr:nth-child(2) > td') return remaing_element.text def getlinkbtn(word): linkbtn = portDict.get(word) return linkbtn
メッセージの取得部分。 さいたま市のポート状況ページがjavascriptで描画されるページなので、seleniumを使って持ってきています。
Herokuでchrome driver使えるのか不安でしたが、ググったらBuildpackを入れることで使用できるようです。 Buildpackで使用されるChromeDriverとpythonでインストールしたchromedriver_binaryのバージョンを合わせるように注意です。githubのReadMeに記載されていました。
ところで、さいたま市のレンタサイクルのページがaspxで実装されているっぽい
こんなところで.netと対面するとは
https://saitama-cycle.jp/port/user_portinfo.aspx?WINTYPE=%27SUB%27
感想
意外と簡単にできました。ただ時々503エラーが起きます...
まぁ個人で使う分には問題ないのでこれでいいです。
本当は貸出残が0になったら向こうから通知して欲しいのですが、pushは有料っぽいとのことだったので、ひとまず保留です。 あとの機能としては、ポート名がある程度あいまいでも近いポートの残数を返すとか時間があれば追加する??
さて次何をしましょう
LINE系の出来ることを掘り下げつつ、並行してデータ分析とかやってみたいですね スポーツ好きなので、サイバメトリクスとか手を出してみたいです。