Ruby + MySQL で郵便番号データ取り込み!

Updated:


Ruby on Rails 上で郵便番号を検索できるシステムを作成することを思いつき、まずは日本郵便のサイトからダウンロードしたCSVファイルを Ruby + MySQL で取り込むことを考えてみました。

日本郵便のサイトによると、郵便番号データは「郵便事業株式会社は著作権を主張しません。自由に配布していただいて結構です。」となっております。

詳細は説明しませんが、全体の作業の流れは以下のとおり。

  • 予め、こちらからCSVファイルをダウンロード・解凍を手動で行っておきます。

  • CSVファイル名は “KEN_ALL.CSV” 固定。
  • CSVファイルは「読み仮名データの促音・拗音を小書きで表記しないもの」と「読み仮名データの促音・拗音を小書きで表記するもの」のどちらにも対応可能。 (ローマ字データや事業所の個別番号データも存在しますが、現時点では対応しません。)

  • MySQLにデータベース・テーブルを作成。

  • 後述のテーブルスクリプトを参照。

  • Rubyスクリプトを実行する。

  • 後述のRubyスクリプトを参照。

参考までに当方が作成したテーブルのテーブルスクリプト、Rubyスクリプトを掲載しておきます。 当然ながら、Ruby + MySQL が動作する環境が必要です。(文字コード等の調整が必要な場合もあります) 詳細についてはソースの随所にコメントを記述していますので、Rubyソースをご覧ください。そんなに複雑な処理は行っていなませんので、ご理解いただけるかと思います。

テーブルスクリプト

  • CSVファイルの全カラムを取得するようテーブルを作成しています。
  • ストレージエンジンは高速性を重視し “MyISAM” を指定しています。
  • 現時点では、インデックスは未確定です。 ( 検索条件により変更する可能性有り )
  • 環境によっては、MySQL自体の設定のチューニングが必要になるかもしれません。

postal_codes.sql

CREATE TABLE IF NOT EXISTS `postal_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `public_code` char(5) NOT NULL,
  `postal_code_old` char(5) NOT NULL,
  `postal_code` char(7) NOT NULL,
  `pref_name_a` varchar(10) DEFAULT NULL,
  `city_name_a` varchar(30) DEFAULT NULL,
  `town_name_a` varchar(100) DEFAULT NULL,
  `pref_name_n` varchar(10) DEFAULT NULL,
  `city_name_n` varchar(30) DEFAULT NULL,
  `town_name_n` varchar(100) DEFAULT NULL,
  `flg_two_code` int(1) DEFAULT NULL,
  `flg_koaza` int(1) DEFAULT NULL,
  `flg_chome` int(1) DEFAULT NULL,
  `flg_two_town` int(1) DEFAULT NULL,
  `flg_upd` int(1) DEFAULT NULL,
  `flg_change` int(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_postal_code` (`postal_code`),
  KEY `idx_chimei` (`city_name_a`,`town_name_a`,`city_name_n`,`town_name_n`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Rubyスクリプト

大まかな流れは以下のとおり。

  • CSVファイルを全件読み込み、配列に格納。
  • 既存のテーブルのデータを削除。 ( TRUNCATE )
  • 読み込んだCSVデータ配列を全件書き込み。( INSERT )

get_post.rb

require 'csv'
require 'mysql'

#=郵便番号データ取得
class GetPost

  # DB(MySQL)接続情報
  $DB       = 'hoge'
  $HOST     = '127.0.0.1'
  $USER     = 'hogehoge'
  $PASSWORD = 'xxxxxxxx'

  # CSVファイル
  FILENAME_CSV = "KEN_ALL.CSV"

  # [CLASS] チェック
  class Check

    # CSVファイル存在チェック
    def check_csv()

      begin

        # 存在チェック
        if File.exist?( FILENAME_CSV )

          return ""

        else
          
          return "[ERROR] " + FILENAME_CSV + " is not exist!"

        end

      rescue => e

        # エラーメッセージ
        str_msg = "[例外発生][" + self.class.name + ".check_csv()] " + e.to_s
        STDERR.puts( str_msg )
        exit 1

      end

    end

  end

  # [CLASS] CSVファイル
  class Csv

    # CSVファイル読み込み
    #   0 全国地方公共団体コード --------------------------- 半角数字・5桁
    #   1 (旧)郵便番号(5桁) -------------------------------- 半角数字・5桁
    #   2 郵便番号(7桁) ------------------------------------ 半角数字・7桁
    #   3 都道府県名 --------------------------------------- 半角カナ ( コード順 ) [MAX: 7Byte,  7文字]
    #   4 市区町村名 --------------------------------------- 半角カナ ( コード順 ) [MAX:22Byte, 22文字]
    #   5 町域名 ------------------------------------------- 半角カナ ( 五十音順 ) [MAX:73Byte, 73文字]
    #   6 都道府県名 --------------------------------------- 漢字 ( コード順 )     [MAX: 8Byte,  4文字]
    #   7 市区町村名 --------------------------------------- 漢字 ( コード順 )     [MAX:20Byte, 10文字]
    #   8 町域名 ------------------------------------------- 漢字 ( 五十音順 )     [MAX:74Byte, 37文字]
    #   9 一町域が二以上の郵便番号で表される場合の表示 ----- 半角数字・1桁
    #      (「1」は該当、「0」は該当せず )
    #  10 小字毎に番地が起番されている町域の表示 ----------- 半角数字・1桁
    #      (「1」は該当、「0」は該当せず )
    #  11 丁目を有する町域の場合の表示 --------------------- 半角数字・1桁
    #      (「1」は該当、「0」は該当せず )
    #  12 一つの郵便番号で二以上の町域を表す場合の表示 ----- 半角数字・1桁
    #      (「1」は該当、「0」は該当せず )
    #  13 更新の表示 --------------------------------------- 半角数字・1桁
    #      (「0」は変更なし、「1」は変更あり、「2」廃止(廃止データのみ使用))
    #  14 変更理由 ----------------------------------------- 半角数字・1桁
    #      (「0」は変更なし、「1」市政・区政・町政・分区・政令指定都市施行、「2」住居表示の実施、
    #       「3」区画整理、「4」郵便区調整等、「5」訂正、「6」廃止(廃止データのみ使用) )
    # -------------------------------------------------------------------------------------------
    def read_csv()

      begin

        # 読み込み件数
        cnt_row = 0

        # CSVファイル全件ループ
        print "Reading CSV File "
        ary_data = []
        CSV.foreach( FILENAME_CSV ) do |row|
          ary_row = []
          row.each do |col|
            ary_row << NKF.nkf('-Sw', col.to_s )
          end
          cnt_row += 1
          print "." if cnt_row % 1000 == 0
          ary_data << ary_row
        end
        puts "\nRead Count = #{cnt_row.to_s}"

        return ary_data

      rescue => e

        # エラーメッセージ
        str_msg = "[例外発生][" + self.class.name + ".read_csv()] " + e.to_s
        STDERR.puts( str_msg )
        exit 1

      end

    end

  end

  # [CLASS] postal_codeテーブル
  class PostalCode

    # データ削除
    def delete_data()

      begin

        # MySQL 接続
        my = Mysql::connect( $HOST, $USER, $PASSWORD, $DB )

        # SQL文
        sql =  "TRUNCATE TABLE postal_codes "
        my.query( sql )

        puts "Deleted TABLE postal_codes ..."

      rescue => e

        # エラーメッセージ
        str_msg = "[例外発生][" + self.class.name + ".delete_data()] " + e.to_s
        STDERR.puts( str_msg )
        exit 1

      end

    end

    # データ書き込み
    def write_data( ary_data )

      begin

        # MySQL 接続
        my = Mysql::connect( $HOST, $USER, $PASSWORD, $DB )
        my.query("SET NAMES UTF8") # 環境によっては不要

        # Prepare
        sql =  "INSERT INTO postal_codes "
        sql << "          ( public_code, "
        sql << "            postal_code_old, "
        sql << "            postal_code, "
        sql << "            pref_name_a, "
        sql << "            city_name_a, "
        sql << "            town_name_a, "
        sql << "            pref_name_n, "
        sql << "            city_name_n, "
        sql << "            town_name_n, "
        sql << "            flg_two_code, "
        sql << "            flg_koaza, "
        sql << "            flg_chome, "
        sql << "            flg_two_town, "
        sql << "            flg_upd, "
        sql << "            flg_change ) "
        sql << " VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) "
        st = my.prepare( sql )

        # 書き込み件数
        cnt_ins = 0

        # INSERT
        print "Writing TABLE postal_codes "
        ary_data.each do |row|

          cnt_ins += 1

          # Execute
          st.execute( row[ 0].to_s,
                      row[ 1].to_s,
                      row[ 2].to_s,
                      row[ 3].to_s,
                      row[ 4].to_s,
                      row[ 5].to_s,
                      row[ 6].to_s,
                      row[ 7].to_s,
                      row[ 8].to_s,
                      row[ 9].to_s,
                      row[10].to_s,
                      row[11].to_s,
                      row[12].to_s,
                      row[13].to_s,
                      row[14].to_s )

          print "." if cnt_ins % 1000 == 0

        end

        puts "\nWrite Count = #{cnt_ins}"

      rescue => e

        # エラーメッセージ
        str_msg = "[例外発生][" + self.class.name + ".write_data()] " + e.to_s
        STDERR.puts( str_msg )
        exit 1

      end

    end

  end

  #################
  #### メイン処理 ####
  #################
  begin

    puts( "STARTED!!" )
    puts( "-" * 40 )

    # CLASS(チェック)インスタンス化
    obj_chk = Check.new()

    # CSVファイル存在チェック
    str_err = obj_chk.check_csv()
    if str_err != ""
      # エラーの場合、終了
      puts( str_err )
      exit
    end

    # CLASS(CSV)インスタンス化
    obj_csv = Csv.new()

    # CSVファイル読み込み
    ary_data = obj_csv.read_csv()

    # postal_codeテーブル インスタンス化
    tbl_postal_code = PostalCode.new()

    # postal_codeテーブル レコード削除
    tbl_postal_code.delete_data()

    # postal_codeテーブル レコード挿入
    tbl_postal_code.write_data( ary_data )

    puts( "-" * 40 )
    puts( "FINISHED!")

  rescue => e

    # エラーメッセージ
    str_msg = "[例外発生] " + e.to_s
    STDERR.puts( str_msg )
    exit 1

  end

end

2011年7月26日付けのデータだと、123,006件のデータが登録されます。 ある程度のマシンなら1分かかりません。非力なマシンでもそんなにかからないと思います。

郵便番号データの取り込みについては以上です。次は Ruby on Rails で検索できるようにしてみます。

以上。





 

Sponsored Link

 

Comments