天気予報配信サービス停止による天気予報データ読上げpythonプログラムリプレース実施

1.天気予報が過去日付で表示

Raspberrypi を使用して電光掲示板で、時刻、天気予報及びニュースを定時に表示していることは、過去の記事(Pythonでコマンドを呼出し天気予報を電光掲示板に表示ラズパイ電光掲示板/天気予報の表示内容を変えてみる)で何度かお知らせしています。

この度、電光掲示板の天気予報表示に違和感を感じました。8月半ばで「危険な暑さ」と騒がれているのに、最高気温が 31℃、最低気温が 23℃と表示されているんです。

その原因は、電光掲示板に流れる日付を見て氷解しました。

8/15になっているのに、天気予報は7月31日の天気を出していたのです。

以前も、ラズパイが瞬断した際、同じような現象が起きたことがありました(古いニュース、天気予報を表示する電光掲示板の不具合を対応/お約束の問題発生!)。

但し、今回は事情が違い、ニュースは正しい日付で表示されているのです。

2.原因はサービス停止

猛暑の中ですが、原因究明に入りました。

まずは、天気予報を流している shell を動かしてみましょう。

start
140010
Traceback (most recent call last):
File "/home/pi/python-pg/tenki_file.py", line 116, in
main()
File "/home/pi/python-pg/tenki_file.py", line 13, in main
txt_weather()
File "/home/pi/python-pg/tenki_file.py", line 50, in txt_weather
obj = json.loads( r.read().decode('utf-8') )
File "/usr/lib/python3.5/json/init.py", line 319, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.5/json/decoder.py", line 339, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.5/json/decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 4)
convert end
add border end
size change end

案の定、エラーが発生していますね。本来 value が来るべき箇所に、value でないものが来ているようなことを言っています。

天気予報の内容を受領する次の命令でエラーが発生しているようです。

obj = json.loads( r.read().decode('utf-8') )

以前、天気予報の内容が、うまく受領できないときに追加した命令です。

ウムム、ひょっとして、ここで使用している json.loads の引数が変わっとかしてエラーが発生したのかと勝手に解釈し、ネットの世界をググってみました。

さんざん格闘しましたが原因がつかめません。

そこで、json データを受領しているURLをブラウザ上から検索してみました。

livedoor のページが検索されます。

数時間気づかなかったのですが、ふと「何で、jsonコードが表示されないのか?」という疑問に思い当たりました。

そこで、別のラズパイで天気予報を読み上げる同じ jsonコードを使ったプログラムを実行してみたんですが、何とこちらもニュースを喋ってくれません。

再実行したログを見たところ、電光掲示板側で出たものと全く同じエラーが表示されます。

勘の鈍い60爺ですが、ここでやっと気がつきました。ひょっとして天気予報の配信サービスが終了したのではないかと・・・。

早々ニュースを当たってみたところ livedoor の天気予報のサービスが7月31日をもって終了していたことがわかりました。

これが原因だったのですが、気付くのに半日以上を費やしてしまいました。

3.対策

原因がわかったことで、その対策に入ります。対策と言っても、動かなくなった以下の pythonプログラムを修正するだけのことです。

  • 天気予報を読み上げている pythonプログラム
  • 電光掲示板で天気予報を表示している pythonプログラム

簡単に言えば、現在、livedoor から受信する json形式のデータ取出し部分を、他社からのデータ受信に変更すればいいだけのことです。

ところが、別会社で無料(!)で json形式データを提供しているところを探したのですが、日本だと見つからないんです。

OpenWeatherMap などが代替候補として挙がりましたが、当然、帰ってくる内容は英語表記です^^;

熟慮しましたが、やはり、英語表記のデータを扱うことは止めておこうという結論に達しました(天気の内容を天気マークに置き換える部分がかなり大変に見えました。参考資料①)。

そこで、json形式で受取る部分を rss形式に変更しデータを取り出すことに決定しました。

そして、rss形式でデータを配信している中で、大手でサービスを簡単には止めないであろう「Yahoo!天気」からデータを受信することに決めました。

4.天気予報読上げpythonプログラムリプレース

上記2つの修正プログラムのうち、まずは、「天気予報を読み上げている pythonプログラム」の修正から実施します。

電光掲示板では、天気予報マークを設定する必要があり、天気の内容から天気マークを取り出す部分が手間取りそうだと判断したのは上述したとおりです。

(1) 訂正箇所

① データ取出し部分

rss形式のデータを取り出す部分ですが、参考資料②の内容を丸々コピーすることで、天気予報データを手に入れることが出来ました。

【訂正前】

    json_url = 'https://weather.livedoor.com/forecast/webservice/json/v1' #API URL

    try:
        r =  urllib.request.urlopen('%s?city=%s' % (json_url, city) )
        obj = json.loads( r.read().decode('utf-8') )

【訂正後】

## 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)

### Execute
# 抽出対象のRSS(神奈川県横浜市)
rssurl = "https://rss-weather.yahoo.co.jp/rss/days/4610.xml"

② 今日の最高気温、最低気温を追加

今までのプログラムは、最高気温と最低気温は明日のものしかなかったんですが、今日の最高気温と最低気温を読み上げるように訂正します。

【訂正前】

        # TODAY
        cast = forecasts[0]
        today_w_txt = weather_text % (cast['dateLabel'], cast['telop'])

        # TOMMOROW
        cast = forecasts[1]
        temperature = cast['temperature']
        tommorow_w_txt = weather_text % (cast['dateLabel'], cast['telop'])
        tommorow_t_txt = temperature_text % (cast['dateLabel'], temperature['max']['celsius'], temperature['min']['celsius'])

        # SAY
        weather_str = title + ' ' + today_w_txt + ' ' + tommorow_w_txt + ' ' + tommorow_t_txt

【訂正後】

    for i in range(0,2):
        detail2 = detail[i].split()
        if i == 0:
             kyo_tenki = detail2[0]
             temp = detail2[2].split('/')
             kyo_max = temp[0]
             kyo_min = temp[1]
        else:
             asu_tenki = detail2[0]
             temp = detail2[2].split('/')
             asu_max = temp[0]
             asu_min = temp[1]

    title = u'神奈川県横浜の天気'

    # TODAY
    today_w_txt = weather_text % ('今日', kyo_tenki)
    today_t_txt = temperature_text % ('今日', kyo_max, kyo_min)

    # TOMMOROW
    tommorow_w_txt = weather_text % ('明日', asu_tenki)
    tommorow_t_txt = temperature_text % ('明日', asu_max, asu_min)

    # SAY
    weather_str = title + ' ' + today_w_txt + today_t_txt + ' ' + tommorow_w_txt + ' ' + tommorow_t_txt

(2) その他の訂正

① BeautifulSoup インストール

この天気情報を取得するのに、Python のライブラリのひとつである BeautifulSoup を用いていたため、下記のインストールが必要でした。

60爺のは、python3 なので以下のコマンドで実施しました。

sudo pip3 install beautifulsoup4

5.ソースコード

rssコードの取得部分が、参考資料のコピーにより簡単に出来たので、思いのほか時間をかけずに pythonプログラムの訂正が出来ました。

title については、livedoor のようになっておらず、以下のような総合データが出てしまうので、リテラルで対応しました。

【 18日(火) 東部(横浜) 】 晴のち曇 - 34℃/27℃ - Yahoo!天気・災害

その結果、以前と遜色ないレベルで天気予報の読み上げを実現できました。

jsay 8月20日、10時26分7秒
/usr/local/bin/jsay 8月20日、10時26分7秒
jsay '神奈川県横浜の天気 今日の天気は晴れです。今日の予想最高気温、35℃度、予想 最低気温、26℃度です。 明日の天気は晴れです。 明日の予想最高気温、35℃度、予想最低気温、26℃度です。'

最後に、(余り美しくないプログラムですが)ソースコードを掲載しておきます。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import shlex
import subprocess

from datetime import datetime

import urllib.request
from bs4 import BeautifulSoup

CMD_SAY = 'jsay'

def main():
    say_datetime()
    say_weather()
    return

def say_datetime():
    d = datetime.now()
    text = '%s月%s日、%s時%s分%s秒' % (d.month, d.day, d.hour, d.minute, d.second)
    text = CMD_SAY + ' ' + text
    print (text)
    text = '/usr/local/bin/' + text
    print (text)
    proc = subprocess.Popen(shlex.split(text))
    proc.communicate()
    return

def say_weather():

    weather_text = u'%sの天気は%sです。'
    temperature_text = u'%sの予想最高気温、%s度、予想最低気温、%s度です。'

    Parser(rssurl) # 天気予報サイトのHTMLタグから天気情報を抽出

    for i in range(0,2):
        detail2 = detail[i].split()
        if i == 0:
             kyo_tenki = detail2[0]
             temp = detail2[2].split('/')
             kyo_max = temp[0]
             kyo_min = temp[1]
        else:
             asu_tenki = detail2[0]
             temp = detail2[2].split('/')
             asu_max = temp[0]
             asu_min = temp[1]

    title = u'神奈川県横浜の天気'

    # TODAY
    today_w_txt = weather_text % ('今日', kyo_tenki)
    today_t_txt = temperature_text % ('今日', kyo_max, kyo_min)

    # TOMMOROW
    tommorow_w_txt = weather_text % ('明日', asu_tenki)
    tommorow_t_txt = temperature_text % ('明日', asu_max, asu_min)

    # SAY
    weather_str = title + ' ' + today_w_txt + today_t_txt + ' ' + tommorow_w_txt + ' ' + tommorow_t_txt
    #weather_str = weather_str.encode('utf-8')

    text = '''%s '%s' ''' % (CMD_SAY, weather_str)
    print (text)
    proc = subprocess.Popen(shlex.split(text))
    proc.communicate()

    return

## 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)

### Execute
# 抽出対象のRSS(神奈川県横浜市)
rssurl = "https://rss-weather.yahoo.co.jp/rss/days/4610.xml"

tenki = []
detail = []
detail2 = []
temp = []

if __name__ == "__main__":
    main()

この後、「電光掲示板で天気予報を表示している pythonプログラム」の対応に入りたいと思います。

※参考
天気予報APIを使って天気を表示してみた
【Yahoo!天気リプレース版】LINE Notify + Pythonで天気情報を取得する方法