日本の住所処理で鼻血を出さないために

こちらの記事「[日本の住所の正規化に本気で取り組んでみたら大変すぎて鼻血が出た。](https://qiita.com/miya0001/items/598070abcdf0799daebc)」を読み、実案件で住所を扱うシステムの構築・運用や住所を含むデータ分析など行っていた頃の思い出と重なり、もらい鼻血を出しそうになったので記事にしました。

色々書いていますが、要は特殊な地名に起因する辛さ以外にもこんなに大変なことがあるということです。
大変なので次の２点をまずは検討すると幸せになれるかもしれません、という記事です。

 - 緯度経度への変換が必要ならまずはGoogleかYahooのAPIを検討
 - 大量に処理する必要があるなら都道府県や市区町村などの粒度でサマライズし、個別の地点取得はなるべく避ける

前出の記事は住所コードへの突合を目的としている様子なので、私が経験したことのある案件、例えば会員ページで住所情報の入力を受けてDBへ格納するとか、データ分析案件で顧客データの住所フィールドで色々やるとかという場合とは状況が違う点は前置きしておきます。

住所処理を真面目にやるとヤバイというのは定番の冗談だと思っていました。鼻を殴ったら鼻血が出るのはしかたありません。工数に入れる場合は慎重になる必要があります。

# 入力には制約が必要というはなし

まず、住所に限らずユーザからの入力を何らかの処理で使いたいとき、制約無し＝自然言語処理となるの必然です。それは郵便番号でも電話番号でも「応援している野球チームを記入してください」でもなんでもそうです。

# 住所のルールは変化します

住所にまつわる決まりごとは一般的な市区町村や政令指定都市のルールであればある程度把握できそうですが、細かい統廃合や区分の変更が随時発生し、特に例外への決め打ち対応が困難です。国に２つめのキャピタル（都）を置きたいと考えている政党がいるくらいです、いつ東京が府に戻るかしれません。

# 住所文字列で正規表現の腕試しを行うのは止めた方が良い

住所処理に挑む若手エンジニアはまず正規表現でなんとかしようと考えるのではないでしょうか。幸い正規表現の知識不足のために早々にその試みが無謀だと気づくのですが、不幸にもベテランがこれに挑むと誰にもメンテナンスできない記号の羅列が何行もエディタの表示領域を専有するはめになります。そして、対応できないルールが見つかり、面倒なので書き直そうという気持ちになり最初に戻ります。エンジニアは「完璧か？」と聞かれた際のうまい返し方をいくつもストックしていると思いますが（悲しいことに、それらの多くは非エンジニアを不愉快にさせるものという印象がありますが）、論理的な意味ではなく、現実的にもこのタスクは完璧になりえないと思います。

# ルール以前のはなし

色々書いてきましたが、それ以前のもっとアナログな問題も悩みの種となります。

### 都道府県を省略する人がいる

年配の方に多い気がしますが、そもそも都道府県名を省略する方がいます。

### 都道府県名が重複している人がいる

一方、結果の住所の都道府県名が重複している人がいます。
こらは、既に選択肢で都道府県を選択しているのに、自由入力の市区町村欄に再度入力しているケースです。

### 大字（おおあざ）の文字を住所に含めるか否か

一般的な話として「大字（おおあざ）」の文字を住所表記に入れるか入れないかは人によってポリシーが違います。
（チェックしていると「この『大字』って町名ですか？」というケースが出てきます。正式には入れないというルールになっているようですが）
そのことを気にしてカッコをつけて入力するなど配慮している人もいます。

### 全部入力してくれない

プライバシーを気にする方に多いと思いますが丁目以降を省略して入力する人も多いです。

### マンションの表記が特殊

マンション名などは住所の最後に入力すると思いますが、マンション名に地名が含まれていたり、変わった名前だったりすると正規化を試みる際に邪魔になるケースがあります。またマンション名を省略して番地に続けて部屋番号を入力する人も多いですし、視認性を気にしてかハイフンやピリオドを使い分けている方もいます。

### 宇宙人問題

若年者も利用するようなサービスでは「地球」とか「俺の部屋」などとふざけてみたり、好きなアニメだか漫画に出てくる架空の地名を入れたりと別の次元に突入するだけでなく、多分急いでいたのか自分の名前が入っていたりとマトモな心では対応できないものが必ずあります。

### 何かのメッセージが入っている

２世帯住宅に住んでいたりと特殊な事情がある場合に、住所欄にメッセージが入っていることがあります。

### 普通の表記揺れ

もちろん、ひらがな・カタカナなどの表記ゆれだけでなく、誤変換のリスクも大いに存在します。

# そもそも厳密な処理が必要か考える

そもそも、何丁目レベルの精度が必要なケースか検討する必要もあります。
必要書類の郵送を伴うなどクリティカルなものは目検を外せないと思うので、システム部門の仕事としては、せいぜい振り分けくらいで充分なケースもあります。

# 緯度経度への変換が必要な場合、GoogleかYahooのAPIを検討

例えば、住所文字列から緯度経度を取得して地図上にピンを配置する。という案件を考えた場合、GoogleやYahooの提供しているAPIを使うのが第一候補と思います。また、APIの結果として、緯度経度だけでなく都道府県や市区町村の判定結果も含まれるので構造化の目的にも使用できます。

### Google Geocoding API

Geocoding APIを使うとGoogleの強力な言語処理が解釈した結果として入力文章を住所として解釈し、緯度経度情報を含む住所データを得ることができます。Google Mapはかなり適当な入力でもポイントしてくれますが、おそらく同じレベルと感じます。使い方も簡単でGCPアカウントを作成しAPIの認証キーを取得すれば次のコードで利用できます。

前出の記事で「最強のボスキャラ」として登場していた京都の住所でやってみます。

（マジレスしてしまうと、住所に限らずこういった難易度の高いケースが複数想定される処理に正面から取り組んだ時点で負けています。個別対応は論外ですが汎用化の工数と複雑さが級数的に増加します。完璧な翻訳システムとか、間違えない採点システムと同様に「ラストワンマイル」に阻まれる可能性が高いです。）

```python
import json
import requests

GOOGL_GEOCODING_API_KEY = "キー"

address = "京都府京都市東山区大和大路通正面下る大和大路2"

base_url = "https://maps.googleapis.com/maps/api/geocode/json"  
url = "{}?components=country:JP&address={}&key={}".format(base_url, address, GOOGL_GEOCODING_API_KEY)  
response = requests.get(url)  
dic = json.loads(response.text)

```

上記を実行すると、dicの中身は次のようになります。

```json
{'results': [{'access_points': [],
   'address_components': [{'long_name': '大和大路通正面下る',
     'short_name': '大和大路通正面下る',
     'types': ['neighborhood', 'political']},
    {'long_name': '大和大路二丁目',
     'short_name': '大和大路二丁目',
     'types': ['political', 'sublocality', 'sublocality_level_2']},
    {'long_name': '東山区',
     'short_name': '東山区',
     'types': ['political', 'sublocality', 'sublocality_level_1']},
    {'long_name': '京都市',
     'short_name': '京都市',
     'types': ['locality', 'political']},
    {'long_name': '京都府',
     'short_name': '京都府',
     'types': ['administrative_area_level_1', 'political']},
    {'long_name': 'Japan',
     'short_name': 'JP',
     'types': ['country', 'political']},
    {'long_name': '605-0933',
     'short_name': '605-0933',
     'types': ['postal_code']}],
   'formatted_address': 'Japan, 〒605-0933 京都府京都市東山区大和大路二丁目 大和大路通正面下る',
   'geometry': {'location': {'lat': 34.99006370000001, 'lng': 135.7708268},
    'location_type': 'APPROXIMATE',
    'viewport': {'northeast': {'lat': 34.9992044, 'lng': 135.7868342},
     'southwest': {'lat': 34.980922, 'lng': 135.7548194}}},
   'place_id': 'ChIJHaYV_rUIAWARkKaVOimOW9U',
   'types': ['neighborhood', 'political']}],
 'status': 'OK'}
```

googleのGeocoding APIのデメリットとしては

- 有料(最大40,000件/月まで無料、それ以降は$5/1,000件、詳細は[こちら](https://cloud.google.com/maps-platform/pricing/sheet?hl=ja))
- API経由なので処理に多少の時間がかかる
- 意図しない地点の情報を返す場合がある
- 構造化された住所も取得できるのですが、JPを指定しても英語表記の場合がある（この点YahooのAPIは丁寧だった印象です）

### YahooのAPI (YOLP)

Yahooのlocation系のAPIであるYOLPでも同じように住所文字列から各種情報を得ることができます。数年前にGoogle Geocoding APIが整備される前はこちらを利用していました。APIのアクセス間隔への要件が厳しく大量に取得する際に時間がかかった記憶があります。商用でなければ無料だった認識ですが使う場合は確認してください。郵便番号から住所、緯度経度から住所など取得できます。

https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/geocoder.html

### APIを使うにあたって

上記APIはかなりの揺れも強引に解釈して緯度経度を返すので、特にデータ分析の場合に便利なのですが、前出の「俺の部屋」といった文字列に大してはこの名前のラーメン屋の所在地など、おかしな値を返します。なので必要であれば前処理でなんとかする必要があります。

# 大量に処理したい場合

レポートとして大量の住所をヒートマップで可視化したい場合など、APIでやるのはちょっと重いです。
都道府県や市区町村レベルであれば県庁所在地や役所の住所が各所で手に入るので、こちらに名寄せするアイディアがあります。特に顧客のヒートマップを日本地図上に表示する場合などは細かい実際の住所でやるよりも負荷が軽くて良いです。