連載第2回目の目的
この連載第2回目では、Excel VBAによるWebサービスの呼び出しとXMLデータの解析についての手順を学びます。連載第1回目では、Excelの備える機能にてほぼ自動的にテーブルへの展開を行っていましたが、今回はそれらをVBAで行います。Webサービスでは、取得できるデータをXML形式で提供しているものもありますので、似たようなケースでは同様の手順でのデータ取得などに応用できるでしょう。
題材としては、株式会社ヴァル研究所の提供する「駅すぱあとWebサービス」を用います。このサービスは、Webサイトやスマートフォンアプリで有名な「駅すぱあと 乗換案内」でできることをWebサービスを通して利用できるようにするものです。継続使用には有償の契約が必要ですが、90日間無償で全機能を使用できる無料評価版がありますので、今回はこれを使用して指定区間の運賃を自動取得するExcelワークシートを作成します(図1)。
完成サンプルのソースコード
https://github.com/wateryinhare62/mynavi_excelvba_webservice
なお、本連載では動作確認をWindows 10 Pro、Microsoft 365(Excel 16.0、VBA 7.1)で行っています。旧バージョンや単体のExcelで試す場合にはご注意下さい。
APIキーを取得する
最初に、「駅すぱあとWebサービス」を使用するためのAPIキーを取得します。まず、「駅すぱあとWebサービス」のトップページにアクセスします(図2)。
▼駅すぱあとWebサービス - システム連携サービス
https://ekiworld.net/service/sier/webservice/index.html
ページ下方にある「90日間無料評価版」を見つけてクリックします(図3)。
「駅すぱあとWebサービス スタンダードプラン 評価版お申し込み」のページに移りますので、すべての項目に入力します(図4)。
・会社名(必須):法人名を入力します。
・お名前(必須):利用者名を入力します。
・メールアドレス(必須):利用者のメールアドレスを入力します。APIキーはこのアドレスに届きますので間違いのないように入力します。
・ご利用ドメイン(必須):利用者のドメイン名を入力します。アクセス時の認証がこのドメイン名を用いて行われますので、アクセスする組織のドメイン名を指定してください。
・ご利用目的(必須):利用目的(APIの評価、テストなど)を入力します。
・お申し込みのきっかけ:自由に選択して下さい。
・ガイドライン(必須):注意事項になりますので、一読した上で問題なければ[同意する]にチェックを入れて下さい。
・個人情報の取り扱い(必須):個人情報保護法に基づく注意事項です。こちらも一読した上で問題なければ[同意する]にチェックを入れて下さい。
すべて入力した上で[送信する]をクリックすると、送信内容の確認ページが表示されますので、問題なければ[この内容で送信する]をクリックします(図5)。[戻る]をクリックして、内容を修正することもできます。
このあと、送信したことを示す画面となりますので(図6)、先ほど指定したメールアドレスにAPIキーについての連絡が入るまで待ちます。送信した旨の自動返信メールがきちんと届いているか確認しましょう。
筆者の場合、当日中にAPIキーを知らせるメールが送られてきました。APIキーはサービスを利用する上で重要ですから、外部に漏えいしないように注意しましょう。APIキーが入手できれば、ひとまず「駅すぱあとWebサービス スタンダードプラン 評価版」の利用を開始する準備ができたことになります。
APIの呼び出しをテストする
APIキーを取得したら、APIの呼び出しをテストしてみましょう。WebブラウザのURL欄に、以下のURLを入力します。「APIキー」の箇所は、各自が入手したAPIキーで置き換えて下さい。
https://api.ekispert.jp/v1/xml/dataversion?key=APIキー
たとえばGoogle Chromeの場合、図6のように表示されれば、APIキーは有効で、APIの呼び出しもうまくいっていることがわかります。
この回はXMLデータを扱うので、URLはXMLを取得するようになっています(URL中の「xml」の箇所)。「駅すぱあとWebサービス」ではXML形式のほかにJSON形式でもデータを取得できるようになっており、その場合にはURLの[xml」を「json」に変更します。 なお、「駅すぱあと Webサービス」のAPIについての詳細は、下記を参照してください。本記事では、APIについては最小限の解説のみ行います。
駅すぱあとWebサービス Documents 駅データ・路線検索のWebAPI https://docs.ekispert.com/v1/
ワークシートの準備
APIが使えることを確認できたら、計算開始のボタン、運賃を取得したい区間のリストなど、ワークシートを準備します。
取得開始のボタンを配置する
ワークシートは、「空白のブック」で始めます。このワークシートの上部に、運賃取得を開始するボタンを配置します。[開発]タブをクリックし、[コントロール]グループの[挿入]ボタンをクリックすると、コントロール一覧がメニューとして表示されます([開発]タブが表示されていない場合は連載第1回を参照)。ここから、「フォームコントロール」カテゴリにある[ボタン(フォームコントロール)]をクリックします。マウスカーソルの形状が「+」に変わって、好きな位置に好きな大きさのボタンを配置できるようになります。
マウスボタンのリリースと同時に「マクロの登録」ダイアログが開きますので、[新規作成]ボタンをクリックして、ボタンクリック時に呼び出されるイベントハンドラを作成します(図7)。
イベントハンドラ名の初期値は「ボタン1_Click」ですが、[マクロ名]欄で名称を変更することもできます。Visual Basic Editorが起動して、イベントハンドラが作成されたことを確認できます(図8)。
ワークシート画面に戻ると、ボタンも作成されていることが確認できます(図9)。
ここで、ワークシートを保存しておきます。ワークシート名は何でもよいですが、形式を「Excel マクロ有効ブック (*.xlsm)」にしてください。マクロを有効にしておかないと、VBAのスクリプトを実行できないからです。
運賃取得区間のリストを準備する
次に、運賃取得区間のリストをワークシートに作成します。リストは単純で、出発駅、到着駅を記載するだけです。記載した分だけ繰り返し、片道の運賃と往復の運賃をAPIを通じて取得し、ワークシートに反映させます。
なお、リストは出発駅、到着駅、片道運賃、往復運賃の4列がメインですが、デバッグ用に出発駅コード、到着駅コードを埋め込む列を設けており、計6列のリストになっています。開発中はすべての列を表示させておき、開発が終了したらコードの入る列は隠してしまうことにしまうことにして、ここでは列の幅を調整するだけにします(図10)。
駅コードを取得する
ここからスクリプトを書いていきますが、まずは駅コードを取得するところからやっていきます。なぜなら、経路や運賃を取得する場合には、駅は名前ではなくコードで指定するからです。出発駅、到着駅のそれぞれについて駅コードを取得し、駅名の隣の列にセットしていきます。
Microsoft XML v6.0を準備する
VBAでXMLデータを使いますので、そのために必要なライブラリへの参照を有効にしておきます。Visual Basic Editorにて、[ツール]メニューの[参照設定]から「参照設定 - VBA Project」を開きます。[参照可能なライブラリ ファイル]から「Microsoft XML v6.0」を見つけて、チェックが入っていなければチェックを入れます。そして[OK]ボタンをクリックすれば終了です(図11)。
Webサービスにアクセスする
今回は、駅コードを取得するだけではなく、それをもとに運賃を取得することも行います。取得できる情報はまだまだあるので、拡張性を考慮してWebサービスへのアクセスは共通化しましょう。まずは、そのスクリプトを書いていきます。ボタンの配置でスクリプトを関連付けた時点で、すでに標準モジュールのModule1が作成されていますので、ここにスクリプトを追加していきます。Webサービスにアクセスするスクリプトは、KickWebServiceという関数にします。それをリスト1に示します。「End Sub」の下に追加します。
[リスト1]KickWebService関数
Private Function KickWebService(ByVal Api As String, ByVal Param As String) As String
Dim ApiKey As String, Url As String
ApiKey = "test_xxxxxxxxxxx" (1)
Url = "https://api.ekispert.jp/v1/xml/" & Api & "?key=" & ApiKey & Param
Dim http As Object (2)
Set http = CreateObject("MSXML2.XMLHTTP")
With http
.Open "GET", Url, False
.send
KickWebService = .responseText (3)
End With
End Function
引数に、「駅すぱあと Webサービス」で使用できるAPIの名称(URLパスの一部)、そしてクエリ引数(「&name=value…」の形式)を受け取って、結果としてXML文字列を返す関数です。ApiKey変数の値は、各自が入手したAPIキーで置き換えて下さい(1)。 VBAでは、WebサービスなどHTTPでアクセスする必要のあるとき、MSXML2.XMLHTTPというモジュールのオブジェクトを使用します。オブジェクト作成後、HTTPメソッドとURLを指定してOpenメソッドを呼び出し、必要なリクエストパラメータを設定した後(ここでは不要なのでやっていない)、sendメソッドで実際の呼び出しを行います(2))。 呼び出した結果はResponseTextプロパティで取得できます。これが空なら何らかのエラーが発生していますが、これをそのまま関数の戻り値として返します(3)。
Webサービス呼び出しをテストする
KickWebService関数が問題なく動くか確認するためのスクリプトを書きます。KickWebService関数の下に、リスト2のTestWebServiceプロシージャを追加します。
[リスト2]テストのためのプロシージャ
Sub TestWebService()
Dim Result As String
Result = KickWebService("station/light", "&name=横浜")
If Result = "" Then
MsgBox "取得失敗!"
Else
MsgBox Result
End If
End Sub
そして、ボタン1_Clickの中に、リスト3の1行を追加します。
[リスト3]ボタン1_ClickにTestWebService呼び出しを追加
Sub ボタン1_Click()
TestWebService '追加
End Sub
ワークシートに戻ってボタンをクリックして、図12のように表示されれば関数はうまく動作しています。「テスト失敗!」でない場合でも、図12のように駅名などが混じった長いXML文字列でなくエラーとか含まれる場合は、呼び出し方に何らかの問題があります。スクリプトが正しく書けているか確認しましょう。
駅コード取得関数を書く
テストがうまくいったら、駅コードの取得スクリプトを書いていきましょう。先ほどのテストスクリプトでは横浜駅の「駅簡易情報」を取得していました。駅コードは、この駅簡易情報の中に入っていますが、XMLデータから探し出してあげる必要があります。リスト4のParseStationCode関数を追加します。
[リスト4]ParseStationCode関数
Private Function ParseStationCode(xmlDom As MSXML2.DOMDocument60) As Integer
Dim ResultSet As MSXML2.IXMLDOMNode, Point As MSXML2.IXMLDOMNode, Station As MSXML2.IXMLDOMNode
Set ResultSet = xmlDom.ChildNodes(1) (1)
Dim i As Integer, j As Integer, k As Integer, Code As Integer
Code = 0
For i = 0 To ResultSet.ChildNodes.Length - 1
Set Point = ResultSet.ChildNodes.Item(i) (2)
For j = 0 To Point.ChildNodes.Length - 1
If Point.ChildNodes.Item(j).nodeName = "Station" Then (3)
Set Station = Point.ChildNodes.Item(j)
For k = 0 To Station.ChildNodes.Length - 1
If Station.ChildNodes(k).nodeName = "Type" Then (4)
If Station.ChildNodes(k).nodeTypedValue = "train" Then
Code = Val(Station.Attributes(0).Text) (5)
Exit For
End If
End If
Next k
End If
Next j
Next i
ParseStationCode = Code
End Function
このスクリプトを理解するには、駅簡易情報のXMLデータの形式を知っておく必要があります。図13のようになっています。
ルート要素であるResultSetの中に、拠点の情報であるPoint要素が複数個含まれるという構造です。Point要素の下には、駅の名前と種類を持つStation要素、そして補助的な情報として県名を持つPrefecture要素が含まれます。これを踏まえてリスト3を見てみると、最初に(1)でDOMツリーからルート要素ResultSetをChildNodesメソッドで取り出し、(2)でResultSetの下のPoint要素をその個数回処理し、さらに(3)でPoint要素の下のStation要素からType要素が「train」すなわち鉄道駅であるものを見つけて(4)、その駅コードを取得しています(5)。駅コードは、要素の値ではなくStation要素の属性に含まれるため、Attributesプロパティから取得しています。XMLのツリーの各要素に的確にアクセスするのは面倒なのですが、図があると迷いにくくなるでしょう。
取得&書き込みスクリプトを書く
駅コードを取得できる下準備ができましたので、ワークシートから駅名を取り出し、取得した駅コードをワークシートに書き込むスクリプトをボタン1_Clickの中に追加します(リスト5)。テストに使用したTestWebServiceプロシージャの呼び出しは不要なので、'でコメントアウトするか削除してください。
[リスト5]駅コード取得&書き込み
Dim Row As Integer
Dim xmlDom As MSXML2.DOMDocument60
Set xmlDom = New MSXML2.DOMDocument60
Row = 4
While Cells(Row, 1) <> "" (1)
Dim StartCode As Integer, DestCode As Integer
xmlDom.LoadXML (KickWebService("station/light", "&name=" + Cells(Row, 1))) (2)
StartCode = ParseStationCode(xmlDom)
Cells(Row, 2) = StartCode
xmlDom.LoadXML (KickWebService("station/light", "&name=" + Cells(Row, 3))) (2)
DestCode = ParseStationCode(xmlDom)
Cells(Row, 4) = DestCode
Row = Row + 1
Wend
この流れは非常に単純です。ワークシートを参照し、この場合は4行目から出発駅のある列を順番に読み取り、空になったらそこで処理を終えます(1)。空でない場合には、出発駅、到着駅の順にWebサービスを呼び出し、その結果から駅コードを取得してワークシートに書き込みます(2)。ここまでの状態でボタンクリックすると、5個のリストに対して駅コードを取得して、図14のように書き込まれます。ここまでで、必要な情報は揃いました。
経路と運賃を取得する
駅コードが取得できましたので、それをもとに経路と運賃を取得します。
経路取得プロシージャを書く
経路は今回の目的ではないのですが、経路の情報に運賃が含まれるので、経路を取得するという形になります。経路も、Webサービスで取得するのは変わりないので、考え方はこれまでと同じです。新たにParseCourseFareというプロシージャを作ります(リスト6)。
[リスト6]ParseCourseFareプロシージャ
Private Sub ParseCourseFare(xmlDom As MSXML2.DOMDocument60, ByRef Oneway As Integer, ByRef Round As Integer) (1)
Dim ResultSet As MSXML2.IXMLDOMNode, Course As MSXML2.IXMLDOMNode, Price As MSXML2.IXMLDOMNode
Set ResultSet = xmlDom.ChildNodes(1)
Dim i As Integer, j As Integer, k As Integer
For i = 0 To ResultSet.ChildNodes.Length - 1
Set Course = ResultSet.ChildNodes.Item(i)
For j = 0 To Course.ChildNodes.Length - 1
If Course.ChildNodes.Item(j).nodeName = "Price" Then
Set Price = Course.ChildNodes.Item(j) (2)
For k = 0 To Price.Attributes.Length - 1
If Price.Attributes.Item(k).nodeName = "kind" Then (3)
If Price.Attributes.Item(k).nodeTypedValue = "FareSummary" Then
Oneway = Val(Price.ChildNodes.Item(0).nodeTypedValue) (4)
Round = Val(Price.ChildNodes.Item(1).nodeTypedValue)
Exit For
End If
End If
Next k
End If
Next j
Next i
End Sub
やや長いですが、やっていることはParseStationCode関数とほぼ同じです。片道運賃と往復運賃という2つの値が欲しいので、関数ではなく参照渡しによるプロシージャとしています(1)。これを理解するために、経路探索のXMLデータの形式を知っておく必要があります。図15のようになっています。
重要なのはPrice要素です(2)。この要素の属性kindがFareSummaryであるものを探しだし(3)、その下にあるOneway要素とRound要素から片道運賃と往復運賃を取得します(4)。
金額をセットするスクリプトを書く
完成は目前です。あとは、経路探索のAPIの呼び出しと、ParseCourseFare関数を呼び出してワークシートに書き込むスクリプトをボタン1_Clickに追加します。リスト7がその内容です。
[リスト7]運賃書き込み
Dim Oneway As Integer, Round As Integer
Oneway = 0
Round = 0
xmlDom.LoadXML (KickWebService("search/course", _
"&from=" & CStr(StartCode) & "&to=" & CStr(DestCode)))
Call ParseCourseFare(xmlDom, Oneway, Round)
Cells(Row, 5) = Oneway
Cells(Row, 6) = Round
これで、冒頭の図1のような状態になれば完成です。駅名をいろいろ変えて試してみて下さい。
まとめ
今回は、「駅すぱあと Webサービス」の無料試用版を使って、Webサービスの呼び出しやXMLの解析の手順を紹介しました。ほとんどがVBAによるスクリプトでしたので難しかったかも知れませんが、ワークシートへのアクセスもデータの処理も自由なので、応用が利くと思います。次回は、JSONのWebサービスの事例として、気象庁の天気情報を取り上げたいと思います。
WINGSプロジェクト 山内直著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中