読者です 読者をやめる 読者になる 読者になる

IT業界のすみっこ暮らし

ふと気がついたときの記録

IPアドレス所在地検索機能の実装

一番最初にIPに紐付く地域情報DBを調べる

「ip address location database」でググるといくつかのサイトが確認できる。

www.ip2location.com

lite.ip2location.com

db-ip.com


調べた結果

調べてみると、IPから国~市情報までは無料提供もあるけど、細かい情報は大体有料な場合が多い。でも、機能の実装ならIP2Locationで提供しているLITE11で十分なのでこれをダウンロードしてみる。

lite.ip2location.com

IP2LocationはダウンロードページからDB構築手順も案内していて、色んな開発ケースに対してチュートリアルも提供しているので便利。

f:id:papamau:20170209174303p:plain

www.ip2location.com


IP2LocationでIPアドレス検索用DB構築

IPv4

CREATE TABLE `mt_ip2location_db11`(    
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `ip_from` INT(10) UNSIGNED,
    `ip_to` INT(10) UNSIGNED,
    `country_code` CHAR(2),
    `country_name` VARCHAR(64),
    `region_name` VARCHAR(128),
    `city_name` VARCHAR(128),
    `latitude` DOUBLE,
    `longitude` DOUBLE,
    `zip_code` VARCHAR(30),
    `time_zone` VARCHAR(8),
    PRIMARY KEY (`id`),
    INDEX `idx_ip_from` (`ip_from`),
    INDEX `idx_ip_to` (`ip_to`),
    INDEX `idx_ip_from_to` (`ip_from`, `ip_to`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;  

IPv6

CREATE TABLE `mt_ip2location_db11_ipv6`(   
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `ip_from` DECIMAL(39,0) UNSIGNED NULL DEFAULT NULL,
    `ip_to` DECIMAL(39,0) UNSIGNED NOT NULL,
    `country_code` CHAR(2),
    `country_name` VARCHAR(64),
    `region_name` VARCHAR(128),
    `city_name` VARCHAR(128),
    `latitude` DOUBLE,
    `longitude` DOUBLE,
    `zip_code` VARCHAR(30),
    `time_zone` VARCHAR(8),
    PRIMARY KEY (`id`),
    INDEX `idx_ip_from` (`ip_from`),
    INDEX `idx_ip_to` (`ip_to`),
    INDEX `idx_ip_from_to` (`ip_from`, `ip_to`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;  

IPv4

mysql>  LOAD DATA LOCAL INFILE 'C:\\util\\ip-address\\IP2LOCATION-LITE-DB11.CSV\\IP2LOCATION-LITE-DB11.CSV'   
    -> INTO TABLE mt_ip2location_db11    
    -> FIELDS TERMINATED BY ','  
    -> ENCLOSED BY '"'   
    -> LINES TERMINATED BY '\r\n'    
    -> (ip_from,ip_to,country_code,country_name,region_name,city_name,latitude,longitude,zip_code,time_zone);    
Query OK, 4044940 rows affected (56.48 sec) 
Records: 4044940  Deleted: 0  Skipped: 0  Warnings: 0

IPv6

mysql>  LOAD DATA LOCAL INFILE 'C:\\util\\ip-address\\IP2LOCATION-LITE-DB11.IPV6.CSV\\IP2LOCATION-LITE-DB11.IPV6.CSV' 
    -> INTO TABLE mt_ip2location_db11_ipv6   
    -> FIELDS TERMINATED BY ','  
    -> ENCLOSED BY '"'   
    -> LINES TERMINATED BY '\r\n'    
    -> (ip_from,ip_to,country_code,country_name,region_name,city_name,latitude,longitude,zip_code,time_zone);    
Query OK, 4366556 rows affected, 3 warnings (1 min 9.98 sec)    
Records: 4366556  Deleted: 0  Skipped: 0  Warnings: 3   

DBが準備できれば後はSELECTするだけなのでここまで。
※DBからIPv6の値を元に検索する方法が現在チュートリアルには存在してない、且つPythonのライブラリが存在するため、Pythonで開発する場合はライブラリを利用して実装することをお勧めする。

django rest frameworkでの実装例

IP情報からIPv4IPv6か判別し、地域情報を返す
(IPv6はDB格納、IPv6はライブラリ(BIN)をプロジェクト内に格納)

Python

class Ip2locationViewSet(viewsets.ViewSet):
    queryset = mt_ip2location_db11.objects.all()
    def list(self, request):
        ip_val = request.query_params.get('ip')
        if ip_val is not None:
            try:
                ipnet = ipaddress.ip_network(ip_val)
                if ipnet.version == 4:
                    ipv4_val = int(ipaddress.IPv4Address(ip_val))
                    ip_infos = mt_ip2location_db11.objects.filter(ip_to__gte=ipv4_val).order_by('ip_to')[:1]
                elif ipnet.version == 6:
                    IP2LocObj = IP2Location.IP2Location();
                    IP2LocObj.open(os.path.join("data","IP2LOCATION-LITE-DB11.IPV6.BIN"));
                    ip_infos = IP2LocObj.get_all(ip_val);
                    ipv6_result = OrderedDict()
                    ipv6_result['country_code'] = ip_infos.country_short
                    ipv6_result['country_long'] = ip_infos.country_long
                    ipv6_result['region_name'] = ip_infos.region
                    ipv6_result['city_name'] = ip_infos.city
                    ipv6_result['latitude'] = str(ip_infos.latitude)
                    ipv6_result['longitude'] = str(ip_infos.longitude)
                    ipv6_result['zip_code'] = ip_infos.zipcode
                    ipv6_result['time_zone'] = ip_infos.timezone
                    ipv6_result['ip'] = ip_val
                    return Response([ipv6_result])
                else:
                    ip_infos = mt_ip2location_db11.objects.all()[:1]
                    return Response(ip_infos, status=status.HTTP_404_NOT_FOUND)
            except ValueError:
                return Response({'message': 'IP形式が正しくありません。', 'ip': ip_val}, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response({'message': 'IPアドレスの情報がありません。', 'ip': ip_val}, status=status.HTTP_400_BAD_REQUEST)
        serializer = Ip2locationSerializer(ip_infos, many=True)
        serializer.data[0]['ip'] = ip_val
        return Response(serializer.data)


その他、参考サイト

IP2Locationの開発向けのページ

www.ip2location.com

ip2location-python

pypi.python.org