日常项目开发中经常需要使用到获取用户IP位置的场景,例如网站的日志功能,需要记录操作用户的IP地址位置到日志里。
站长在开发公司的PHP项目时就遇到了需要获取有用户IP地址位置信息的场景,根据网上的几个开源的IP地址位置数据库项目简易用PHP写了调用案例,站长在这里分享记录和大家学习一下。
基于Ip2region
github开源地址:https://github.com/lionsoul2014/ip2region
这个IP地址库号称准确率能达到99.9%,不过经过站长测试发现错误率还挺高的,不确定是否有99.9%的准确率。
这个数据库使用xdb的文件数据格式,语言支持率很高,对于不同语言的开发者相当友好,常见的编程语言(如C、C++、Python、Java、PHP、Node JS、Lua等)都支持IP数据查询。
安装方法也很简单,直接通过 composer 命令安装即可:
composer require zoujingli/ip2region
完成后创建一个PHP文件,上传 ip2region.xdb 至安装目录,然后通过下方的代码导入数据文件并调用查询方法即可:
<?phprequire_once("./vendor/autoload.php");use ip2region\XdbSearcher;
$ip = $_GET['ip'];$xdb = './ip2region.xdb';try { $region = XdbSearcher::newWithFileOnly($xdb)->search($ip);
$arr = explode('|',$region);
echo json_encode([ 'ip' => $ip, 'country' => $arr[0], 'province' => $arr[2], 'city' => $arr[3], 'isp' => $arr[4], ], JSON_UNESCAPED_UNICODE);
} catch (\Exception $e) { exit(json_encode(['error'=>'ip invalid']));}
当然也可以通过本文最下方下载站长已经配置好的文件,上传到PHP网站目录后解压即可直接使用。
调用方法:
http://youhostname/phpfile.php?ip=192.168.0.1
返回数据格式:
{"ip":"192.168.0.1","country":"0","province":"0","city":"内网IP","isp":"内网IP"}
基于GeoLite2
GeoLite2 是由 MaxMind 公司提供的一套免费的IP地理定位数据库,主要数据库文件为.mmdb
格式的二进制文件。
国内知名运维面板“宝塔”面板中的“Nginx防火墙”中的IP地址库貌似就是用的这个开源库。
github下载地址:https://github.com/wp-statistics/GeoLite2-City/blob/master/GeoLite2-City.mmdb.gz
maxmind官方网站:https://support.maxmind.com/hc/en-us/articles/4408216129947-Download-and-Update-Databases
composer 安装:
- 在PHP网站目录下新建
composer.json
文件,输入以下内容:
{ "require": { "maxmind-db/reader": "~1.0", "geoip2/geoip2": "~2.0" }}
- 使用 composer 命令安装:
composer install
-
安装完成后将下载的
GeoLite2-City.mmdb
文件上传至网站目录 -
创建一个PHP脚本,并输入以下代码调用即可:
<?phprequire_once 'vendor/autoload.php';
try { $reader = new \GeoIp2\Database\Reader('GeoLite2-City.mmdb'); $ip = $_GET['ip'] ?? '127.0.0.1'; $record = $reader->city($ip); if ($record) { $country = $record->country->names['zh-CN']?? '未知'; $province = $record->mostSpecificSubdivision->names['zh-CN']?? '未知'; $city = $record->city->names['zh-CN']?? '未知'; echo json_encode(compact('ip','country', 'province', 'city'), JSON_UNESCAPED_UNICODE); } else { echo "未找到该 IP 的地理位置信息。\n"; } $reader->close();
} catch (\Exception $e) { echo "发生错误: " . $e->getMessage();}
此版本站长也提供了配置好的源码包,可选择在下方下载后直接上传至网站目录解压后直接使用。
调用方法:
http://youhostname/phpfile.php?ip=121.31.255.255
返回数据格式:
{"ip":"121.31.255.255","country":"中国","province":"广西","city":"玉林市"}
注意: 由于此项目由国外maxmind公司开源维护,对国内的IP识别准确率不是很高,如果您是用在国内项目的话不太建议使用此库。
基于纯真IP地址库
纯真(CZ88.NET)自2005年起一直为广大社区用户提供社区版IP地址库,由众多志愿者和专业人士共同维护,准确性往往优于一些商业数据库。
本文中使用的是由国内纯真网络提供的社区版纯真IP地址库,对国内的IP识别率是三个项目中最完整、最高的。
原纯真IP地址库使用了.dat
格式的二进制数据库文件,文件名默认为 qqwry.dat
。
github下载地址:https://github.com/nmgliangwei/qqwry/releases
纯真IP地址库还提供了.ipdb
格式的二进制数据库文件,文件名默认为 qqwry.ipdb
github下载地址:https://github.com/nmgliangwei/qqwry.ipdb/releases
本文中站长使用的纯真IP地址库为.dat
格式的二进制数据库文件。
安装:
-
将纯真IP地址下载上传至PHP网站目录解压
-
创建一个名为
ip.class.php
的PHP文件,PHP代码如下:
<?phperror_reporting(0);class IPQuery{
private $file,$fd,$total,$indexStartOffset,$indexEndOffset;
private $provinceFormatMap=["广西"=>"广西壮族自治区","内蒙古"=>"内蒙古自治区","新疆"=>"新疆维吾尔自治区","宁夏"=>"宁夏回族自治区","西藏"=>"西藏自治区","北京"=>"北京市","天津"=>"天津市","上海"=>"上海市","重庆"=>"重庆市","香港"=>"香港特别行政区","澳门"=>"澳门特别行政区"];
private $capitalMap=["河北"=>"石家庄","山西"=>"太原","辽宁"=>"沈阳","吉林"=>"长春","黑龙江"=>"哈尔滨","江苏"=>"南京","浙江"=>"杭州","安徽"=>"合肥","福建"=>"福州","江西"=>"南昌","山东"=>"济南","河南"=>"郑州","广东"=>"广州","湖南"=>"长沙","湖北"=>"武汉","海南"=>"海口","四川"=>"成都","贵州"=>"贵阳","云南"=>"昆明","陕西"=>"西安","甘肃"=>"兰州","青海"=>"西宁","台湾"=>"台北","内蒙古"=>"呼和浩特","广西"=>"南宁","西藏"=>"拉萨","宁夏"=>"银川","新疆"=>"乌鲁木齐","北京"=>"北京","天津"=>"天津","上海"=>"上海","重庆"=>"重庆","澳门"=>"澳门","香港"=>"香港"];
private $directCities = ["北京市", "天津市", "上海市", "重庆市"];
public function __construct($file) { if (!file_exists($file) ||!is_readable($file)) { throw new Exception("{$file} does not exist, or is not readable"); } $this->file = $file; $this->fd = fopen($file, 'rb'); $this->indexStartOffset = $this->unpackLong($this->readOffset(4, 0)); $this->indexEndOffset = $this->unpackLong($this->readOffset(4)); $this->total = ($this->indexEndOffset - $this->indexStartOffset) / 7 + 1; }
private function formatProvince($province, $country) { return $country === "中国" ? ($this->provinceFormatMap[$province]?? $province. "省") : $province; }
private function formatCity($city, $province) { return in_array($province, $this->directCities) ? substr($province, 0, -3). "市" : ($city? $city. "市" : ""); }
public function query($ip) { if (!filter_var($ip, FILTER_VALIDATE_IP)) { return json_encode(["error" => "{$ip} is not a valid IP address"], JSON_UNESCAPED_UNICODE); } $ipNum = ip2long($ip); $ipFind = $this->find($ipNum, 0, $this->total); $ipOffset = $this->indexStartOffset + $ipFind * 7 + 4; $ipRecordOffset = $this->unpackLong($this->readOffset(3, $ipOffset). chr(0)); $r = $this->readRecord($ipRecordOffset); if (empty($r[0]) && empty($r[1])) { $r = ["未知", "未知"]; } $resultParts = explode('–', $r[0]); $count = count($resultParts); $country = $count? $resultParts[0] : null; $province = $count > 1? $this->formatProvince($resultParts[1], $country) : ($country === "中国"? "未知" : null); $city = $count > 2? $this->formatCity($resultParts[2], $province) : ($country === "中国"? "未知" : null);
if ($city === "未知" && $province && $province!== "未知") { $provinceShort = preg_replace('/(省|市|自治区|特别行政区)$/', '', $province); if (isset($this->capitalMap[$provinceShort])) { $city = $this->formatCity($this->capitalMap[$provinceShort], $province); } } if ($country!== "中国" && $city === "未知") { $city = null; } $isp = preg_match('/[^\x{4e00}-\x{9fa5}\s]/u', $r[1])? null : $r[1]; if ($country === "IANA") { $country = "特殊用途地址"; $province = $city = $isp = null; } return json_encode(compact('ip','country', 'province', 'city', 'isp'), JSON_UNESCAPED_UNICODE); }
private function readRecord($offset) { $offset += 4; $flag = ord($this->readOffset(1, $offset)); if ($flag == 1) { $locationOffset = $this->unpackLong($this->readOffset(3, $offset + 1). chr(0)); $subFlag = ord($this->readOffset(1, $locationOffset)); if ($subFlag == 2) { return [$this->readLocation($this->unpackLong($this->readOffset(3, $locationOffset + 1). chr(0))), $this->readLocation($locationOffset + 4)]; } return [$this->readLocation($locationOffset), $this->readLocation($locationOffset + strlen($this->readLocation($locationOffset)) + 1)]; } if ($flag == 2) { return [$this->readLocation($this->unpackLong($this->readOffset(3, $offset + 1). chr(0))), $this->readLocation($offset + 4)]; } return [$this->readLocation($offset), $this->readLocation($offset + strlen($this->readLocation($offset)) + 1)]; }
private function readLocation($offset) { if ($offset == 0) { return ''; } $flag = ord($this->readOffset(1, $offset)); if ($flag == 0) { return ''; } if ($flag == 2) { return $this->readLocation($this->unpackLong($this->readOffset(3, $offset + 1). chr(0))); } $location = ''; $chr = $this->readOffset(1, $offset); while (ord($chr) != 0) { $location.=$chr; $offset++; $chr = $this->readOffset(1, $offset); } return mb_convert_encoding($location, 'UTF-8', 'GBK'); }
private function find($ipNum, $l, $r) { return $l + 1 >= $r? $l : ($ipNum < $this->unpackLong($this->readOffset(4, $this->indexStartOffset + intval(($l + $r) / 2) * 7))? $this->find($ipNum, $l, intval(($l + $r) / 2)) : $this->find($ipNum, intval(($l + $r) / 2), $r)); }
private function readOffset($bytes, $offset = null) { if ($offset!== null) { fseek($this->fd, $offset); } return fread($this->fd, $bytes); }
private function unpackLong($str) { return unpack('L', $str)[1]; }
public function __destruct() { if ($this->fd) { fclose($this->fd); } }}
- 创建一个主PHP脚本,并输入以下代码调用即可:
<?phprequire 'ip.class.php';
try { $ipQuery = new IPQuery('qqwry.dat'); $ip = $_GET['ip']?? '127.0.0.1'; $result = $ipQuery->query($ip); echo $result;} catch (Exception $e) { echo json_encode([ "error" => "Error: " . $e->getMessage() ], JSON_UNESCAPED_UNICODE);}
此版本站长也提供了配置好的源码包,可选择在下方下载后直接上传至网站目录解压后直接使用。
调用方法:
http://youhostname/phpfile.php?ip=111.183.71.20
返回数据格式:
{"ip":"111.183.71.20","country":"中国","province":"湖北省","city":"武汉市","isp":"电信"}
注意:
站长将此IP库的输出规则做了部分调整,规则如下:
- 纯真IP库默认的省级及市级都不带完整后缀,站长将默认输出的地区带上了完整的后缀,例如:广西->广西壮族自治区、北京->北京市
- 如果只能查询到省级,则默认将地级市输出为省会或首府,例如:province=广西壮族自治区,则city=南宁市。
- 查询结果为IANA则替换为
特殊用途地址
版权及免责声明
版权: 本文由 雾创岛 作者 小森 (暗屿之森)撰写,版权由 雾创岛 所有,如需转载请注明出处!
免责声明: 此文章为技术分享,代码及案例仅用于演示学习,如需使用到正式项目内请自行斟酌代码可用性,本站不为因用户引用本文代码出现的任何问题后果负责!
开源协议: 本文内的代码无任何开源协议,您可以将本文中的代码任意的使用于:修改、二次发布、商用等。
使用建议: 如果有能力及动手能力较强的,站长推荐将三个开源IP数据库整合查询,相互补齐查询结果数据,提升查询完整性和准确度。
后记:
目前的IP地址库数据都纯在数据误差,Ip2region、GeoLite2和qqwry纯真IP地址库三套IP数据库输出的结果都可能不太相同,例如:
IP:36.163.145.237
输出结果分别为:
qqwry纯真IP地址库:
{"ip":"36.163.145.237","country":"中国","province":"北京市","city":"北京市","isp":"移动"}
GeoLite2:
{"ip":"36.163.145.237","country":"中国","province":"陕西","city":"西安"}
Ip2region:
{"ip":"36.163.145.237","country":"中国","province":"0","city":"0","isp":"移动"}
站长认为较为准确和推荐的是qqwry纯真IP地址库。
下载地址
PHP开源IP地址库
来源:初春云盘