iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 23
1

今天要來仔細一點的介紹 Line Messaging API 的程式部分,昨天只講到讓 Linebot 重複使用者說的話,這一丁點用也沒有,所以今天來做一些好玩的,我們做出一個可以詢問天氣的對話機器人,問他天氣他會回應氣祥局的資訊。


氣象局 API 註冊

因為我們要有氣象資料,但總不可能自己去設氣象站吧!?所以我們要存取中央氣象局的自動氣象站資訊,那該怎麼用呢?氣象局有開放資料 API,從那邊拿就好啦~ 但是這個服務需要註冊,所以先到 https://opendata.cwb.gov.tw/index 註冊一個帳號,不用擔心,這個服務不用錢呦~

訪問 API 你會看到一大串 JSON 格式的東西,這就是我們要的資料!

取得天氣資料

我們先建立一個 GetWeather(station) 函式,傳入值為氣象站的名稱,回傳值是該氣象站的天氣資訊,依照 API 的格式宣告一個合併 station 變數的字串變數 end_point,利用 requests 向 API 主機請求特定氣象站的天氣資訊,並透過 requests 內含的 json() 解析方法解析格式字串透過 for 迴圈依次比對,若有符合使用者想要的氣象站資訊則回傳,若無則回應 "not found"

def GetWeather(station):
    end_point = "https://opendata.cwb.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=rdec-key-123-45678-011121314"

    data = requests.get(end_point).json()
    data = data["records"]["location"]

    target_station = "not found"
    for item in data:
        if item["locationName"] == str(station):
            target_station = item
    return target_station

訊息生成

透過 GetWeather() 取得特定地點的天氣資訊後,我們必須把他轉成使用者容易看的資訊,如果直接回應 JSON 字串我想大概會被檢舉(? 所以我們先呼叫 GetWeather(),再檢查資料是否為"not found",若是,則代表使用者輸入不正確的測站,所以函式回應 False。

若成功取讀資料了,我們一層一層的取出我們想要的數據,第二段程式碼是在取得溫度及濕度資料,並且以大家比較容易看懂的方式製作訊息,最後呼叫 MakeAQI(station),呼叫 AQI 資料 API 並格式化,這個函式等等會說明,最後回傳訊息字串。

def MakeWeather(station):
    WeatherData = GetWeather(station)
    if WeatherData == "not found":
        return False

    WeatherData = WeatherData["weatherElement"]
    msg = "豆芽天氣報告 - " + station
    msg += "\n\n氣溫 = " + WeatherData[3]["elementValue"] + "℃\n"
    msg += "濕度 = " + \
        str(float(WeatherData[4]["elementValue"]) * 100) + "% RH\n"

    msg += MakeAQI(station)
    return msg

取得並生成 AQI 資訊

這邊我用了環保署的 API,取得特定測站的空氣品質資料,想必現在很多人都很在乎每天的天氣品質吧,這裡的做法有點像是 GetWeather() 和 MakeWeather() 的合體,透過 requests 取得 AQI 資料並檢查 HTTP code,若API伺服器回應 500 Server Error 則回應給使用者無 AQI 資料,畢竟使用者大多不懂 HTTP code,若成功則開始解析 JSON,並生成訊息格式,最後回傳訊息字串。

def MakeAQI(station):
    end_point = "http://opendata.epa.gov.tw/webapi/api/rest/datastore/355000000I-000259?filters=SiteName eq '" + \
        station + "'&sort=SiteName&offset=0&limit=1000"

    data = requests.get(end_point)
    AQImsg = ""

    if data.status_code == 500:
        return "無 AQI 資料"
    else:
        AQIdata = data.json()["result"]["records"][0]
        AQImsg += "AQI = " + AQIdata["AQI"] + "\n"
        AQImsg += "PM2.5 = " + AQIdata["PM2.5"] + " μg/m3\n"
        AQImsg += "PM10 = " + AQIdata["PM10"] + " μg/m3\n"
        AQImsg += "空品:" + AQIdata["Status"]
        return AQImsg

整合程式碼

最後整合進主程式,用 event.message.text.split(" ") 以空格分開使用者傳送的訊息,很粗略的用 if 判斷式解析語句,其他就和昨天的程式碼相似,程式碼我也不私藏啦~如果看不懂的話複製貼上也是可以,記得 Token 和 Secret 要改成你自己的呦!

import os
from flask import Flask, request, abort
import requests
import json

#LINE bot 必要套件
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,ImageMessage,ImageSendMessage
)

app = Flask(__name__)

# Channel Access Token
line_bot_api = LineBotApi("YOUR_TOKEN")

# Channel Secret
handler = WebhookHandler("YOUR_Secret")


def MakeAQI(station):
    end_point = "http://opendata.epa.gov.tw/webapi/api/rest/datastore/355000000I-000259?filters=SiteName eq '" + \
        station + "'&sort=SiteName&offset=0&limit=1000"

    data = requests.get(end_point)
    AQImsg = ""

    if data.status_code == 500:
        return "無 AQI 資料"
    else:
        AQIdata = data.json()["result"]["records"][0]
        AQImsg += "AQI = " + AQIdata["AQI"] + "\n"
        AQImsg += "PM2.5 = " + AQIdata["PM2.5"] + " μg/m3\n"
        AQImsg += "PM10 = " + AQIdata["PM10"] + " μg/m3\n"
        AQImsg += "空品:" + AQIdata["Status"]
        return AQImsg


def GetWeather(station):
    end_point = "https://opendata.cwb.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=rdec-key-123-45678-011121314"

    data = requests.get(end_point).json()
    data = data["records"]["location"]

    target_station = "not found"
    for item in data:
        if item["locationName"] == str(station):
            target_station = item
    return target_station


def MakeWeather(station):
    WeatherData = GetWeather(station)
    if WeatherData == "not found":
        return False

    WeatherData = WeatherData["weatherElement"]
    msg = "豆芽天氣報告 - " + station
    msg += "\n\n氣溫 = " + WeatherData[3]["elementValue"] + "℃\n"
    msg += "濕度 = " + \
        str(float(WeatherData[4]["elementValue"]) * 100) + "% RH\n"

    msg += MakeAQI(station)
    return msg


def MakeRailFall(station):
    result = requests.get(
        "https://opendata.cwb.gov.tw/api/v1/rest/datastore/O-A0002-001?Authorization=rdec-key-123-45678-011121314")
    msg = "豆芽降雨報告 - " + station + "\n\n"

    if(result.status_code != 200):
        return "雨量資料讀取失敗"
    else:
        railFallData = result.json()
        for item in railFallData["records"]["location"]:
            if station in item["locationName"]:
                msg += "目前雨量:" + \
                    item["weatherElement"][7]["elementValue"] + "mm\n"
                if item["weatherElement"][3]["elementValue"] == "-998.00":
                    msg += "三小時雨量:0.00mm\n"
                else:
                    msg += "三小時雨量:" + \
                        item["weatherElement"][3]["elementValue"] + "mm\n"
                msg += "日雨量:" + \
                    item["weatherElement"][6]["elementValue"] + "mm\n"
                return msg
        return "沒有這個測站啦"

@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:
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    cmd = event.message.text.split(" ")

    if cmd[0] == "天氣":
        station = cmd[1]
        WeatherMsg = MakeWeather(station)

        if not WeatherMsg:
            WeatherMsg = "沒這個氣象站啦"

        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=WeatherMsg))
    if cmd[0] == "雨量":
        station = cmd[1]
        RailFallMsg = MakeRailFall(station)

        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=RailFallMsg))


if __name__ == "__main__":
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)

部屬到 Heroku

寫好後依照昨天的方法,部屬到 heroku 上,如果忘記沒關係,我再打一次,先開啟 cmd,到專案目錄下輸入以下指令

git add .
git commit -am "Add weather feature."
git push heroku master

然後去試試看,發送天氣 鳳山給機器人,看看他有沒有成功回應啊~

範本

我自己也有寫一個 Line 對話機器人,裡面也有天氣或雨量等等的功能,可以給大家參考參考
加好友 QR code:
https://ithelp.ithome.com.tw/upload/images/20191009/20120282bQ4okD1HTx.png

呈現結果:
https://ithelp.ithome.com.tw/upload/images/20191009/20120282Lm4zQrp1Tt.jpg


我覺得因為在台灣很多人用 Line,幾乎大家都有,所以透過 Linebot 來建立服務十分方便,使用者不須額外安裝程式,也可以達到應用程式的效果,加上對答的模式符合現在服務的潮流,這一部分很是值得研究研究,而且 Line 公司近日在開發者社群這一塊挺用心的,API/Documents 等等都準備得很周全,甚至有開發者聚會,資源相對來說是很足夠的!


參考資料
https://developers.line.biz/en/reference/messaging-api/


上一篇
Day22-Python Line 整合應用2 -- Line 對話機器人II
下一篇
Day24-Python 影像處理套件 PIL
系列文
原來電腦可以這樣用!? 果蠅也懂的程式語言教學30

尚未有邦友留言

立即登入留言