正在加载...
2298 字
11 分钟
PHP调用三个开源的IP地址位置离线数据库,基于Ip2region、GeoLite2和qqwry纯真IP地址库。
2025-04-17

日常项目开发中经常需要使用到获取用户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 至安装目录,然后通过下方的代码导入数据文件并调用查询方法即可:

<?php
require_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 安装:

  1. 在PHP网站目录下新建 composer.json 文件,输入以下内容:
{
"require": {
"maxmind-db/reader": "~1.0",
"geoip2/geoip2": "~2.0"
}
}
  1. 使用 composer 命令安装:
composer install
  1. 安装完成后将下载的 GeoLite2-City.mmdb 文件上传至网站目录

  2. 创建一个PHP脚本,并输入以下代码调用即可:

<?php
require_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 格式的二进制数据库文件。

安装:

  1. 将纯真IP地址下载上传至PHP网站目录解压

  2. 创建一个名为 ip.class.php 的PHP文件,PHP代码如下:

<?php
error_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);
}
}
}
  1. 创建一个主PHP脚本,并输入以下代码调用即可:
<?php
require '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地址库

来源:初春云盘

下载
PHP调用三个开源的IP地址位置离线数据库,基于Ip2region、GeoLite2和qqwry纯真IP地址库。
https://www.tr0.cn/641/
作者
小森
发布于
2025-04-17
许可协议
CC BY-NC-SA 4.0