気の向くままに辿るIT/ICT/IoT
IoT・電子工作

自作ラズパイスマートスピーカーで天気予報を読み上げ

ホーム前へ次へ
Raspberry Piって?

自作ラズパイスマートスピーカーで天気予報を読み上げ

自作ラズパイスマートスピーカーで天気予報を読み上げ

2019/02/06

 以前、作ったRaspberry Pi 3 Model B+とJuliusOpen JTalkベースのスマートスピーカーを自作した時点で搭載済みの機能ですが、改めてページとして起こしておくことに。

 主な機能は、

 尚、ラズパイ用ACアダプタを挿したスイッチ付きコンセントでのON/OFFとは別にラズパイ用boot/reboot/shutdown物理ボタン付き。

 音声認識にJuliusを使った自作スマートスピーカーに伝言とメモの機能を実装するにあたり、マイクとスピーカーを専有してしまうOSSやALSAからPulseAudioに移行しました。

 ちなみに便利なのでラズパイだけでなく、PC/Debianにも自作スマートスピーカー機能を搭載しています。

 自ずとモニタ付きとなるPC版スマートスピーカーには、PC及びラズパイ双方のスマートスピーカー機能のデスクトップアプリとしてPyQt5/Qt Designerによる操作パネルも作成しました。

[2020/08/14]

 ずっと猛暑だから聞いてもしょうがないなって放置してたら、気づけば、livedoor天気サービス終了なんだって...。

 2020年7月31日(金)14:00 をもちまして、「livedoor 天気」のサービス提供を終了...

 当然、Weather Hacksも終わりってことね...ありがとね、Weather Hacks。

 最近、このページのアクセスが、少し増えてて?だったんだけど、そういうことか。

 って、どうしようかな...、OpenWeatherMapは、登録しなくちゃいけないし、Yahoo!APIもそうだけど、その上、わかりにくいし...。

 というわけでYahoo!天気・災害をpythonでスクレイピングしてみることにしました。

Yahoo!Japan天気・災害をスクレイピングして天気情報取得

 livedoorの天気予報API『Weather Hacks』のサービスがlivedoor天気と一緒に終了したらしく、これを使ったスクリプトは後段に置いておくとして改めてYahoo!Japan天気・災害の特定の都道府県・地域のページをWebスクレイピングして天気情報を取得することにしました。

 これを選んだのは、一番最初に見つけた無料で登録の必要がないものだったから。

Yahoo!天気スクレイピングスクリプト

$ chmod +x ~/script/get_yahoo_weather.py
$ cat ~/script/get_yahoo_weather.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
 
import requests
from bs4 import BeautifulSoup
import sys
 
####################################
# 1〜9までの数値1つを引数にとるスクリプト
 
# 1:今日の天気
# 2:明日の天気
# 3:明後日の天気
# 4:今日の気温
# 5:明日の気温
# 6:明後日の気温
# 7:今日の降水確率
# 8:明日の降水確率
# 9:明後日の降水確率
####################################
 
arg = sys.argv
if len(arg) <= 1:
  url = "https://weather.yahoo.co.jp/weather/jp/13/"
  target = "引数がありません"
  flg = "7"
  print(target)
else:
  if arg[1] == "1":
    url = "https://weather.yahoo.co.jp/weather/jp/13/"
    target = "今日の天気"
    flg = "1"
  elif arg[1] == "2":
    url = "https://weather.yahoo.co.jp/weather/jp/13/?day=2"
    target = "明日の天気"
    flg = "2"
  elif arg[1] == "3":
    url = "https://weather.yahoo.co.jp/weather/jp/13/?day=3"
    target = "明後日の天気"
    flg = "3"
  elif arg[1] == "4":
    url = "https://weather.yahoo.co.jp/weather/jp/13/"
    target = "今日の気温"
    flg = "4"
  elif arg[1] == "5":
    url = "https://weather.yahoo.co.jp/weather/jp/13/?day=2"
    target = "明日の気温"
    flg = "5"
  elif arg[1] == "6":
    url = "https://weather.yahoo.co.jp/weather/jp/13/?day=3"
    target = "明後日の気温"
    flg = "6"
  elif arg[1] == "7":
    url = "https://weather.yahoo.co.jp/weather/jp/13/"
    target = "今日の降水確率"
    flg = "7"
  elif arg[1] == "8":
    url = "https://weather.yahoo.co.jp/weather/jp/13/?day=2"
    target = "明日の降水確率"
    flg = "8"
  elif arg[1] == "9":
    url = "https://weather.yahoo.co.jp/weather/jp/13/?day=3"
    target = "明後日の降水確率"
    flg = "9"
  else:
    url = "https://weather.yahoo.co.jp/weather/jp/13/"
    target = "引数が変です"
    flg = "10"
    print(target)
 
html = requests.get(url)
soup = BeautifulSoup(html.text, 'lxml')
 
anounce = soup.find('span', class_='time').text
place = soup.find('h1').text
 
weather = soup.find('dd', class_='forecast').p.img.get('alt')
high = soup.find('em', class_='high').get_text()
low = soup.find('em', class_='low').get_text()
precip = soup.find('p', class_='precip').get_text()
 
if flg != "10":
  phrase = anounce + " " + place + " " + target + 'は、'
 
if flg == "1" or flg == "2" or flg == "3":
   phrase += weather + 'です。' + " " + '最高気温は、' + high + "度" + " " + '最低気温は、' + low + '度、' + '降水確率は、' + precip + 'です。'
elif flg == "4" or flg == "5" or flg == "6":
   phrase += '最高気温' + " " + high + "度" + " " + '最低気温' + " " + low + '度です。'
elif flg == "7" or flg == "8" or flg == "9":
   phrase += precip + 'です。'
else:
  target
 
if flg != "10":
  print(phrase)
$

 スクリプトはこんな感じ。

 今回は、JSONデータじゃなくてHTMLをスクレイピング。

 Pythonらしくないかも...、しかも嫌いなフラグを使ってしまった...、代わりと言ってはなんですが、今回は、今日・明日・明後日の3日分の天気と気温、降水確率の9種をスクリプト1つにまとめてみました。

 これは、東京の天気及び気温を取得する例、他の都道府県や地域の場合は、url要変更。

 東京のページのソースを眺めてみると今時点では、urlに([?day=1]を省略可っぽい)当日以外は、[?day=]で明日を示す2から7日先の8まで指定することで実際には、全部で8日分取得できます。

 音声合成のOpen JTalkから成るjsayスクリプトを組み込む方法がわからくなったので最終的にテキストを出力するスクリプトにしました。

$ cat ~/sound/voice.pl
#!/usr/bin/env perl
use utf8;
use warnings;
...
 
...
  when("今日天気"){
    system("/path/to/sound/jsay \"`/path/to/sound/script/get_yahoo_weather.py 1`\" &");
  }
...
$

 また、スマートな方法を思いつかなかったこともあり、ラズパイ版もパソコン版もスマートスピーカー用応答スクリプト(自身が作ったのはPythonじゃなくてPerl)の方で、jsayスクリプトに渡すのに、このスクリプトを実行・展開すべく、バッククォートで、更にひとまとまりのテキストとするため、ダブルクォートでくくる方法をとることにしました。

2019/02/06

天気予報APIから天気情報取得

 天気APIから情報を得るスクリプトについては、livedoorの天気予報API『Weather Hacks』を使ったというPythonで書かれたtalk_weather.pyをhttp://raspi.seesaa.net/article/415530289.htmlから拝借。

 入力に応じた分岐条件を追記・編集してもよかったのかもしれませんが、Python初心者の自身は、用途ごとにスクリプトファイルを分割し、ベースと成る音声操作応答用Perlスクリプトから、それらPythonスクリプトを直接呼び出す方法をとりました。

 このpythonスクリプト内では、奇しくも発話用にjsayという同じ名前のスクリプトを指定していますが、登録済み実行パスに存在するコマンドとして書いてあるようなので、そうでない場合は、環境に応じてjsayスクリプトのパスを合わせておく必要があります。

 jsayスクリプトは、Open JTalkから成るスクリプト、詳細は、冒頭リンク「スマートスピーカーを自作」参照。

 自身は、ローカルで対応しましたが、このWeather Hacks、日付や時刻情報も取得できます。

天気予報API取得情報読み上げスクリプト

$ chmod +x ~/script/today_weather.py
$ cat ~/script/today_weather.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import shlex
import subprocess
 
from datetime import datetime
 
import urllib2
import json
 
#CMD_SAY = 'jsay'
#CMD_SAY = './jsay'
CMD_SAY = '/home/xxx/tmp/sound/jsay'
 
def main():
  say_weather()
  return
 
def say_weather():
  city = '130010'; # Tokyo
  json_url = 'http://weather.livedoor.com/forecast/webservice/json/v1' #API URL
 
  weather_text = u'%sの天気は%sです。'
 
  try:
    r = urllib2.urlopen('%s?city=%s' % (json_url, city) )
    obj = json.loads( unicode(r.read()) )
 
    title = obj['title']
    forecasts = obj['forecasts']
 
    # TODAY
    cast = forecasts[0]
    today_w_txt = weather_text % (cast['dateLabel'], cast['telop'])
 
    # SAY
    weather_str = title + ' ' + today_w_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()
  finally:
    r.close()
 
  return
 
 
### Execute
if __name__ == "__main__":
  main()
$

 今日の天気、最高気温・最低気温を読み上げてくれるpythonスクリプト。

 と思いましたが、お天気Webサービス仕様のサンプルを見ると当日の最低気温はnullで取得できない仕様、最高気温は取得できそうに見えるもダメな模様。

 ただ、後述の通り、今日の最高気温だけなら取得できます。

 よって当日は、天気のみとしました。

$ chmod +x ~/script/tomorrow_weather.py
$ cat ~/script/tomorrow_weather.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import shlex
import subprocess
 
from datetime import datetime
 
import urllib2
import json
 
#CMD_SAY = 'jsay'
#CMD_SAY = './jsay'
CMD_SAY = '/home/xxx/tmp/sound/jsay'
 
def main():
  say_weather()
  return
 
def say_weather():
  city = '130010'; # Tokyo
  json_url = 'http://weather.livedoor.com/forecast/webservice/json/v1' #API URL
 
  weather_text = u'%sの天気は%sです。'
  temperature_text = u'%sの予想最高気温、%s度、予想最低気温、%s度です。'
 
  try:
    r = urllib2.urlopen('%s?city=%s' % (json_url, city) )
    obj = json.loads( unicode(r.read()) )
 
    title = obj['title']
    forecasts = obj['forecasts']
 
    # TOMORROW
    cast = forecasts[1]
    temperature = cast['temperature']
    tomorrow_w_txt = weather_text % (cast['dateLabel'], cast['telop'])
    tomorrow_t_txt = temperature_text % (cast['dateLabel'], temperature['max']['celsius'], temperature['min']['celsius'])
 
    # SAY
    weather_str = title + ' ' + 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()
  finally:
    r.close()
 
  return
 
 
### Execute
if __name__ == "__main__":
  main()
$

 明日の天気及び最高気温、最低気温を読み上げてくれるpythonスクリプト。

$ chmod +x ~/script/day_after_tomorrow_weather.py
$ cat ~/script/day_after_tomorrow_weather.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import shlex
import subprocess
 
from datetime import datetime
 
import urllib2
import json
 
#CMD_SAY = 'jsay'
#CMD_SAY = './jsay'
CMD_SAY = '/home/xxx/tmp/sound/jsay'
 
def main():
  say_weather()
  return
 
def say_weather():
  city = '130010'; # Tokyo
  json_url = 'http://weather.livedoor.com/forecast/webservice/json/v1' #API URL
 
  weather_text = u'%sの天気は%sです。'
 
  try:
    r = urllib2.urlopen('%s?city=%s' % (json_url, city) )
    obj = json.loads( unicode(r.read()) )
 
    title = obj['title']
    forecasts = obj['forecasts']
 
    # THE DAY AFTER TOMORROW
    cast = forecasts[2]
    day_after_tommorow_w_txt = weather_text % (cast['dateLabel'], cast['telop'])
 
    # SAY
    weather_str = title + ' ' + day_after_tommorow_w_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()
  finally:
    r.close()
 
  return
 
 
### Execute
if __name__ == "__main__":
  main()
$

 明後日の天気を読み上げてくれるpythonスクリプト。

 Weather Hacksでは、明後日の気温情報はnullであり、ないので天気のみ。

$ chmod +x ~/script/today_temperature.py
$ cat ~/script/today_temperature.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import shlex
import subprocess
 
from datetime import datetime
 
import urllib2
import json
 
#CMD_SAY = 'jsay'
#CMD_SAY = './jsay'
CMD_SAY = '/home/xxx/tmp/sound/jsay'
 
def main():
  say_weather()
  return
 
def say_weather():
  city = '130010'; # Tokyo
  json_url = 'http://weather.livedoor.com/forecast/webservice/json/v1' #API URL
 
  temperature_text = u'%sの予想最高気温、%s度、予想最低気温は、今更聞かないで下さい。'
 
  try:
    r = urllib2.urlopen('%s?city=%s' % (json_url, city) )
    obj = json.loads( unicode(r.read()) )
 
    title = obj['title']
    forecasts = obj['forecasts']
 
    # TODAY
    cast = forecasts[0]
    temperature = cast['temperature']
    today_t_txt = temperature_text % (cast['dateLabel'], temperature['max']['celsius'])
 
    # SAY
    weather_str = title + ' ' + today_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()
  finally:
    r.close()
 
  return
 
 
### Execute
if __name__ == "__main__":
  main()
$

 今日の最高気温、最低気温を読み上げてくれるpythonスクリプト。

 ただし、前述の通り、当日の最低気温は、nullで取得できない為、「今更聞かないで」と返すようにしてみました。

$ chmod +x ~/script/tomorrow_temperature.py
$ cat ~/script/tomorrow_temperature.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import shlex
import subprocess
 
from datetime import datetime
 
import urllib2
import json
 
#CMD_SAY = 'jsay'
#CMD_SAY = './jsay'
CMD_SAY = '/home/xxx/tmp/sound/jsay'
 
def main():
  say_weather()
  return
 
def say_weather():
  city = '130010'; # Tokyo
  json_url = 'http://weather.livedoor.com/forecast/webservice/json/v1' #API URL
 
  temperature_text = u'%sの予想最高気温、%s度、予想最低気温、%s度です。'
 
  try:
    r = urllib2.urlopen('%s?city=%s' % (json_url, city) )
    obj = json.loads( unicode(r.read()) )
 
    title = obj['title']
    forecasts = obj['forecasts']
 
    # TOMORROW
    cast = forecasts[1]
    tommorow_t_txt = temperature_text % (cast['dateLabel'], temperature['max']['celsius'], temperature['min']['celsius'])
 
    # SAY
    weather_str = title + ' ' + 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()
  finally:
    r.close()
 
  return
 
 
### Execute
if __name__ == "__main__":
  main()
$

 明日の最高気温、最低気温を読み上げてくれるpythonスクリプト。

ラズパイスマートスピーカーへの反映

 ラズパイスマートスピーカーへの反映についても詳細は、「スマートスピーカーを自作」他、リンク先参照。

ホーム前へ次へ