需求
生成一份从大陆地区的IP到省市编号的映射列表,用于数据分析。
准备工作
统计局提供的标准行政区划代码 http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/,身份证前6位的编号。
从 http://dev.maxmind.com/geoip/geoip2/geolite2/ 下载CSV格式的城市数据库
新浪首页提供一个IP查询接口,可以输出TSV,JSONP,JSON的IP信息
流程
解压包括两个CSV文件:
GeoLite2-City-Blocks.csv:
IP信息
文件头 network_start_ip,network_mask_length,geoname_id,registered_country_geoname_id,represented_country_geoname_id,postal_code,latitude,longitude,is_anonymous_proxy,is_satellite_provider
network_mask_length 是按ipv6的长度计算的,转换为ipv4需要先找出符合ipv4格式的IP,然后使用 32 – 128 + $network_mask_length 转为CIDR。
GeoLite2-City-Locations.csv:
地区信息
文件头 geoname_id,continent_code,continent_name,country_iso_code,country_name,subdivision_iso_code,subdivision_name,city_name,metro_code,time_zone
两个文件使用 geoname_id 关联。
预处理:
拆出IPv4,CIDR,geoname_id
1 |
cat GeoLite2-City-Blocks.csv |grep -E '^::ffff:' | awk -F ',' 'NR == 1 { next } {print $1"\t"32-128+$2"\t"$3}' | awk -F ':' '{print $NF}' > ip_cidr_id.txt |
拆出 geoname_id,省编号,省名称,市名称
1 |
cat GeoLite2-City-Locations.csv | awk -F ',' '$4=="CN" && $6 >0 {print $1"\t"$6"\t"$7"\t"$8}' | sort -n -k 2 > id_provcode_prov_city.txt |
合并文件,筛出中国的IP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php $s = file(__DIR__ . '/id_provcode_prov_city.txt'); $gids = array(); foreach ($s as $r) { list($g, ) = explode("\t", $r, 2); $gids[$g] = 1; } unset($s); $ips = array(); $fp = fopen(__DIR__ . '/ip_cidr_id.txt', 'r'); while ($line = fgets($fp)) { list($ip, $cidr, $gid) = explode("\t", trim($line)); if (empty($gid)) var_dump($line); if (isset($gids[$gid])) { list($subnet, $broadcast, $netmask) = IPCalculator::ipCidr2Subnet($ip, $cidr); $ips[] = ip2long($subnet); } } |
使用新浪的一个公开的IP查询接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php function query_ip($ip) { $url = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=' . $ip; $ch = null; if (empty($ch)) { $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); } curl_setopt($ch, CURLOPT_URL, $url); RETRY: $s = curl_exec($ch); if (empty($s) || !($j = json_decode($s, true)) || $j['ret'] != '1') { echo "Retry {$ip}, $s\n"; sleep(1); goto RETRY; } return $j; } |
循环,查IP
数据源的IP是排好序的,所以直接用。不放心的话,可以直接数组排序。
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php $sina_ip_end = 0; foreach ($ips as $ip) { if ($ip <= $sina_ip_end) continue; $j = query_ip(long2ip($ip)); $sina_ip_end = ip2long($j['end']); if ($j['country'] != '中国') continue; $code = city_code($j['province'], $j['city']); #echo long2ip($ip[0]), "\t", long2ip($ip[1]), "\t", $j['start'], "\t", $j['end'], "\t", $j['province'], "\t", $j['city'], "\t", $j['type'], "\n"; echo $code, "\t", $j['start'], "\t", $j['end'], "\t", $j['province'], "\t", $j['city'], "\t", $j['isp'], "\t", $j['type'], "\n"; } |
整理的一个地区名称转城市编号的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<?php function city_code($province, $city) { static $provinces = Array( 11 => "北京", 12 => "天津", 13 => "河北", 14 => "山西", 15 => "内蒙古", 21 => "辽宁", 22 => "吉林", 23 => "黑龙江", 31 => "上海", 32 => "江苏", 33 => "浙江", 34 => "安徽", 35 => "福建", 36 => "江西", 37 => "山东", 41 => "河南", 42 => "湖北", 43 => "湖南", 44 => "广东", 45 => "广西", 46 => "海南", 50 => "重庆", 51 => "四川", 52 => "贵州", 53 => "云南", 54 => "西藏", 61 => "陕西", 62 => "甘肃", 63 => "青海", 64 => "宁夏", 65 => "新疆", 71 => "台湾", 81 => "香港", 82 => "澳门", ); #TODO 未完成static $cities = array( 11 => Array(0 => "北京"), 12 => Array(0 => "天津"), 13 => Array(1 => "石家庄", 2 => "唐山", 3 => "秦皇岛", 4 => "邯郸", 5 => "邢台", 6 => "保定", 7 => "张家口", 8 => "承德", 9 => "沧州", 10 => "廊坊", 11 => "衡水", 99 => "其它"), 14 => Array(1 => "太原", 2 => "大同", 3 => "阳泉", 4 => "长治", 5 => "晋城", 6 => "朔州", 7 => "晋中", 8 => "运城", 9 => "忻州", 10 => "临汾", 23 => "吕梁", 99 => "其它"), 15 => Array(1 => "呼和浩特", 2 => "包头", 3 => "乌海", 4 => "赤峰", 5 => "通辽", 6 => "鄂尔多斯", 7 => "呼伦贝尔", 22 => "兴安", 25 => "锡林郭勒盟", 26 => "乌兰察布盟", 28 => "巴彦淖尔盟", 29 => "阿拉善盟", 99 => "其它"), 21 => Array(1 => "沈阳", 2 => "大连", 3 => "鞍山", 4 => "抚顺", 5 => "本溪", 6 => "丹东", 7 => "锦州", 8 => "营口", 9 => "阜新", 10 => "辽阳", 11 => "盘锦", 12 => "铁岭", 13 => "朝阳", 14 => "葫芦岛", 99 => "其它"), 22 => Array(1 => "长春", 2 => "吉林市", 3 => "四平", 4 => "辽源", 5 => "通化", 6 => "白山", 7 => "松原", 8 => "白城", 24 => "延边"), 23 => Array(1 => "哈尔滨", 2 => "齐齐哈尔", 3 => "鸡西", 4 => "鹤岗", 5 => "双鸭山", 6 => "大庆", 7 => "伊春", 8 => "佳木斯", 9 => "七台河", 10 => "牡丹江", 11 => "黑河", 12 => "绥化", 27 => "大兴安岭", 99 => "其它"), 31 => Array(0 => "上海"), 32 => Array(1 => "南京", 2 => "无锡", 3 => "徐州", 4 => "常州", 5 => "苏州", 6 => "南通", 7 => "连云港", 8 => "淮安", 9 => "盐城", 10 => "扬州", 11 => "镇江", 12 => "泰州", 13 => "宿迁", 99 => "其它"), 33 => Array(1 => "杭州", 2 => "宁波", 3 => "温州", 4 => "嘉兴", 5 => "湖州", 6 => "绍兴", 7 => "金华", 8 => "衢州", 9 => "舟山", 10 => "台州", 11 => "丽水", 99 => "其它"), 34 => Array(1 => "合肥", 2 => "芜湖", 3 => "蚌埠", 4 => "淮南", 5 => "马鞍山", 6 => "淮北", 7 => "铜陵", 8 => "安庆", 10 => "黄山", 11 => "滁州", 12 => "阜阳", 13 => "宿州", 14 => "巢湖", 15 => "六安", 16 => "亳州", 17 => "池州", 18 => "宣城", 99 => "其它"), 35 => Array(1 => "福州", 2 => "厦门", 3 => "莆田", 4 => "三明", 5 => "泉州", 6 => "漳州", 7 => "南平", 8 => "龙岩", 9 => "宁德", 99 => "其它"), 36 => Array(1 => "南昌", 2 => "景德镇", 3 => "萍乡", 4 => "九江", 5 => "新余", 6 => "鹰潭", 7 => "赣州", 8 => "吉安", 9 => "宜春", 10 => "抚州", 11 => "上饶", 99 => "其它"), 37 => Array(1 => "济南", 2 => "青岛", 3 => "淄博", 4 => "枣庄", 5 => "东营", 6 => "烟台", 7 => "潍坊", 8 => "济宁", 9 => "泰安", 10 => "威海", 11 => "日照", 12 => "莱芜", 13 => "临沂", 14 => "德州", 15 => "聊城", 16 => "滨州", 17 => "菏泽", 99 => "其它"), 41 => Array(1 => "郑州", 2 => "开封", 3 => "洛阳", 4 => "平顶山", 5 => "安阳", 6 => "鹤壁", 7 => "新乡", 8 => "焦作", 9 => "濮阳", 10 => "许昌", 11 => "漯河", 12 => "三门峡", 13 => "南阳", 14 => "商丘", 15 => "信阳", 16 => "周口", 17 => "驻马店", 99 => "其它"), 42 => Array(1 => "武汉", 2 => "黄石", 3 => "十堰", 5 => "宜昌", 6 => "襄阳", 7 => "鄂州", 8 => "荆门", 9 => "孝感", 10 => "荆州", 11 => "黄冈", 12 => "咸宁", 13 => "随州", 28 => "恩施", 99 => "其它"), 43 => Array(1 => "长沙", 2 => "株洲", 3 => "湘潭", 4 => "衡阳", 5 => "邵阳", 6 => "岳阳", 7 => "常德", 8 => "张家界", 9 => "益阳", 10 => "郴州", 11 => "永州", 12 => "怀化", 13 => "娄底", 31 => "湘西", 99 => "其它"), 44 => Array(1 => "广州", 2 => "韶关", 3 => "深圳", 4 => "珠海", 5 => "汕头", 6 => "佛山", 7 => "江门", 8 => "湛江", 9 => "茂名", 12 => "肇庆", 13 => "惠州", 14 => "梅州", 15 => "汕尾", 16 => "河源", 17 => "阳江", 18 => "清远", 19 => "东莞", 20 => "中山", 51 => "潮州", 52 => "揭阳", 53 => "云浮", 99 => "其它"), 45 => Array(1 => "南宁", 2 => "柳州", 3 => "桂林", 4 => "梧州", 5 => "北海", 6 => "防城港", 7 => "钦州", 8 => "贵港", 9 => "玉林", 10 => "百色", 11 => "贺州", 12 => "河池", 13 => '来宾', 14 => '崇左', 21 => "南宁地区", 22 => "柳州地区", ), 46 => Array(1 => "海口", 2 => "三亚", 90 => "其他"), 50 => Array(0 => "重庆", 1 => "万州区", 2 => "涪陵区", 14 => "黔江区"), 51 => Array(1 => "成都", 3 => "自贡", 4 => "攀枝花", 5 => "泸州", 6 => "德阳", 7 => "绵阳", 8 => "广元", 9 => "遂宁", 10 => "内江", 11 => "乐山", 13 => "南充", 14 => "眉山", 15 => "宜宾", 16 => "广安", 17 => "达州", 18 => "雅安", 19 => "巴中", 20 => "资阳", 32 => "阿坝", 33 => "甘孜", 34 => "凉山", 99 => "其>它"), 52 => Array(1 => "贵阳", 2 => "六盘水", 3 => "遵义", 4 => "安顺", 22 => "铜仁", 23 => "黔西南", 24 => "毕节", 26 => "黔东南", 27 => "黔南"), 53 => Array(1 => "昆明", 3 => "曲靖", 4 => "玉溪", 5 => "保山", 6 => "昭通", 23 => "楚雄", 25 => "红河", 26 => "文山", 27 => "思茅", 28 => "西双版纳", 29 => "大理", 31 => "德宏", 32 => "丽江", 33 => "怒江", 34 => "迪庆", 35 => "临沧", 99 => "其它"), 54 => Array(1 => "拉萨", 21 => "昌都", 22 => "山南", 23 => "日喀则", 24 => "那曲", 25 => "阿里", 26 => "林芝", 90 =>"樟木口岸镇"), 61 => Array(1 => "西安", 2 => "铜川", 3 => "宝鸡", 4 => "咸阳", 5 => "渭南", 6 => "延安", 7 => "汉中", 8 => "榆林", 9 => "安康", 10 => "商洛"), 62 => Array(1 => "兰州", 2 => "嘉峪关", 3 => "金昌", 4 => "白银", 5 => "天水", 6 => "武威", 7 => "张掖", 8 => "平凉", 9 => "酒泉", 10 => "庆阳", 24 => "定西", 26 => "陇南", 29 => "临夏", 30 => "甘南", 99 => "其它"), 63 => Array(1 => "西宁", 21 => "海东", 22 => "海北", 23 => "黄南", 25 => "海南", 26 => "果洛", 27 => "玉树", 28 => "海西"), 64 => Array(1 => "银川", 2 => "石嘴山", 3 => "吴忠", 4 => "固原", 90 => '中卫', 99 => '其它'), 65 => Array(1 => "乌鲁木齐", 2 => "克拉玛依", 21 => "吐鲁番", 22 => "哈密", 23 => "昌吉", 27 => "博尔塔拉", 28 => "巴音郭楞", 29 => "阿克苏", 30 => "克孜勒苏", 31 => "喀什", 32 => "和田", 40 => "伊犁", 42 => "塔城", 43 => "阿勒泰", 44 => "石河子", 99 => "其它"), 71 => Array(0 => "台湾"), 81 => Array(0 => "香港"), 82 => Array(0 => "澳门"), ); if (empty($province)) { return '0000'; } $province_found = false; foreach ($provinces as $pc=>$pn) { if ($pn == $province) { if (empty($city)) { return $pc . '00'; } foreach ($cities[$pc] as $cc=>$cn) { if ($cn == $city) { return sprintf('%02s%02s', $pc, $cc); } } return $pc . '99'; } } return '9999'; } |