LINE Notify + Pythonで天気情報を取得する方法

#はじめに

本投稿で取得している天気情報は[livedoor天気情報](http://weather.livedoor.com/)から取得してますが、
livedoor天気情報は2020/7/31を持ってサービス終了することになったため、取得先をYahoo!天気に変更したエントリをこちらに作成しました。

基本的な考え方は一緒ですが、ソースコードに変更が発生しているため、変更後のソースコードは下記エントリを参照していただくようお願いします。。

[【Yahoo!天気リプレース版】LINE Notify + Pythonで天気情報を取得する方法](https://qiita.com/S_eki/items/259f5c1c2238810d6496)

#きっかけ
天気情報をLINEに送る仕組みがあれば便利だなと思っていたら
LINE Notifyというサービスがあることを知り、早速調べてみました。

#LINE Notifyでアクセストークンの取得する。
まずは、LINE Notifyにアクセスし、自分のLINEアカウントにログインした上で通知を行いたいチャットに対してアクセストークンの発行を行う必要があるとのこと。早速やってみました。

アクセストークンの取得手順は下記エントリが非常に分かりやすかったのでここでは割愛。

■PythonでLINEに通知を送る
https://qiita.com/hiro-abe/items/e42f857bd6b40bc178a3

#取得する天気情報データ

天気情報は、livedoorの天気予報のRSSから取得。
http://weather.livedoor.com/

例えば、水戸の天気予報を取得する手順を記す。

１．水戸市の天気を開き、RSSのボタンを押下する。
　　http://weather.livedoor.com/area/forecast/080010

<img width="663" alt="スクリーンショット 2018-01-14 23.07.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/197189/cebb5289-dd00-d462-e6bc-5dc40d139663.png">

２．下記のようなXMLファイルが開かれる。今回取得する天気情報はずばりコレです。

<img width="1266" alt="スクリーンショット 2018-01-14 23.12.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/197189/921d9ee9-0f64-bb1a-8a5d-ce3478f4223e.png">

#通知機能の実装

天気情報を取得し、LINEに通知する仕組みを実装する。

上記の天気情報は、Pythonのライブラリの１つである
BeautifulSoupを用いて取得可能である。

未だインストールしていない方は、pipで所得。

```
$ pip install beautifulsoup4
```

ソースコードはこちら。
変数"line_notify_token"には、LINE Notifyで取得したアクセストークンを入力して下さい。

```GetWeather.py
#!/usr/bin/env python

import urllib.request
from bs4 import BeautifulSoup

import requests
line_notify_token = 'ACCCESS TOKEN NAME'
line_notify_api = 'https://notify-api.line.me/api/notify'

# Get Weather Information (Default:Mito, Ibaraki, Japan)
rssurl = "http://weather.livedoor.com/forecast/rss/area/080010.xml"

tenki = []
with urllib.request.urlopen(rssurl) as res:
    xml = res.read()
    soup = BeautifulSoup(xml, "html.parser")
    for item in soup.find_all("item"):
        title = item.find("title").string
        if title.find("[ PR ]") == -1:
            tenki.append(title)

for i in range(0,2):
    message = tenki[i]
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + line_notify_token}  # Token
    line_notify = requests.post(line_notify_api, data=payload, headers=headers)
```


# 実行結果
ターミナル上で実行したところ、

![image1.jpeg](https://qiita-image-store.s3.amazonaws.com/0/197189/359be791-a442-652a-c861-44541530f25e.jpeg)

当日と翌日の水戸の天気と最高気温を簡単に取得できちゃいました。これは便利！


#スクリプトを自動実行させたい

ちょうど家用のファイルサーバ導入目的で購入したRaspberry Pi3があったので、こちらでcronを回して運用することにしました。

ちなみに運用イメージはこんな感じです。
![名称未設定.png](https://qiita-image-store.s3.amazonaws.com/0/197189/60771dd8-e379-1afb-850e-c2f1f26e7c80.png)


ターミナル開いて、crontab -eを実行して下記を入れるだけ。
OSがLinuxだととても簡単です。

```

#分 時 日 月 曜日 <実行コマンド>
00 06 * * * python3 <pythonファイル格納先の絶対パス>
00 08 * * * python3 <pythonファイル格納先の絶対パス>
```
上記の例では毎日午前6時、午後6時に指定したpythonスクリプトを実行します。
実行ファイルの指定は絶対パスで指定してください。
（絶対パスで指定しないとうまく動かなかった・・・）

# 天気情報に応じたアイコンを出してみた

通知内容がテキストだけだと天気情報が直感的にわかりにくいので
下記のように、テキストとアイコンをセットで出すようにしてみました。
・「晴れ」だったら晴れのアイコンを出す
・「晴れのち雨」だったら晴れと雨が順に並んでいるアイコンを出す


<img src="https://qiita-image-store.s3.amazonaws.com/0/197189/351de576-e331-a0d3-b7c3-3398bdffbd6b.jpeg" width=80%>

↑こんな感じ。直感的に使いやすくなった気がします。

ソースはは下記、だいぶ記載量増えてますが
やってることはメッセージに出てくる天気情報を判別してアイコンを出すようにしているだけ。

「◯◯のち▲▲」と「◯◯時々▲▲」は分けるとパターンが複雑になるため、一緒のアイコンを出すようにしています。

```GetWeather.py
#!/usr/bin/env python
import urllib.request
from bs4 import BeautifulSoup
import requests

icon_path = "アイコンが格納されているディレクトリの絶対パス"

line_notify_token = 'アクセストークンIDを入力してください'
line_notify_api = 'https://notify-api.line.me/api/notify'

# 抽出対象のRSSとURL(デフォルトは茨城県水戸市)
rssurl = "http://weather.livedoor.com/forecast/rss/area/080010.xml" 
URL = "http://weather.livedoor.com/area/forecast/080010"

tenki = []
detail = []

## Parser : 天気情報WebページのHTMLタグから天気情報を抽出してパースするメソッド ##########################
def Parser(rssurl):
   with urllib.request.urlopen(rssurl) as res:
      xml = res.read()
      soup = BeautifulSoup(xml, "html.parser")
      for item in soup.find_all("item"):
         title = item.find("title").string
         description = item.find("description").string
         if title.find("[ PR ]") == -1:
            tenki.append(title)
            detail.append(description)

## ck_Weather : 取得した天気情報とそれに応じたアイコンを出力するメソッド ################################
def ck_Weather(i, detail):
   if (detail[i].find("晴")) != -1 and (detail[i].find("曇")) == -1 and (detail[i].find("雨")) == -1 and (detail[i].find("雪")) == -1:
       files = {'imageFile': open(icon_path + "Sun.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                            
   elif (detail[i].find("晴時々曇")) != -1 or (detail[i].find("晴のち曇")) != -1:
       files = {'imageFile': open(icon_path + "SunToCloud.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                        
   elif (detail[i].find("晴時々雨")) != -1 or (detail[i].find("晴のち雨")) != -1:
       files = {'imageFile': open(icon_path + "SunToRain.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                    
   elif (detail[i].find("晴時々雪")) != -1 or (detail[i].find("晴のち雪")) != -1:
       files = {'imageFile': open(icon_path + "SunToSnow.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                
   elif (detail[i].find("曇")) != -1 and (detail[i].find("晴")) == -1 and (detail[i].find("雨")) == -1 and (detail[i].find("雪")) == -1:
       files = {'imageFile': open(icon_path + "Cloud.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                            
   elif (detail[i].find("曇時々晴")) != -1 or (detail[i].find("曇のち晴")) != -1:
       files = {'imageFile': open(icon_path + "CloudToSun.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                        
   elif (detail[i].find("曇時々雨")) != -1 or (detail[i].find("曇のち雨")) != -1:
       files = {'imageFile': open(icon_path + "CloudToRain.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                    
   elif (detail[i].find("曇時々雪")) != -1 or (detail[i].find("曇のち雪")) != -1:
       files = {'imageFile': open(icon_path + "CloudToSnow.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                
   elif (detail[i].find("雨")) != -1 and (detail[i].find("晴")) == -1 and (detail[i].find("曇")) == -1 and (detail[i].find("雪")) == -1:
       files = {'imageFile': open(icon_path + "Rain.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                            
   elif (detail[i].find("雨時々晴")) != -1 or (detail[i].find("雨のち晴")) != -1:
       files = {'imageFile': open(icon_path + "RainToSun.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                                        
   elif (detail[i].find("雨時々曇")) != -1 or (detail[i].find("雨のち曇")) != -1:
       files = {'imageFile': open(icon_path + "RainToCloud.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                                                    
   elif (detail[i].find("雨時々雪")) != -1 or (detail[i].find("雨のち雪")) != -1:
       files = {'imageFile': open(icon_path + "RainToSnow.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                                                                
   elif (detail[i].find("雪")) != -1 and (detail[i].find("晴")) == -1 and (detail[i].find("雨")) == -1 and (detail[i].find("曇")) == -1:
       files = {'imageFile': open(icon_path + "Snow.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                                                                            
   elif (detail[i].find("雪時々晴")) != -1 or (detail[i].find("雪のち晴")) != -1:
       files = {'imageFile': open(icon_path + "SnowToSun.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                                                                                        
   elif (detail[i].find("雪時々曇")) != -1 or (detail[i].find("雪のち曇")) != -1:
       files = {'imageFile': open(icon_path + "SnowToCloud.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
                                                                                                                                                                                                    
   elif (detail[i].find("雪時々雨")) != -1 or (detail[i].find("雪のち雨")) != -1:
       files = {'imageFile': open(icon_path + "SnowToRain.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
    
   elif (detail[i].find("暴風雨")) == -1:
       files = {'imageFile': open(icon_path + "Typhon.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)

   elif (detail[i].find("暴風雪")) == -1:
       files = {'imageFile': open(icon_path + "HeavySnow.png","rb")}
       line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)

   else:
       line_notify = requests.post(line_notify_api, data=payload, headers=headers)


## メイン処理 ###################################################################################

Parser(rssurl) # 天気予報サイトのHTMLタグから天気情報を抽出
for i in range(0,2):
    message = detail[i]
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + line_notify_token}  

    ck_Weather(i, detail) # 天気情報とそれに応じた天気アイコンを出力

message = URL
payload = {'message': message}
headers = {'Authorization': 'Bearer ' + line_notify_token}  # Notify URL
line_notify = requests.post(line_notify_api, data=payload, headers=headers)

################################################################################################


```

なお、アイコンはフリー素材のサイトのものを使用しております。
PNGでダウンロードし、指定したディレクリパスに格納しておけばOKです。
<img width="450" alt="スクリーンショット 2019-12-05 22.51.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197189/d24e4793-98f2-6deb-10d6-63d612a9b92d.png">

<font color="Red">**【2020/06/12 追記】
  少しリファクタリングを試みました。**</font>

- アイコンを格納先のディレクトリを変数"icon_path"で外だしにすることで、
  格納先の変更をしやすくしました。

- 天気予報WebサイトのHTMLタグから天気情報を抽出する処理と
天気情報とそれに応じたアイコンを出力する処理をメソッド化しました。
  - <font color="Blue">**Parser**</font> : 天気情報WebページのHTMLタグから天気情報を抽出してパースするメソッド
  - <font color="Blue">**ck_Weather**</font> : 取得した天気情報とそれに応じたアイコンを出力するメソッド