見出し画像

Selenium+PythonでGoogle map経路検索からmapcodeを抽出する

この記事は、プログラミングど素人の私が、完全独学でpython+seleniumで組んだコードを公開してみようという記事です。我ながらやろうとしていること自体はいいと思うのですが、いかんせんスキルが追い付いていません。
もっとスキルを持った方がコード改造して自分で使うだとか、または(私を含めた)ほかに人に使わせてあげるだとかするぶんには、ご自由になさってください。(というか、そんなことを主張できるほどのものでもないですが。)

動作内容について

概要

Googleマップの自動車による経路案内ページから、目的地のmap codeの一覧をエクセルで出力するものです。

そもそもこんなものをつくった動機

私は旅行先でレンタカーを借りていろいろまわることがあります。いまどきのレンタカーにはカーナビが標準で備わっているので、道に迷うことはほとんどないのですが、展望台やマイナーな施設の場合はナビの検索に苦労することがけっこうな頻度であります。
このときの対処としてもいろいろあり得るのですが、

  • Google mapで電話番号を検索して入力する
    → 目的の施設ではなく市役所観光課の事務所が表示されてしまう

  • 住所を入力する
    → 田舎だと番地が存在せず、同じ字のかなり離れた別の場所が表示されてしまう

という事態に陥ってしまい結局のところ、カーナビの地図画面でそれらしきところを探し出して目的地に設定するという超面倒な方法しかない場合も多いです。

google mapをカーナビとして使うと、狭い道に誘導される

google mapをそのままカーナビとして使うと、本当に容赦なく離合不可能な道に誘導されることが多いのです。このあたり、車に備え付けのカーナビはかなり配慮されていると感じます。
私は自分で車を持っておらず、それほど運転に自信があるわけではありませせんので、多少遠回りになったとしても広くて安全な道を案内してくれるカーナビのほうを使いたいのです。

naviconアプリを使う方法もある(らしい)が、レンタカーでは設定が面倒?

私は使ったことがないのですが、naviconというアプリを使うとスマホで検索した場所をbluetoothで簡単にカーナビに転送できるそうです。これはこれで便利そうではありながらも、レンタカーだと自分のスマホとカーナビの接続が面倒に思えます。

今回作ったコードの動作について

こちらは、コードが動作している際の画面です。

基本的には、

1. Google mapの経路検索のURLをコピペで貼り付ける。

「google map URL」のボックスにURLを貼り付けます

  日付のところは選択してもしなくてもOKです。

2.Seleniumで改めて経路検索ページを開き、目的地の名前、住所、距離、
  所要時間を記録する

経路案内から、出発地、目的地、経由地や所要時間、距離などの情報を取得

3. https://japanmapcode.com/ja にアクセスして、目的地のmapcodeを
  記録する

mapcode情報を取得

4. Googleで、目的地を検索して、サイドパネル情報から電話番号を
  記録する

電話番号を取得。施設によっては電話番号が掲載されていないこともありますが…。

5. 3~4を全経由地、目的地に対して繰り返す

6. エクセルで一覧として出力する

という流れです。

出来上がったエクセル画面

残念ながら大阪駅は、Googleの検索画面のサイドパネルに電話番号が表示されていないので、電話番号は抽出できませんでした。

出来としては「いちおうなんとか動く」といった感じです。

コードについて

全文公開

ダメっぽいところや、私の試行錯誤もわかるように、「print」などの動作には不要なところも含めてそのままです。
webdriverやエクセルファイル保存先のパスだけ、オリジナルから変更しております。

from bs4 import BeautifulSoup
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
import datetime as dt
import time
import tkinter as tk
from tkinter import ttk
from tkcalendar import Calendar, DateEntry
import subprocess
from selenium.webdriver.common.keys import Keys



root = tk.Tk()
root.title("mapcode自動取得")

# URL記入ボックス――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
frame1 = ttk.Labelframe(root, text = "google map URL", padding = 10)
frame1.grid(row=0, column=0, padx=15, pady=15)
urlbox = tk.Entry(frame1, width=70)
urlbox.grid(row=0, column=0)
# カレンダーボックス――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
frame2 = ttk.Labelframe(root, text="日付選択", padding = 10)
frame2.grid(row=1, column=0, padx=15, pady=15)
calen = DateEntry(frame2, locale='ja_JP', showweeknumbers=False, width = "10")
calen.grid(row=0, column=0)
# 時間と分を選択――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
hourchoice = ("0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23")
hourbox = ttk.Combobox(frame2, height=24, justify="center", values=hourchoice, width = "2")
hourbox.grid(row=0, column=1, padx=0)
hourlabel = tk.Label(frame2, text="時", width="0")
hourlabel.grid(row=0, column=2, padx=0)
minchoice = ("0","15","30","45")
minbox = ttk.Combobox(frame2, height=44, justify="center", values=minchoice, width = "2")
minbox.grid(row=0, column=3, padx=0)
minlabel = tk.Label(frame2, text="分", width="0")
minlabel.grid(row=0, column=4, padx=0)


# tkinterはボタン以外完成したところで命令を定義――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――


def mapbtn():

# URLボックスに記入した経路を解析する――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#    try:
    url = urlbox.get()
    url2 = r"https://japanmapcode.com/ja/"
    url3 = r"https://www.google.co.jp/"

    startdate = calen.get_date()
    starthour = hourbox.get()
    startmin = minbox.get()

    root.destroy()

    driver_file = r"C:\Users\example\msedgedriver.exe"
    driver = webdriver.Edge()
    driver.get(url)
    time.sleep(5)

    soup = BeautifulSoup(driver.page_source, "html.parser")

# 目的地一覧――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    dest = soup.find_all("div", class_ = "first-line")
    print(dest)
    dests =[]

    for x in range(0, len(dest)):
        dest[x] = dest[x].text
        dests.append(dest[x])



# 所要時間一覧――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    timeanddist = soup.find_all("div", class_ = "directions-mode-distance-time orkNtc fontBodySmall")

    timeoftheday = []

    for x in range(0, len(timeanddist)): # まずは「1時間2分」などの記載を抽出
        timeoftheday.append((timeanddist[x].text).split("(")[0])

    print(timeoftheday)


    hlist = []
    minlist = []


    for x in range(0, len(timeoftheday)): #「1時間2分」などの記載を、時間と分に分けて数値化
        if "時間" in timeoftheday[x]:
            hour = timeoftheday[x][:timeoftheday[x].find("時間")-1]
            hlist.append(hour)
            min = timeoftheday[x][timeoftheday[x].find("時間")+3:]
            min = min[:min.find("分")-1]
            minlist.append(min)
        else:
            hlist.append("0")
            min = timeoftheday[x][:timeoftheday[x].find("分")-1]
            minlist.append(min)


    #カレンダーの日付と日時を取得して、上記所要時間を足していく―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    today = dt.datetime.today().replace(second=0, microsecond=0)
    if starthour == "" or startmin == "":
        starttime = today
    else:
        starttime = dt.datetime.strptime(str(startdate) + " " +starthour + ":" + startmin, "%Y-%m-%d %H:%M")  # 取得した日時文字列を、datetime型に変換

    addtime = [starttime]
    for x in range(0, len(hlist)): # dt.timedelta使って、所要時間を足して言って時刻化する
        addtime.append(addtime[x] + dt.timedelta(hours=int(hlist[x]), minutes=int(minlist[x])))
    for x in range(0, len(addtime)): # datetime型を、分までの文字列化する
        addtime[x] = addtime[x].strftime("%Y/%m/%d %H:%M")
    timeoftheday.append("ー")

    print(timeoftheday)

    #距離を取得―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    distance = []

    for x in range(0, len(timeanddist)): # まずは「3.8km」「800m」などの記載を抽出
        distance.append(((timeanddist[x].text).split("(")[1])[:-1])


    for x in range(0, len(distance)): # 距離の単位により、kmに合わせる
        if "km" in distance[x]:
            distance[x] = distance[x][:distance[x].find("km")-1]
        else:
            distance[x] = "0." + distance[x][:distance[x].find("km")-1]
    distance.append("ー")


    print(distance)



    #住所だけとか目的地だけだと、Drive!Nipponでうまく検索できないので、目的地と住所をつないだ文字列作って検索ワードとする―――――――――――――――――――――――――――――――――――――――
    poslist = soup.find_all("div", class_ = "second-line fontBodyMedium")

    posanddest = []
    for x in range(0, len(poslist)): # 距離の単位により、kmに合わせる
        posanddest.append((poslist[x].text + dests[x])[10:])

    print(posanddest)

    #web上にて、上記目的地と住所をつないだ単語を使ってマップコードを取得する―――――――――――――――――――――――――――――――――――――――
    mapcode=[]
    tel=[]
    for x in range(0, len(posanddest)):
        driver.get(url2)
        time.sleep(5)
        address = driver.find_element(By.ID, "search-box")
        address.clear()
        address.send_keys(posanddest[x])
        time.sleep(4)
        address.send_keys(Keys.ARROW_DOWN)
        time.sleep(2)
        address.send_keys(Keys.ENTER)
        time.sleep(5)
        soup = BeautifulSoup(driver.page_source, "html.parser")
        mapcode.append((soup.find("span", id = "place-mapcode-value")).text)
        print(mapcode)
        time.sleep(2)
        driver.get(url3)
        searchwindow = driver.find_element(By.ID, "APjFqb")
        searchwindow.send_keys(posanddest[x])
        searchwindow.send_keys(Keys.ENTER)
        time.sleep(5)
        soup = BeautifulSoup(driver.page_source, "html.parser")
        print(soup.find_all("div", class_ = "rllt__details"))
        print(soup.find_all("span", class_ = "LrzXr zdqRlf kno-fv"))
        if len(soup.find_all("div", class_ = "rllt__details")) != 0:
            telnumlist = (soup.find("div", class_ = "rllt__details").text).split("·")
            print(telnumlist)
            telnum = telnumlist[-1][1:13]
            print(telnum)
            tel.append(telnum)
        elif len(soup.find_all("span", class_ = "LrzXr zdqRlf kno-fv")) != 0:
            print((soup.find("span", class_ = "LrzXr zdqRlf kno-fv")).text)
            tel.append((soup.find("span", class_ = "LrzXr zdqRlf kno-fv")).text)
        else:
            tel.append("ー")



    df_map = pd.DataFrame({"目的地": [], "到着時刻": [], "mapcode": [], "距離": [], "時間": [], "電話番号": []})
    df_map["目的地"] = dests
    df_map["到着時刻"] = timeoftheday
    df_map["mapcode"] = mapcode
    df_map["距離"] = distance
    df_map["時間"] = addtime
    df_map["電話番号"] = tel

    strtoday = (str(today).replace("/", "-"))[0:10]



    with pd.ExcelWriter(r"C:\Users\exsample\保存用" + "\\" + strtoday + ".xlsx",engine='openpyxl') as writer:
        df_map.to_excel(writer, sheet_name="test")
    subprocess.run(['start', r"C:\Users\exsample\保存用" + "\\" + strtoday + ".xlsx"], shell=True)


print_button = tk.Button(frame2, text="取得", command=mapbtn)
print_button.grid(row=1, column=0)

root.mainloop()

tkinterとtkcalenderを使って、なんとかGUIをつくろうとしています。
seleniumはheadlessにしてもいいのですが、そうするとエラー起きたときに何が起きたのかわかりづらいので、そのままにしています。
本当は、どこで何をやりたいか懇切丁寧に書けばいいのですが、疲れてきたのでこのまま載せるだけにします。大した内容ではないので、詳しい人が読んだらすぐに解読できる程度のものだと思います。map code検索時の検索ワードに住所と目的地名称をつなげたものを入力するなど、細かい工夫はいろいろしているのですが、全体的にダサダサです。

ダメなところ

  • 例外処理が一切ないので、何か予期せぬことが起きたら止まるのみ

  • 困ったときは全部time.sleep。もうちょっとスマートなやり方したい…。

  • ほんまにこの程度の内容で、こんなややこしいことしなきゃならないのか?

  • 動作にとにかく時間がかかる。

まとめ

Google mapからmap code一覧をつくるというアイデア自体は良いと思うのですが、いかんせん私の能力が低すぎて「ギリギリ動く」程度にとどまっています。もうちょっとなんとかならんものか…。

この記事が気に入ったらサポートをしてみませんか?