最近这两年,国内的很多网站和 App 的评论区和内容发表区都加入了显示 IP 归属地的功能,大多数网站的国内 IP 精确到省,国外的 IP 精确到国家。

显示 IP 归属地,最简单的方式就是通过调用在线的 API 服务,目前也有很多提供 IP 归属地查询的 API 服务,只需要通过 HTTP 请求传入 IP,就能返回 IP 的归属地。

在线的 IP 查询服务虽然使用方便,但是目前很难找到免费稳定的服务,而且 HTTP 请求需要消耗很多时间,除非你在用户发表内容的时候就把归属地一起存入数据库,否则体验会很差。

想要长期稳定的使用,最好的选择就是在本地查询,大多数网站的 IP 归属地估计也是在本地查询的。目前在 Github 上已经有开源的 IP 数据库了,开发者宣传的是 99.9% 的准确率。

Github 地址:https://github.com/zoujingli/ip2region

上面是 PHP 的版本,这个 IP 数据库目前提供 Go、PHP、Java、Lua、C、Rust、Python、Node.js、C#、Erlang、Nginx 的调用版本。

使用 Composer 安装

Composer 是 PHP 的一个软件包管理工具,现在很多 PHP 的开源项目也会发布到 Composer,如果你没有安装 Composer 的话可以到 https://getcomposer.org/download/ 下载安装。

下面通过 Composer 安装 ip2region,从命令行进入你的项目目录,输入:

composer require zoujingli/ip2region

如果 Composer 出现超时或安装速度过慢的情况可以使用腾讯的 Composer 镜像,输入:

composer config -g repos.packagist composer https://mirrors.tencent.com/composer/

可以切换到腾讯的 Composer 镜像。

简单使用

下面手动指定 IP 查询:

// 引入加载文件
require_once 'vendor/autoload.php';

$ip2region = new \Ip2Region();
// 指定 IP 查询
$location = $ip2region->simple('112.53.42.114');
// 输出查询到的 IP 归属地
echo $location;

调用 Ip2Regionsimple 需要传入一个 String 的 IP,返回 String 的 IP 归属地信息,上面查询的 IP 输出为:

中国广东省深圳市【移动】

下面再查询一些不同的 IP:

// 引入加载文件
require_once 'vendor/autoload.php';

$ip2region = new \Ip2Region();
// 查询和输出 IP 归属地
echo $ip2region->simple('127.0.0.1');  // 内网IP
echo $ip2region->simple('172.93.47.76');  // 美国加利福尼亚
echo $ip2region->simple('43.154.90.42');  // 澳大利亚
echo $ip2region->simple('112.45.122.107');  // 中国四川省成都市【移动】
echo $ip2region->simple('108.160.170.45');  // 美国弗吉尼亚阿什本
echo $ip2region->simple('113.246.243.98');  // 中国湖南省长沙市【电信】
echo $ip2region->simple('106.113.172.133');  // 中国河北省石家庄市【电信】

上面都是手动指定 IP 查询,如果你需要获取访问者的 IP 可以使用 $_SERVER['REMOTE_ADDR'] 获取,如果你在本地服务器测试的话,获取的可能是内网 IP,如果你访问的是 localhost 的话,获取的就是 ::1

完全基于文件的查询

上面查询的数据格式可能不一定是你需要的,下面手动读取 xdb 数据库文件查询:

$dbFile = 'vendor/zoujingli/ip2region/ip2region.xdb';  // xdb 文件位置
require_once 'vendor/autoload.php';  // 引入加载文件

// 读取 xdb 文件
try {
    $searcher = XdbSearcher::newWithFileOnly($dbFile);
}catch (Exception $e) {
    exit('无法读取 xdb 文件' . $e);
}

// 查询
$sTime = XdbSearcher::now();
$location = $searcher->search('112.45.122.107');
if ($location == null) {
    exit('无法查询到 IP 信息');
}

// 输出查询结果
echo $location;

上面的查询结果为 中国|0|四川省|成都市|移动,它的格式是 国家|区域|省份|城市|ISP,没有查询到的就是 0,你可以使用 explode 通过分隔符 | 把查询结果字符串拆分为数组使用。

下面简单处理一下查询结果,如果是中国就输出省份名称,不是中国就输出国家名称:

$location = '中国|0|四川省|成都市|移动';  // 查询结果
// 把查询结果拆分为数组
$location = explode('|', $location);

if ($location[0] == '中国') {
    // 如果是中国就输出省份名称
    echo $location[2];
}else {
    // 不是中国就输出国家名称
    echo $location[0];
}

缓存 xdb 文件

如果你需要循环查询大量 IP 的话,可以直接把 xdb 数据库文件缓存到内存,缓存后就不需要每次查询都访问硬盘,可以极大的增加查询速度。

下面加载 xdb 到内存查询 IP:

$dbFile = 'vendor/zoujingli/ip2region/ip2region.xdb';  // xdb 文件位置
require_once 'vendor/autoload.php';  // 引入加载文件

// 加载 xbd 到内存
$cBuff = XdbSearcher::loadContentFromFile($dbFile);
if ($cBuff == null) {
    exit('无法加载 xdb 文件');
}

// 创建查询对象
try {
    $searcher = XdbSearcher::newWithBuffer($cBuff);
}catch (Exception $e) {
    exit('无法创建查询对象' . $e->getMessage());
}

// 查询
$sTime = XdbSearcher::now();
$location = $searcher->search('112.45.122.107');
if ($location == null) {
    exit('无法查询到 IP 信息');
}

// 输出查询结果
echo $location;

上面的查询结果为 中国|0|四川省|成都市|移动,它的格式是 国家|区域|省份|城市|ISP,没有查询到的就是 0

xdb 文件的大小差不多是 10.5M,缓存后你的内存会多占用 10.5M,在需要查询大量 IP 的情况下可以考虑缓存 xdb。