サーバー側で検索する

 郵便番号検索を行うには、まず元になるデータが必要です。郵便番号のデータはCSV形式で以下のURLに用意されています。

http://www.post.japanpost.jp/zipcode/download.html

 ここでは全国一括のデータをダウンロードし利用します。
【重要】ここでは厳密に処理するわけではないので、一部対象区域外の場合に表示される文字が適切なものにはなりません。データで「以下の住所以外」といったものに関しては全く処理していないため、実際とは異なる場所が表示されます。この章では、大量のデータをどう処理するか、にポイントを置いているため、正しい郵便番号検索処理を行うには、ちゃんとしたスクリプトを作成する必要があります。あらかじめ、ご了承ください。

 Ajaxを使った郵便番号検索は、いくつかのサイトに存在しますが、多くはSQL (MySQLやPosgreSQLなどのデータベース)を利用して実現されています。ここではSQLを使わずにUNIXで多く利用されている文字列検索を行うgrepを利用して処理してみます。
 grepで処理する前に、いくつか準備しなければなりません。まず、郵便番号データ (KEN_ALL.CSV) のサイズが11.4MBと、かなり大きいため検索に時間がかかる可能性があります。特に多数のアクセスが集中するとサーバー側に負荷がかかりレスポンスが悪くなることもあります。そこで、郵便番号と住所のデータだけを抽出しCSV形式で再保存します。12万行以上あるためExcelでは処理できません。そこで、JavaScriptを使ってCSV形式を読み出し、必要なデータ部分を保存するようにします。と言ってもブラウザに実装されているJavaScriptでは保存ができないので(Ajaxを使えば別ですが、今回は処理時間の都合もあるのでパス)、Adobe Photoshop CS/CS2を使って処理しました。JavaScriptはAdobeのアプリケーションでも動作しますし、他のアプリケーションでもJavaScriptが動作するものがあります。今回はAdobe Photoshop CSで変換しましたが、AfterEffects 6.5以降やIllustrator CS以降、InDesign CS以降でも同じスクリプトで動作すると思います。実際のスクリプトですが、以下のようになります。(サンプルスクリプトをダウンロードする)

filename = "/KEN_ALL.CSV";
savename = "/KEN.txt";
fileObj = new File(filename);
saveObj = new File(savename);
flag = fileObj.open("r");
if (flag == true)
{
flag = saveObj.open("w");
if (flag == true)
{
while(!fileObj.eof)
{
text = fileObj.readln();
str = text.split(",");
if (text == "") break;
zip = str[2].substring(1,str[2].length-1);
d1 = str[6].substring(1,str[6].length-1);
d2 = str[7].substring(1,str[7].length-1);
d3 = str[8].substring(1,str[8].length-1);
saveObj.writeln(zip+","+d1+d2+d3);
}
saveObj.close();
}else{
alert("保存できません");
}
fileObj.close();
}else{
alert("ファイルが開けませんでした");
}

 起動ディスク(ドライブ)のルートディレクトリ(一番上の階層)に郵便番号データ (KEN_ALL.CSV) を入れておき実行すれば、郵便番号と住所のCSVファイル (KEN.txt) が作成されます(ルートディレクトリに書き込み権限がない場合には動作しません)。
 このKEN.txtの内容は以下のようになります。

0600000,北海道札幌市中央区以下に掲載がない場合
0640941,北海道札幌市中央区旭ケ丘
0600041,北海道札幌市中央区大通東
0600042,北海道札幌市中央区大通西(1〜19丁目)
0640820,北海道札幌市中央区大通西(20〜28丁目)

 このデータをgrepを使って検索します。grepは以下のように検索文字列とファイル名を指定します。

grep 郵便番号 KEN.txt

 これで該当する郵便番号があれば、その行を返します。ここでは郵便番号を完全一致で調べるわけではないため、単純に郵便番号を指定してしまうと無関係な地域まで条件に一致してしまいます。そこで正規表現を使って処理を行います。幸いにしてgrepは正規表現で検索文字列を指定することができるので、以下のようにして郵便番号を検索します。

grep ^郵便番号 KEN.txt

 ^は先頭にマッチするという正規表現の記号です。これで郵便番号を正しく検索処理することができるようになります。実際のスクリプトは以下のようになっています。(Ruby 1.8.4を使用しています)

#!/usr/local/bin/ruby
require "cgi-lib"
input = CGI.new
zipdata = input["query"]
print "Content-type: text/html\n\n"
bom = "\xef\xbb\xbf"
print bom
fh = open("| grep ^"+zipdata+" KEN.txt")
while !fh.eof
print fh.gets
end
fh.close

 これでサーバー側の準備ができたので、あとはクライアント側(ブラウザ側)のスクリプトを用意します。結果はサーバー側がCSV形式で返すため、返された文字列を,(カンマ)ごと分けます。JavaScriptではsplit(",")とすればカンマごとに区切られたデータを分割し配列として返してくれます。あと、郵便番号の桁数が少ない場合(1桁や2桁)には大量の住所がヒットしてしまうため、ここでは3桁入力された時点でサーバーに問い合わせるようにします。実際のスクリプトは以下のようになります。(サンプルを実行する

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>郵便番号検索(サーバー側で処理)</title>
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript"><!--
function loadDataFile(searchZipCode)
{
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","zipcode.rb?query="+searchZipCode+"&cache="+(new Date()).getTime(),true);
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
parseZIPCode(httpObj.responseText);
}else{
$("result").innerHTML = "郵便番号データを読み込み中...";
}
}
function findZipCode()
{
var txt = $("zipCode").value;
if (txt.length < 3)
{ // 郵便番号が3桁未満の場合は結果をクリアして以後は処理しない
$("result").innerHTML = "";
return;
}
loadDataFile(txt);
}
// カンマ区切りテキストを解析して一致したデータを表示
function parseZIPCode(zipData)
{
var resultText = "";
var LF = String.fromCharCode(10); // 改行コード (LF)
lineData = zipData.split(LF);
for (var i=0; i<lineData.length; i++)
{
if (lineData[i] != "") resultText += lineData[i].split(",")[1] + "<br>";
}
if (resultText == "") resultText = "該当する住所はありません";
$("result").innerHTML = resultText;
}
// --></script>
</head>
<body>
<h1>郵便番号検索(サーバー側で処理)</h1>
<form name="ajaxForm" onSubmit="findZipCode();return false">
<input type="text" value="0640941" id="zipCode">
<input type="button" value="検索" onClick="findZipCode()"><br>
</form>
<div id="result"></div>
</body>
</html>

 郵便番号を入れて検索ボタンをクリックすると条件に一致する住所が表示されます。これだと、通常のCGIで処理するのと変わらないので、番号を入力したらリアルタイムに対応する住所が表示されるようにします。この場合、タイマーを使って処理するのが一番安全です。onkeydownなどのキーイベントはブラウザによっては期待通り動作しないことがあるためです。タイマーはsetTimeout()とsetInterval()の二種類がありますが、ここではページが読み込まれたらsetInterval()を使って定期的に関数を呼び出すようにしています。実際にリアルタイムに処理するといってもbodyタグのonload〜部分を追加すればできあがりです。(実際のサンプルを実行する

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>郵便番号検索(サーバー側で処理&リアルタイム処理)</title>
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript"><!--
oldNum = 0;
function loadDataFile(searchZipCode)
{
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","zipcode.rb?query="+searchZipCode+"&cache="+(new Date()).getTime(),true);
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
parseZIPCode(httpObj.responseText);
}else{
$("result").innerHTML = "郵便番号データを読み込み中...";
}
}
function findZipCode()
{
var txt = $("zipCode").value;
if (txt.length < 3)
{ // 郵便番号が3桁未満の場合は結果をクリアして以後は処理しない
$("result").innerHTML = "";
return;
}
if (oldNum == txt) return; // 内容が変更されていない場合は処理しない
oldNum = txt;
loadDataFile(txt);
}
// カンマ区切りテキストを解析して一致したデータを表示
function parseZIPCode(zipData)
{
var resultText = "";
var LF = String.fromCharCode(10); // 改行コード (LF)
lineData = zipData.split(LF);
for (var i=0; i<lineData.length; i++)
{
if (lineData[i] != "") resultText += lineData[i].split(",")[1] + "<br>";
}
if (resultText == "") resultText = "該当する住所はありません";
$("result").innerHTML = resultText;
}
// --></script>
</head>
<body onload='setInterval("findZipCode()",500)'>
<h1>郵便番号検索(サーバー側で処理&リアルタイム処理)</h1>
<form name="ajaxForm" onSubmit="findZipCode();return false">
<input type="text" value="0640941" id="zipCode">
<input type="button" value="検索" onClick="findZipCode()"><br>
</form>
<div id="result"></div>
</body>
</html>

 基本的に難しい処理ではないのですが、大量にアクセスが集中した場合にはサーバーが耐えられないこともあります。そこで、次項ではサーバー側の負荷を軽減するため、サーバー側で検索するのではなくクライアント側(ブラウザ側)で郵便番号検索を行ってみます。

[第十章 2:クライアント側で検索するへ]
[目次へ]

(2006.1.28)