はじめに
【2023/08/17更新】
RSSサービス終了に伴い、記事の内容、および実装方法を大幅刷新しました。
以前投稿したLINE Notify + Pythonで天気予報を取得する方法で天気予報サイトからスクレイピングした天気予報をLINEで通知する方法を紹介しました。
しかし、ソース元のlivedoor天気予報が2020年7月31日を以ってサービス終了となってしまったため、天気情報の収集先のWebサービスの変更(リプレース)を実施することにしました。
#移行先の選定と理由
今回はYahoo!天気を移行先として選びました。
選んだ理由は下記。
-
タグ構造がシンプルでlivedoor天気予報のrssとさほど差がなく、修正量を最小限にできる。
-
天気予報サービスとしてはメジャー。今後もサービス終了する可能性は低い。
(ただしRSSサービスは終了した・・・)
本記事の初版投稿時はyahoo!天気予報の水戸市のRSSを取得してスクレイピングを試みたのですが、
残念なことに、Yahoo!天気予報のRSSのサービスが終了してしまったため、直接HTMLの内容から天気情報を取得する手法をとることにしました。
HTMLの中を見ると、forecastCityタグの中に、
当日と翌日の日付、天気情報、最高気温、最低気温の情報が入っているため、ここを抽出すればよさそう。
天気データの抽出処理と手順ははこちらの記事の手法を取り入れさせていただきました。
(データの取り込み方の説明が非常にわかりやすいです)
ISHITECH 【Python】Yahoo天気予報をスクレイピングしてデータを入手する
https://ishi-tech.biz/python-scraping-yahoo-weather/
※今回はRSSではなくHTMLからのデータ取得になるため、念のためYahoo!天気のrobots.txtの内容を確認しました。
記事更新時点(2023年8月時点)ではありますが、Yahoo!天気のサイトでスクレイピングを禁止しているような記述がないことを確認できています。
robots.txtの見方はこちらの記事が参考になります。
スクレイピング実施前に必ずrobots.txtの記述内容を確認してから実施することをお勧めいたします。
実行環境
- OS:Windows10, Mac OS X, Linuxいずれも可。
- python3
- beautifulspoup, request
- raspberry Pi
ソースコード
基本的なロジックは前のエントリと変わりませんが、
天気情報の出し方に若干差異があります。
【差異 その1】
アイコン出力の判定で使用してるので、記載をYahoo!に合わせています。
例えば、Yahoo!天気では晴一時曇のように●●一時▲▲が追加されているようなので、これをOR条件に加えています。
(例)天気情報に"晴一時曇"または"晴のち曇"または"晴れ時々曇"のワードが出た場合・・・
晴マーク+曇マークが表示されたアイコンが表示される。
【差異 その2】
ここは使用する人によっては蛇足な処理かもしれませんが・・・
我が家では
「朝5時半に今日の天気情報を出力し、夜8時に明日の天気情報を出力できるようにしてほしい」
という要望があったため
6時を基準時刻とし
それ以前に本スクリプトが起動された場合は当日の天気時刻を出力
6時より後の時間帯に起動された場合は翌日の天気情報を出力する
という処理を追加することにしました。
ソースコードは下記。
#!/usr/bin/env python
import urllib.request
from operator import itemgetter
from bs4 import BeautifulSoup
import requests
import datetime
icon_path = "アイコンが格納されているパス(絶対パスで入力する)"
# 本番用トークンID
line_notify_token = 'ACCESS TOKEN NAME'
# LINE Notify APIのURL
line_notify_api = 'https://notify-api.line.me/api/notify'
# 抽出対象のURL(デフォルトは茨城県水戸市)
rssurl = "https://weather.yahoo.co.jp/weather/jp/8/4010.html"
## sw_Weather : 取得した天気情報とそれに応じたアイコンを出力するメソッド ################################################################
def sw_Weather(date, description):
# 処理1: 天気予報サイトのURLのリンクを出力
tenki_url = rssurl # 天気情報サイトのURLを格納
url_payload = {'message': tenki_url}
url_headers = {'Authorization': 'Bearer ' + line_notify_token}
line_notify = requests.post(line_notify_api, data=url_payload, headers=url_headers)
# 処理2: 天気情報とそれに応じた天気アイコンを出力
message = description
payload = {'message': "\n" + message}
headers = {'Authorization': 'Bearer ' + line_notify_token}
if (date.find("晴れ")) != -1 and (date.find("曇り")) == -1 and (date.find("雨")) == -1 and (date.find("雪")) == -1:
files = {'imageFile': open(icon_path + "Sun.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("晴一時曇")) != -1 or (date.find("晴のち曇")) != -1 or (date.find("晴時々曇")) != -1 or (date.find("晴のち時々曇")) != -1 or (date.find("晴のち一時曇")) != -1:
files = {'imageFile': open(icon_path + "SunToCloud.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("晴一時雨")) != -1 or (date.find("晴のち雨")) != -1 or (date.find("晴時々雨")) != -1 or (date.find("晴のち時々雨")) != -1 or (date.find("晴のち一時雨")) != -1 :
files = {'imageFile': open(icon_path + "SunToRain.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("晴一時雪")) != -1 or (date.find("晴のち雪")) != -1 or (date.find("晴時々雪")) != -1 or (date.find("晴のち時々雪")) != -1 or (date.find("晴のち一時雪")) != -1:
files = {'imageFile': open(icon_path + "SunToSnow.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("曇り")) != -1 and (date.find("晴れ")) == -1 and (date.find("雨")) == -1 and (date.find("雪")) == -1:
files = {'imageFile': open(icon_path + "Cloud.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("曇一時晴")) != -1 or (date.find("曇のち晴")) != -1 or (date.find("曇時々晴")) != -1 or (date.find("曇のち時々晴")) != -1 or (date.find("曇のち一時晴")) != -1:
files = {'imageFile': open(icon_path + "CloudToSun.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("曇一時雨")) != -1 or (date.find("曇のち雨")) != -1 or (date.find("曇時々雨")) != -1 or (date.find("曇のち時々雨")) != -1 or (date.find("曇のち一時雨")) != -1:
files = {'imageFile': open(icon_path + "CloudToRain.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("曇一時雪")) != -1 or (date.find("曇のち雪")) != -1 or (date.find("曇時々雪")) != -1 or (date.find("曇のち時々雪")) != -1 or (date.find("曇のち一時雪")) != -1:
files = {'imageFile': open(icon_path + "CloudToSnow.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雨")) != -1 and (date.find("晴れ")) == -1 and (date.find("曇り")) == -1 and (date.find("雪")) == -1:
files = {'imageFile': open(icon_path + "Rain.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雨一時晴")) != -1 or (date.find("雨のち晴")) != -1 or (date.find("雨時々晴")) != -1 or (date.find("雨のち時々晴")) != -1 or (date.find("雨のち一時晴")) != -1:
files = {'imageFile': open(icon_path + "RainToSun.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雨一時曇")) != -1 or (date.find("雨のち曇")) != -1 or (date.find("雨時々曇")) != -1 or (date.find("雨のち時々曇")) != -1 or (date.find("雨のち一時曇")) != -1:
files = {'imageFile': open(icon_path + "RainToCloud.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雨一時雪")) != -1 or (date.find("雨のち雪")) != -1 or (date.find("雨時々雪")) != -1 or (date.find("雨のち時々雪")) != -1 or (date.find("雨のち一時雪")) != -1:
files = {'imageFile': open(icon_path + "RainToSnow.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雪")) != -1 and (date.find("晴れ")) == -1 and (date.find("雨")) == -1 and (date.find("曇り")) == -1:
files = {'imageFile': open(icon_path + "Snow.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雪一時晴")) != -1 or (date.find("雪のち晴")) != -1 or (date.find("雪時々晴")) != -1 or (date.find("雪のち時々晴")) != -1 or (date.find("雪のち一時晴")) != -1:
files = {'imageFile': open(icon_path + "SnowToSun.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雪一時曇")) != -1 or (date.find("雪のち曇")) != -1 or (date.find("雪時々曇")) != -1 or (date.find("雪のち時々曇")) != -1 or (date.find("雪のち一時曇")) != -1:
files = {'imageFile': open(icon_path + "SnowToCloud.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("雪一時雨")) != -1 or (date.find("雪のち雨")) != -1 or (date.find("雪時々雨")) != -1 or (date.find("雪のち時々雨")) != -1 or (date.find("雪のち一時雨")) != -1:
files = {'imageFile': open(icon_path + "SnowToRain.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.find("暴風雨")) != -1:
files = {'imageFile': open(icon_path + "Typhon.png","rb")}
line_notify = requests.post(line_notify_api, data=payload, headers=headers, files=files)
elif (date.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)
## メイン処理 ###################################################################################################################
#天気情報WebページのHTMLタグから天気情報を抽出してパースする
data = urllib.request.urlopen(rssurl)
soup = BeautifulSoup(data, 'html.parser')
rs = soup.find(class_='forecastCity')
rs = [i.strip() for i in rs.text.splitlines()]
rs = [i for i in rs if i != ""]
#基準時刻の取得
# 本プログラムの起動時刻に応じて使用者が自ら時刻設定をすること。
# 使用例:5時と20時の2回起動する場合、基準時刻を6時に設定すれば、
# 5時の場合は今日の天気情報を、22時の場合は明日の天気情報を出力できるようになる。
# base : 基準時刻
# now : 現在時刻
#基準時刻の取得
base = datetime.time(6, 0, 0)
#現在時刻の取得
datetime_now = datetime.datetime.now()
now = datetime_now.time()
if (now > base): # 現在時刻 が 基準時刻 より後の場合は明日の天気情報を変数descriptionに追加する。
description = "明日 "
description+= rs[18]
description+= "の天気は"
description+= rs[19]
description+= "。"
description+= "最高気温 "
description+= rs[20]
description+= "、"
description+= "最低気温 "
description+= rs[21]
description+= "です。"
else: # 現在時刻 が 基準時刻 以前の場合は今日の天気予報を変数descriptionに追加する。
description = "今日 "
description+= rs[0]
description+= "の天気は"
description+= rs[1]
description+= "。"
description+= "最高気温 "
description+= rs[2]
description+= "、"
description+= "最低気温 "
description+= rs[3]
description+= "です。"
if (now > base):
sw_Weather(rs[19], description) #明日の天気情報と天気のアイコンを表示
else:
sw_Weather(rs[1], description) #今日の天気情報と天気のアイコンを表示
################################################################################################
実行結果
Raspberry Piにcron設定して自動実行します。
詳細はLINE Notify + Pythonで天気予報を取得する方法の「実行結果」と「スクリプトを自動実行したい」「天気情報に応じたアイコンを出してみた」の章を参照してください。
今回は例として、午前5時30分と午後8時の計2回スクリプトを実行するように設定してみました。