今回はRubyで郵便番号から住所の都道府県、市、町名などを調べるスクリプトとRailsアプリを作成してみました。
概要
データを手に入れる
日本郵便のサイトで郵便番号のデータは無料でダウンロードできます。
「読み仮名データの促音・拗音を小書きで表記するもの 全国一括」というファイルが必要なので、以下のリンクでダウンロードして展開してください。
http://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip
データを読み込む
展開した「KEN_ALL.csv]は以下の形式です。3つ目の欄は郵便番号、7つ目は都道府県、8つ目は市など、9つ目は町名などです。それ以外は使いませんが、興味があれば日本郵便のサイトに説明があります。
01101,"060 ","0600000","ホッカイドウ", "サッポロシチュウオウク", "イカニケイサイガナイバアイ", "北海道", "札幌市中央区", "以下に掲載がない場合",0,0,0,0,0,0
01101,"064 ","0640941","ホッカイドウ", "サッポロシチュウオウク", "アサヒガオカ", "北海道", "札幌市中央区", "旭ケ丘",0,0,1,0,0,0
01101,"060 ","0600041","ホッカイドウ", "サッポロシチュウオウク", "オオドオリヒガシ", "北海道", "札幌市中央区", "大通東 ",0,0,1,0,0,0
01101,"060 ","0600042","ホッカイドウ", "サッポロシチュウオウク", "オオドオリニシ(1-19チョウメ)", "北海道", "札幌市中央区", "大通西(1〜19丁目)",1,0,1,0,0,0
...
ファイルのエンコーディングはShift_JISなので、エディタはサポートしていない場合、文字化けになるかもしれません。
まず、新しいRubyのスクリプトを作成して、データを読み込みます。
# encoding: utf-8
require 'csv'
class PostCodeIndex
def initialize(file_name)
@data = {}
CSV.foreach(file, encoding: 'Shift_JIS:UTF-8') do |row|
post_code, city, prefecture, street = row[2], row[6], row[7], row[8]
@data[post_code] = {
post_code: post_code,
prefecture: prefecture,
city: city,
street: clean_street(street)
}
end
end
private
def clean_street(input)
return '' if input == '以下に掲載がない場合'
input.sub(/(.*$/, '')
end
end
RubyのCSVライブラリーはファイルの内容を読み込んで各行を配列にします。そして、encoding: 'Shift_JIS:UTF-8'」
を与えると、データのエンコードイングはShift_JISからUTF-8になります。
CSVのブロックで使う欄をハッシュに与えます。郵便番号をキーにして作ったハッシュを@data
のハッシュに与えます。
郵便番号によっては町名がなくて「以下に掲載がない場合」が書いてある場合や、町名があっても「(1-19チョウメ)」のような括弧が付いている場合もあります。このような場合はユーザーに見せたくないテキストを消して町名を正規化するために、clean_street
のメソッドを作りました。
住所を調べる
読み込んだデータを使うために2つのメソッドを追加します。String#tr
についてわからない方はこちら。
# encoding: utf-8
require 'csv'
class PostCodeIndex
# ...
def lookup(post_code)
@data[clean_post_code(post_code)]
end
private
# ...
def clean_post_code(input)
input.to_s.tr('0-9', '0-9').gsub(/[^0-9]/, '')
end
end
これで以下のように郵便番号から住所の都道府県、市、町名が調べる事ができます。
index = PostCodeIndex.new('KEN_ALL.csv')
# 記号があってもなくても一緒
index.lookup('140-0002') # => {:post_code=>"1400002", :prefecture=>"品川区", :city=>"東京都", :street=>"東品川"}
# 文字列値でも整数値でも構わない
index.lookup(4180112) # => {:post_code=>"4180112", :prefecture=>"富士宮市", :city=>"静岡県", :street=>"北山"}
# 半角と全角はどっちでも大丈夫
index.lookup('108ー0075') # => {:post_code=>"1080075", :prefecture=>"港区", :city=>"東京都", :street=>"港南"}
# もし与えられた郵便番号がデータにない場合、nilを戻す
index.lookup('000-0000') # => nil
もしユーザー入力中に郵便番号や住所の予測変換表示したい場合、もう一つのメソッドが必要です。
def suggest(input, top = 5)
partial_code = clean_post_code(input)
@data.select { |post_code| post_code.start_with?(partial_code) }.values.take(top)
end
これで、郵便番号の一部を与えると、予測変換された住所が戻ります。
index = PostCodeIndex.new('KEN_ALL.csv')
index.suggest('140-0')
# => [
# {:post_code=>"1400000", :prefecture=>"品川区", :city=>"東京都", :street=>""},
# {:post_code=>"1400014", :prefecture=>"品川区", :city=>"東京都", :street=>"大井"},
# {:post_code=>"1400012", :prefecture=>"品川区", :city=>"東京都", :street=>"勝島"},
# {:post_code=>"1400001", :prefecture=>"品川区", :city=>"東京都", :street=>"北品川"},
# {:post_code=>"1400015", :prefecture=>"品川区", :city=>"東京都", :street=>"西大井"}
# ]
# 提案はいくつほしいか指定できる
index.suggest('10800', 3)
# => [
# {:post_code=>"1080022", :prefecture=>"港区", :city=>"東京都", :street=>"海岸"},
# {:post_code=>"1080075", :prefecture=>"港区", :city=>"東京都", :street=>"港南"},
# {:post_code=>"1080014", :prefecture=>"港区", :city=>"東京都", :street=>"芝"}
# ]
index.suggest('000-0000') # => []
Railsアプリに追加してみる
以上のコードはどんなアプリやフレームワークでも使えるけれども、Railsアプリに追加してみましょう。
# encoding: utf-8
require 'csv'
require 'singleton'
class PostCodeIndex
include Singleton
def initialize
file = File.join File.dirname(__FILE__), '..', '..', 'config', 'KEN_ALL.csv'
# ...
end
# ...
end
私は風通にデータベース以外、JSON APIなどからデータを読み込むクラスをservices
に入れます。そして、このクラスを構築するのは結構時間かかるので、一回だけ構築するようにSingletonにしました。
Rails.logger.info 'Initializing post code index...'
PostCodeIndex.instance
PostCodeIndex
が最初に使われるとき、時間かからる構築しないように、initializerを追加しました。こうすると、アプリ起動するとき、構築されます。
class ExampleController < ActionController::Base
layout 'application'
def index
end
def lookup_address
address = PostCodeIndex.instance.lookup(params[:post_code])
render json: { data: address }
end
end
Rails.application.routes.draw do
get '/example', to: "example#index"
get '/example/lookup_address', to: 'example#lookup_address'
end
lookup_address
はJSONを戻すので、AJAXで住所のデータが読み込めます。
<script>
$(document).ready(function () {
$("#example_zip_code").change(function () {
$.ajax({
url: "/example/lookup_address",
json: true,
data: { post_code: $(this).val() }
}).done(function (response) {
$("#example_prefecture").val(response.data.prefecture);
$("#example_city").val(response.data.city);
$("#example_street").val(response.data.street);
});
});
});
</script>
<%= form_for :example do |f| %>
<%= f.text_field :zip_code %>
<%= f.text_field :prefecture %>
<%= f.text_field :city %>
<%= f.text_field :street %>
<% end %>
追加した/example/lookup_address
のJSON APIはどんなJavaScriptのライブラリーやフレームワークでも使えるけれども、jQueryがRailsと付いているのでこの例はjQueryを使います。
簡単ですが、完成です。
まとめ
他のウェブサイトでよくこの機能を見た事がありますが、思った以上に簡単に作れました。もちろん、郵便番号から住所を調べるライブリやJSON APIなどがあります。例えば、ajaxzip3。でも、自分で作くるのは一番柔軟で、自分のプロジェクトの条件と完全に合わせる事ができます。