【ExcelVBA】Yahoo!地図からルート検索【SeleniumBasic】

昨日の記事の続きです。

Yahoo!地図のルート検索を、SeleniumBasicもWebdriverもなしにExcelVBAだけでアクセスして情報をゲットするのは難しいという話を、お問い合わせをいただいた方にメールしたところ、職場でSeleniumBasic等のダウンロードが申請すれば許可されるかもしれないということでした。

SeleniumBasicとWebdriverがダウンロードできるのであれば、もう何も困ることはありません。

ということで今回は、Yahoo!地図のルート検索の距離を、SeleniumBasicを使って取得する方法を紹介します。

仕様

Yahoo!地図でルート検索すると、候補が3件表示されます。

東京駅から高崎駅までを検索したところ。候補が3件表示される。

今回のその3件の距離数を把握したいとのことでした。

さらに、出発地点と到着地点を入れ替えたときの3件の距離数も把握したいとのこと。

ワークシートの体裁はこんな感じになります。

ワークシート

シート名に特に制約はありませんが、先頭にあるシートを使うことにしました。

使うブラウザはChromeかEdgeで、どちらか使いやすい方でよいとのことでしたので、Chromeにしました。

環境

Windows10

MicrosoftOffice365

Chromeバージョン114.0.5735.110(Official Build) (64 ビット)

ChromeのWebdriver(chromedriver.exe)はブラウザに合わせたものをダウンロードしてあります。

VBEの参照設定で、下記のとおりSelenium Type Libraryにチェックをいれてあります。

参照設定

コード

私が考えたコードはこんな感じです。

Option Explicit
'ワークシートに表示する情報のヘッダ
Enum e
    出発地 = 1
    目的地
    往路距離1
    往路距離2
    往路距離3
    復路距離1
    復路距離2
    復路距離3
    TheEnd = 復路距離3
End Enum
'
'作成:野口香
'作成:2023/06/14
'
Public Sub GetMap()
    '作業用シート
    Dim sh As Worksheet
    Set sh = ThisWorkbook.Worksheets(1)
    
    'Seleniumを用意
    Dim driver As Object
    Set driver = CreateObject("Selenium.WebDriver")
    
    'スクレイピングのお作法・・・wait時間
    Const clWAIT As Long = 500
    
    'Chromeをスタート
    driver.Start "Chrome"
    
    'ブラウザを最大サイズに
    driver.Window.Maximize
    
    '前回までの情報をクリアしておく
    Const clHeader As Long = 6
    sh.Range(sh.Cells(clHeader + 1, e.往路距離1), sh.Cells(sh.Rows.Count, e.TheEnd)).ClearContents
    
    'Yahoo!地図のURL
    Const cstUrl As String = "https://map.yahoo.co.jp/route/car"
    driver.Get cstUrl
    driver.Wait clWAIT * 15 'けっこう長めに待たないとだめみたいだ・・・①
    
    'Loop開始前、準備
    'ワークシート上の調査対象件数が何件か把握しておく
    Dim ii As Long, lRow As Long
    Const cstStart As Long = clHeader + 1 'データが入っている行の最初の行の行番号
    
    lRow = sh.Cells(cstStart, e.目的地).End(xlDown).Row
    If lRow = sh.Rows.Count Then
        lRow = cstStart
    End If
    
    '出発地
    Dim el As Object
    Dim myBy As By
    Set myBy = New By
    Dim Keys As Keys
    Set Keys = New Keys
    
    For ii = cstStart To lRow
    
        '「スタート地点の入力を削除」ボタンが存在するなら・・・②
        If driver.IsElementPresent(myBy.XPath("//*[@id=""search_route_page""]/div[1]/div[1]/div/div[1]/div[1]/div/button")) Then
            '「スタート地点の入力を削除」ボタンを押す(これをやっておかないとInputボックスの中身がクリアされなかった) ・・・②
            driver.FindElementByXPath("//*[@id=""search_route_page""]/div[1]/div[1]/div/div[1]/div[1]/div/button").Click
            driver.Wait clWAIT
        End If
        
        '出発地
        Set el = driver.FindElementByXPath("//*[@id=""route_start""]")
        el.Clear
        driver.Wait clWAIT
        
        el.SendKeys sh.Cells(ii, e.出発地).Value
        driver.Wait clWAIT
        
        el.SendKeys Keys.Tab    'ここでタブキーを押すのは、これをやっておかないと出発地点の候補がリスト表示されてしまい、次の目的地のエレメントに覆いかぶさってしまい、
        '「そんなエレメントは見つからない」というエラーになってしまうため。・・・③
        driver.Wait clWAIT
        
        '「ゴール地点の入力を削除」ボタンを押す(これをやっておかないとInputボックスの中身がクリアされなかった)
        If driver.IsElementPresent(myBy.XPath("//*[@id=""search_route_page""]/div[1]/div[1]/div/div[1]/div[2]/div/button")) Then
            driver.FindElementByXPath("//*[@id=""search_route_page""]/div[1]/div[1]/div/div[1]/div[2]/div/button").Click
            driver.Wait clWAIT
        End If
        
        '目的地
        Set el = driver.FindElementByXPath("//*[@id=""route_goal""]")
        el.Clear
        driver.Wait clWAIT
        
        el.SendKeys sh.Cells(ii, e.目的地).Value
        driver.Wait clWAIT
        
        '「ルートを検索」ボタンをクリック
        driver.FindElementByXPath("//*[@id=""search_route_page""]/div/div[2]/button").Click
        driver.Wait clWAIT * 10 '少し長めに待つ
        
        '往路の距離を取得する
        Call GetKM("往路", sh, ii, driver, clWAIT)
        
        '「スタートとゴールを入れ替え」ボタンを押す
        driver.FindElementByXPath("//*[@id=""search_route_page""]/div[1]/div[1]/div/div[2]/div/button").Click
        driver.Wait clWAIT * 10 '少し長めに待つ
        
        '復路の距離を取得する
        Call GetKM("復路", sh, ii, driver, clWAIT)
        
    Next ii
    '終了
    driver.Close
    
    'オブジェクト解放
    Set sh = Nothing
    Set driver = Nothing
    Set el = Nothing
    
    '終了メッセージ
    MsgBox "終了"
    
End Sub

Private Sub GetKM(ByVal st路, ByRef sh As Worksheet, ByRef ii As Long, ByRef driver As Object, ByRef clWAIT As Long)
    Dim lColumn As Long
    
    If st路 = "往路" Then
        lColumn = e.往路距離1
    Else
        lColumn = e.復路距離1
    End If
    
    '距離1を取得
    sh.Cells(ii, lColumn).Value = Replace(driver.FindElementByXPath("//*[@id=""search_route_page""]/div[2]/ul/li[1]/button/div/div[1]/span").Text, "-", "")
    driver.Wait clWAIT
    
    '距離2を取得
    sh.Cells(ii, lColumn + 1).Value = Replace(driver.FindElementByXPath("//*[@id=""search_route_page""]/div[2]/ul/li[2]/button/div/div[1]/span").Text, "-", "")
    driver.Wait clWAIT
    
    '距離3を取得
    sh.Cells(ii, lColumn + 2).Value = Replace(driver.FindElementByXPath("//*[@id=""search_route_page""]/div[2]/ul/li[3]/button/div/div[1]/span").Text, "-", "")
    driver.Wait clWAIT

End Sub

解説

順に説明します。

①長めに待つ

スクレイピングではネットワークを介してサーバにアクセスする動作が相手方サーバに迷惑をかけないよう、わざとWait時間をあちこちで設けているものなんですが、今回Yahoo!地図にアクセスした直後のWait時間は相手方サーバに迷惑をかけないためというより、読み込みに時間がかかるためにかなり長めにしました。

でないと、なかなかすべての要素が表示されなかったです。

すべての要素が表示されきる前に次のステップに進んでしまうと、「そんな要素はない」とVBAエラーになってしまうのです。

この辺のWait時間はネットワーク環境にもよるものがあると思うので、お使いの環境でチューンナップしてください。

②スタート地点の入力を削除

スタート地点の入力を削除とはどういうことかというと、最初このIf文は書いていませんでした。

ところがデバッグ中に、次のようなエラーが発生しました。

デバッグ中に発生したエラー

ん?なんじゃこりゃ?

検索対象となったものがすべて目的地に入ってしまっているようです。

ゴールのインプットボックスにカーソルを置いて確かめたところ、

517の次に群の字が見えている

確かに、そこまでに検索していたすべての目的地が、一つのインプットボックスに書かれていました。

コードにはインプットボックスの中身をクリアコンテンツするための

el.Clear

というステップを用意しておいたのですが、うまくいっていないようです。

サイトをよく見ると、×のマークがボックスの右側にあります。

このボタンを押さないと、インプットボックスの中身がきちんとクリアされないようです。

なおこの×ボタンは、1回目の入力のときには存在しません。

1回目の入力前のサイト画面。×ボタンが表示されていない。

何らかの入力があると、×ボタンが表示されます。

最初の入力が終わったときのサイト画面。×ボタンが表示された。

そのためIf文で×ボタンの要素が存在するかどうか調べています。要素が存在するかどうかIsElementPresentを使っています。

ゴール地点の入力を削除するボタンも同様に制御しています。

③タブキーを押す

出発地点の入力を終えた後、タブキーを1回押しています。

これはなぜかというと、次のような不具合が起きないようにするためです。

スタート地点の入力が終わったところ。ゴール地点の×ボタンが見えていないが、プログラムからはそんなことは分かりようがないのでIf文で調べようとする。
ゴール地点の要素が覆われてしまった状態

要素は可視である必要があります(人間が見てちゃんと見える状態)。このように要素が覆われてしまうと、「その要素は可視状態ではない」とエラーになります。

ということで、Tabキーを1回押してインプットボックスに入っているカーソルをボックスの外に出しています。

これにより、候補のリストが表示されるのを避けることができます。

④GetKM

距離については往路と復路とで同じ要素を参照します。

同じ要素をだらだらとコード内に重複して書きたくなかったのでGetKMというサブルーチンにしました。

引数は第1引数以外はすべてRef参照(呼び出し元と共有)としました。

まとめ

以上、Yahoo!地図から情報を取得する方法について紹介しました。

なお、Yahoo!地図の利用規約を確認したところ、スクレイピングを禁止する規程はみつけることができませんでしたが、「合理的に必要相当な数を超える利用、乱用」は禁止されていましたので、自動実行する際は必要最小限のアクセスとなるよう、お気を付けください。また、Wait時間は長めにとるのが無難でしょう。

本日作ったものをZIPにしておいておきます。

https://kn-sharoushi.com/wp-content/uploads/2023/06/20230614GetYahooMapBySeleniumBasic.zip