Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

csvファイルにまとめた住所情報から緯度・経度リスト出力(csv出力)

先日、国土地理院APIで住所から緯度・経度を取得する方法をご紹介しました。
elsammit-beginnerblg.hatenablog.com

こちらの記事の最後にcsvファイルからまとめた住所データから緯度・経度を出力するコードを載せたのですが、
今回はこちらのコードについてブログにてご紹介していきたいと思います。
f:id:Elsammit:20210711122900p:plain



■環境

前回の記事と同様に今回も使用するのはpythonです。
pythonのバージョンはPython 3.7.3です。

また今回、csvファイルの読み込み・書き込みを行う関係でcsvライブラリを用います。
未インストールの方はこちらでインストールを行ってください。

pip3 install python-csv

ライブラリは他に前回と同様にurllib、requestsを用いますので、

pip3 install urllib3
pip3 install requests

でインストールしておいてください。

■条件

今回想定するcsvはこちらのように、
1行目をヘッダー、2行目以降にデータが格納された構成にします。
要素は住所が存在すればOKです。

id,店名,住所
1,札幌駅,北海道札幌市北区北6条西4丁目
2,東京駅,東京都千代田区丸の内1丁目
3,蒲郡駅,愛知県蒲郡市元町1

■コード紹介

ではcsvファイルから住所情報を読み出し、緯度・経度をcsv出力するコードを紹介していきます。
全体のコードはこちらです。

# -*- coding: utf-8 -*-

import csv
import sys
import urllib.parse # pip3 install urllib3
import json         # https://note.com/masato1230/n/nba86746179ca
import requests     # pip3 install requests
import time
import os

def main():
    args = sys.argv     # 実行時の引数取得
    if len(args) < 2:   # 実行時にcsvファイルを指定していなかった場合はエラーとする
        print("[Error] csvファイルを引数に与えてください")
        return

    if os.path.exists(args[1]) == False:        # 指定したファイルが存在しない場合にはエラーとする
        print("[Error] 指定したファイルがみつかりませんでした")
        return        

    fileName = os.path.splitext(args[1])        # 指定したファイルがcsvファイルでなければエラーとする
    if fileName[1] != ".csv":
        print("拡張子はcsvファイルにしてください")
        return

    i = 0               # ヘッダーとbody切り替え用
    AddressNum = -1     # 住所 or Addressが書かれた番号
    MapLists = []       # csvから読み出しかつ緯度・経度を追加する用変数

    # 国土地理院URL
    # APIの使用方法等は下記を参考のこと
    # https://libraries.io/github/gsi-cyberjapan/internal-search
    makeUrl = "https://msearch.gsi.go.jp/address-search/AddressSearch?q="    
    with open(args[1],encoding='utf-8') as f:   # 引数に与えたcsvファイル読み込み
        reader = csv.reader(f)
        for line in reader:                     # 各行読み取り
            buf = []
            if i == 0:                          # ヘッダーの場合
                j = 0                           # 列番号カウント用変数
                for item in line:               # 列読み取り
                    if item == "住所" or item == "Address" or item == "address":
                        AddressNum = j          # 住所を見つけたらその時の番号を変数に格納
                    j+=1                        # 列番号カウンタをインクリメント
                    buf.append(item)            # 取得した列をbufに格納
                if AddressNum < 0:              # 住所やアドレスがなかったらエラーとして修了
                    print("[Error] 住所もしくはAddressが項目にありません")
                    return

                buf.append("latitude")          # ヘッダーの列にlatitudeを追加
                buf.append("longitude")         # ヘッダーの列にlongitudeを追加
                MapLists.append(buf)            # ヘッダー行をMapListsに格納
            else:                               # body側の各行情報
                for item in line:               # 各行ごとのカラムを取得
                    buf.append(item)            # 取得したカラムを変数に格納
                print(line[AddressNum])
                s_quote = urllib.parse.quote(line[AddressNum])          # 住所の文字列をURLエンコード
                response = response = requests.get(makeUrl + s_quote)   # エンコードした文字列を国土地理院APIの引数として与えてget request
                if response.json() == []:                               # レスポンスされたjsonデータの中身を確認し空だったら
                    print("[Error] 住所がよくわかりませんでした")          # 判定できなかった旨を出力し緯度・経度は空文字を格納
                    buf.append("")                                      
                    buf.append("")                    
                elif len(response.json()) >1:                           # 候補が複数あった場合、判定出来ないためスキップ
                    print("[Error] 住所の絞り込みが出来ず複数候補が出ました \n 住所の絞り込みをおこなってください ")
                    buf.append("")                                      
                    buf.append("") 
                else:                                                   # レスポンスされたjsonデータが空でなかった場合
                    print(response.json()[0]["geometry"]["coordinates"]) 
                    buf.append(response.json()[0]["geometry"]["coordinates"][0])    # 緯度情報をbufに格納
                    buf.append(response.json()[0]["geometry"]["coordinates"][1])    # 経度情報をbufに格納
                MapLists.append(buf)            # 各行ごとの情報をMapListsに格納                                    
                time.sleep(1)                   # 国土地理院APIに負荷を掛けないように
            i+=1

    with open("output.csv", mode='w',newline='',encoding='shift_jis') as f: # データをcsvファイルへ格納
        writer = csv.writer(f)
        for maplist in MapLists:                # 各行の情報を格納した変数を読み出す
            writer.writerow(maplist)            # 読み出したデータをcsvへ書き込み
    print("Write Finish !!")
            
if __name__ == "__main__":
    main()

まず、csvファイルからデータを読み出し、reader変数に格納します。

    with open(args[1],encoding='utf-8') as f:   # 引数に与えたcsvファイル読み込み
        reader = csv.reader(f)

次に、

for line in reader:                     # 各行読み取り
~~~
    for item in line:               # 列読み取り

にて読み出したデータを1行ごとに分割さらに各列でパースしていきます。

最初はヘッダーになるので、ヘッダー名に"住所"要素がある列番号をセットします。

for item in line:               # 列読み取り
    if item == "住所" or item == "Address" or item == "address":
    AddressNum = j          # 住所を見つけたらその時の番号を変数に格納
    j+=1                        # 列番号カウンタをインクリメント
    buf.append(item)            # 取得した列をbufに格納

後は前回と同様に住所データを国土地理院APIに渡して緯度・経度を取得すればOKです。

for item in line:               # 各行ごとのカラムを取得
    buf.append(item)            # 取得したカラムを変数に格納
	s_quote = urllib.parse.quote(line[AddressNum])          # 住所の文字列をURLエンコード
	response = response = requests.get(makeUrl + s_quote)   # エンコードした文字列を国土地理院APIの引数として与えてget request
	
	buf.append(response.json()[0]["geometry"]["coordinates"][0])    # 緯度情報をbufに格納
   	buf.append(response.json()[0]["geometry"]["coordinates"][1])    # 経度情報をbufに格納
 	MapLists.append(buf)            # 各行ごとの情報をMapListsに格納                                    
	time.sleep(1)                   # 国土地理院APIに負荷を掛けないように

AddressNumにはヘッダーで検索した住所列番号が格納されているので、line[AddressNum]には各住所情報が格納されております。
ですので、line[AddressNum]をurllib.parse.quoteによりURLエンコードしてAPIに渡して緯度・経度を取得しています。

そして、MapListsにデータを格納。
MapListsは最後にcsv出力するためのデータ群が格納されます。

データを読み出して緯度・経度を取得⇒MapListsに格納まで出来たので最後にcsvファイル出力です。
コードはこちら。

with open("output.csv", mode='w',newline='',encoding='shift_jis') as f: # データをcsvファイルへ格納
    writer = csv.writer(f)
    for maplist in MapLists:                # 各行の情報を格納した変数を読み出す
        writer.writerow(maplist)            # 読み出したデータをcsvへ書き込み

MapListsを1行ごとに分割しwriter.writerowで1行ずつcsvファイルに書き込み。

■コードの使い方

本コードは下記の通り実行すれば引数として渡したcsvファイルから読み取り、output.csvが出力されます。

python3 ファイル名.py csvファイル名.csv

APIに負担を掛けないように1秒ごとにwaitを入れ込んでいます。
この関係で複数住所データが格納されていると少し時間がかかってしまいます。
コーヒーを飲んでゆっくり待ってください。

■最後に

今回はcsvファイルにまとめた住所情報から緯度・経度を算出しcsv出力するコードをご紹介しました。
こちらにcsvファイルにまとめた住所データから緯度・経度を算出するコードを格納しておりますので、
よろしければご覧ください。
https://github.com/Elsammit/SearchAddressToMapInfo


■参考
https://pypi.org/project/python-csv/
https://qiita.com/motoki1990/items/0274d8bcf1a97fe4a869