郵便番号検索API(XML)をPythonで問い合わせる。

XML で結果返す、郵便番号検索API http://zip.cgis.biz/ は、古くからあるAPIであるが、
Pythonxml 解析 xml.etree.ElementTree を学ぶのに丁度よい題材である。

API サービス、今や JSON の方が主流で、今更、XMLでレスポンスするものなど。。。
http://zipcloud.ibsnet.co.jp/doc/api
https://postcode-jp.com/ (有料)
これらを使うところが多いのではないだろうか。

とはいえ、xml.etree.ElementTree を学習する題材に、http://zip.cgis.biz/ に利用できます。

xml.etree.ElementTree を使う前に、どんなXMLを返すか?

# -*- coding: UTF-8 -*-
import urllib.request
import urllib.parse

param = { 'zn': '1600023' }
request = urllib.request.Request('http://zip.cgis.biz/xml/zip.php' + '?' + urllib.parse.urlencode(param))
with urllib.request.urlopen(request) as response:
    xml_string = response.read()
    print(xml_string.decode())

返ってくるXML

<?xml version="1.0" encoding="utf-8" ?>
<ZIP_result>
<result name="ZipSearchXML" />
<result version="1.01" />
<result request_url="http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn%3D1600023" />
<result request_zip_num="1600023" />
<result request_zip_version="none" />
<result result_code="1" />
<result result_zip_num="1600023" />
<result result_zip_version="0" />
<result result_values_count="1" />
<ADDRESS_value>
<value state_kana="トウキョウト" />
<value city_kana="シンジュクク" />
<value address_kana="ニシシンジュク(ツギノビルヲノゾク)" />
<value company_kana="none" />
<value state="東京都" />
<value city="新宿区" />
<value address="西新宿(次のビルを除く)" />
<value company="none" />
</ADDRESS_value>
</ZIP_result>

xml.etree import ElementTree を使用して、
クラスとして、まとめます。

zipsearch.py

# -*- coding: UTF-8 -*-
import urllib.request
import urllib.parse
from xml.etree import ElementTree as ET

class ZipSearch:
    def __init__(self):
        self.url = 'http://zip.cgis.biz/xml/zip.php'
        self.rkeys = [ 'state', 'city', 'address', 'company' ]
    def search(self, zipcode=None):
        if zipcode==None: raise AttributeError('zipcode is None')
        result = {}
        param = { "zn": zipcode }
        request = urllib.request.Request(self.url + '?' + urllib.parse.urlencode(param))
        with urllib.request.urlopen(request) as response:
            xml_string = response.read()
            root = ET.fromstring(xml_string)
            # 結果にエラー有れば、エラーコード、エラー内容を返す
            errorcodes = [ t.attrib['error_code'] for t in filter(lambda x: 'error_code' in x.attrib, root.iter('result'))]
            if any(errorcodes):
                errors = [t.attrib['error_note'] for t in filter(lambda x: 'error_note' in x.attrib, root.iter('result'))]
                result['error_code'] = errorcodes[0]
                result['error'] = errors[0]
                return result
            # resdict[0] → 検索結果数
            resdict = [int(t.attrib['result_values_count']) for t in filter(lambda x: 'result_values_count' in x.attrib, root.iter('result'))]
            result['count'] = resdict[0]
            if result['count'] > 0:
                address = []
                for addrvalue in root.iter('ADDRESS_value'):
                    values = {}
                    for item in addrvalue.iter('value'):
                        for k, v in item.attrib.items():
                            values[k] = v
                    addrString = "".join(list(filter(lambda x: x != 'none', [values[a] for a in self.rkeys])))
                    kanaString = " ".join(list(filter(lambda x: x != 'none', [values[a + "_kana"] for a in self.rkeys])))
                    address.append({ 'name': addrString, 'name_kana': kanaString })
                result['address'] = address
        return result

呼出し実行

from zipsearch import ZipSearch

zips = ZipSearch()

res = zips.search('1600023')
print(res)

res = zips.search('4130302')
print(res)

res = zips.search('1111111')
print(res)

res = zips.search('160-0023')
print(res)

結果

{'count': 1, 'address': [{'name': '東京都新宿区西新宿(次のビルを除く)', 'name_kana': 'トウキョウト シンジュクク ニシシンジュク(ツギノビルヲノゾク)'}]}
{'count': 2, 'address': [{'name': '静岡県賀茂郡東伊豆町奈良本', 'name_kana': 'シズオカケン カモグンヒガシイズチョウ ナラモト'}, {'name': '静岡県賀茂郡東伊豆町北川', 'name_kana': 'シズオカケン カモグンヒガシイズチョウ ホツカワ'}]}
{'count': 0}
{'error_code': '3', 'error': '郵便番号パラメータ(zn)形式誤り'}