VBA-JSONでネストされた深いところにあるJSONデータを取得する
VBAのJSONデータのパースは素晴らしいモジュールがあるのでそれを利用させてもらってますが、深い階層のデータ取得方法について書き留めておく。
まあ、開発者のページを読むと書いてあることではありますが。。。
VBA-JSONはここから https://github.com/VBA-tools/VBA-JSON/releases
解析したいJSON例
{ "Dates": { "OutboundDates": [ { "PartialDate": "2019-09-03", "QuoteIds": [ 1 ], "Price": 83.0, "QuoteDateTime": "2019-08-22T10:43:00" }, { "PartialDate": "2019-09-10", "QuoteIds": [ 2 ], "Price": 94.0, "QuoteDateTime": "2019-08-24T12:14:00" }
VBAでは":"記号の右側にあるデータをitemsプロパティからVariant型で取得でき、複数あるときはFor Each in Next文などでループして取得できるのだが、 いちばん注意したいのは配列の開始を表す"["の記号。
パースされた情報は"["から"]"までを一つの配列として処理しているから、配列"[...]"単位でループをネストさせながら深い階層に入っていく。
上のJSONをパースしたものをresult1に格納したとして、"Dates"直下のデータを取得したいときは
result1("Dates")
もう一つ下の"Dates"の中の"OutboundDates"の階層のデータを取得したいときは、
result1("Dates")("OutboundDates")
のように()で引数を増やして希望の階層まで下がっていき、そこにあるデータをFor Each in Next文でループして取得する。 "PartialDate"、"QuoteIds"、"Price"、"QuoteDateTime"の各データを取得したいときは、
VBA例
Sub Deepsea() '参照設定 Microsoft Scripting Runtime '参照設定 Microsoft XML. v6.0 Dim objXMLHttp As New MSXML2.XMLHTTP60 Dim result1 As New Scripting.Dictionary Dim result2 As New Scripting.Dictionary Dim i As Variant objXMLHttp.Open "GET", "http://... ", False objXMLHttp.send Set result1 = JsonConverter.ParseJson(objXMLHttp.responseText) For Each result2 In result1("Dates")("OutboundDates") For Each i In result2.Items Debug.Print i Next Next
ここで、残念ながら"QuoteIds"のデータだけは取得できない。"QuoteIds"はさらにその中が配列"[...]"になっているから。
データは"["記号から次の"["までの間しか取得できないので、"QuoteIds"の中データを取得したいときは
result1("Dates")("OutboundDates")("QuoteIds")
のようにさらに()で引数を増やしてこの配列の中に入りデータを取得する。
【Python】RaspberryPi3で電車の遅延情報をしゃべらせてみる
だいぶ前に買ったRaspberryPi3、使い方がよく分からなくて、使い道も思いつかないので放置していたんだけど、毎朝出勤前に「OK Google!」とかいちいち呼ばなくてもいつも乗る電車の遅延情報を勝手にしゃべってくれるといいかもと思ったので、Pythonの練習がてら作ってみることにした。
概要
- これからはPython2ではなく、3らしいのでPython3を使ってみる
- 毎朝設定した時刻にYahoo路線情報から運行情報を検索
- 路線名、状況、詳細内容のテキストを取得
- 取得したテキストをOpen JTalkで喋らせる
- 平常運転のときは黙っててほしい
HTML解析の準備
HTMLから必要なデータを取り出すのに必要なモジュールを3つLXTerminalからインストール。
pip3 install requests pip3 install lxml
RaspberryPiにPython2とPython3が入っているときは、pipではPython2にインストールされてしまうので、Python3にインストールしたいときはpip3と書く。
運行情報を取得する
設定できる路線は一つ。Yahoo路線情報で路線を探して、そのアドレスを変数target_url
に入れてHTML解析して情報を取得する。
取得したデータを変数q
に代入、この変数の中身がOpen JTalkで喋らせたい言葉になる。
アドレスは末尾の数字で路線が決まってるらしく、うちの地元京葉線は69/0、山手線なら21/0みたいなかんじ。
#Yahoo!路線情報(京葉線) target_url = 'https://transit.yahoo.co.jp/traininfo/detail/69/0/' target_html = requests.get(target_url).text root = lxml.html.fromstring(target_html) #路線名(かな) q = root.xpath('string(//span[@class="staKana"])') #運行情報 q = q + root.xpath('normalize-space(//div[@id="mdServiceStatus"])') #文字列の編集 q=q.replace('(','\n(') q=q.replace(':','時') q=q.replace('頃','分頃') q=q.replace('00分頃','頃') print (q)
取得した変数q
は「けいようせん [!]運転状況 20時10分頃、●●駅間で発生した●●の影響で、現在も列車に遅れや運休が出ています。」みたいなテキスト文になっている。
路線名を漢字じゃなくふり仮名で取得したのは、Open JTalkが「京成」を「きょうせい」とか読み間違えするから。
さらに、オリジナルのテキスト文をOpen JTalkが喋りやすいようにreplaceを使って少々加工してある。例えば「20:10頃」なら「にじゅう、じゅうごろ」と喋るけど、上のように「20時10分頃」と書き換えてやれば「にじゅうじじゅっぷんごろ」と喋ってくれる。
また、改行(\n
)があるとその手前までしか喋らないので、喋ってほしい文中の改行は取り除いたりした(normalize-space)。
Open JTalkを使って音声に変換
OpenJTalkと追加モジュール2つをLXTerminalからインストール。
sudo apt-get install open-jtalk sudo apt-get install open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001
OpenJTalkは、txtファイルに書かれている日本語文章をwavファイルに出力してくれるというもので、例えば初めにDocumentsフォルダ(/home/pi/Documents
)にvoice.txtというファイルを作成して、このファイルにしゃべらせたい言葉をかいておき、次のコマンドをLXTerminalで打つと音声合成変換されたtest.wavファイルを同じDocumentsフォルダに作ってくれる。
open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /home/pi/Documents/test.wav /home/pi/Documents/voice.txt
ちなみに、コマンド前半の
open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow
の部分は決まり文句らしいのでいじらずに、この後ろに出力したいwavファイルの場所と、入力するtxtファイルの場所を書くだけ。
最終的なコードはこんな感じです。(関数jtalk()とmain()のコードの辺りは借用させていただきました、すいません)。
平常運転のときは黙っててほしいので、変数index
が-1(「事故・遅延に関する情報はありません」という文字列が無い)のときだけ動作するようにした。
作成したプログラムはtest.pyという名前でDocumentsフォルダに保存する。
import lxml.html import requests import subprocess target_url = 'https://transit.yahoo.co.jp/traininfo/detail/69/0/' target_html = requests.get(target_url).text root = lxml.html.fromstring(target_html) q = root.xpath('string(//span[@class="staKana"])') q = q + root.xpath('normalize-space(//div[@id="mdServiceStatus"])') q = q.replace('(','\n(') q = q.replace(':','時') q = q.replace('頃','分頃') q = q.replace('00分頃','頃') index = q.find('事故・遅延に関する情報はありません') print(index) print (q) def jtalk(t): app = ['open_jtalk'] mech = ['-x','/var/lib/mecab/dic/open-jtalk/naist-jdic'] htsvoice = ['-m','/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice'] outwav = ['-ow','/home/pi/Documents/test.wav'] cmd = app + mech + htsvoice + outwav c = subprocess.Popen(cmd,stdin=subprocess.PIPE) c.stdin.write(t.encode('utf-8')) c.stdin.close() c.wait() aplay = ['aplay','-q','/home/pi/Documents/test.wav'] wr = subprocess.Popen(aplay) def main(): jtalk(q) if index == -1: if __name__ == '__main__': main()
平日の朝にだけ喋らせる
RaspberryPiにプリインストールされているcronを使いました。Windowsのタスクスケジューラみたいなものだと思います。LXTerminalでcrontab -e
と打つとエディタが起動するので、一番最後の行(べつに最初の行でもいいけど)に
10,20 8 * * 1-5 python3 /home/pi/Documents/test.py
みたいなかんじで書いて保存する。
書式は「分 時 日 月 曜日 コマンド」なので、この例では「月曜から金曜の8時10分と8時20分にDocumentsフォルダにあるtest.pyファイルをpython3で起動させる」となる。
最後に
音声出力はRaspberryPiの3.5Φイヤホン端子にダイソーの人気商品300円スピーカーを繋げてみました。いいかんじです。
かつては風が吹くだけでもすぐ止まると首都圏のJRの中でも悪名高かった京葉線ですが、最近はいろいろ対策されて簡単には止まらなくなったため、なかなかRaspberryPiが喋ってくれなくて寂しいです。