在評論系統、用戶畫像、風控反欺詐等場景中,根據 IP 獲取地理位置(國家、省份、城市、ISP)是基礎需求。淘寶提供免費的 IP 地址庫查詢接口,本文封裝為開箱即用的 PHP 類。
https://ip.taobao.com/service/getIpInfo.php?ip=IP地址< 10 QPS,高並發需自建庫或付費服務<?php
class TaobaoIPQuery {
private $m_ip;
private $m_content; // 解析後的 JSON 對象
/**
* 構造函數:初始化並發起查詢
* @param string $ip 目標 IP 地址,為空則拋出異常
*/
public function __construct($ip) {
if (isset($ip)) {
$this->m_ip = $ip;
} else {
$this->m_ip = "";
}
if (!empty($this->m_ip)) {
$url_handle = curl_init();
curl_setopt($url_handle, CURLOPT_URL, "https://ip.taobao.com/service/getIpInfo.php?ip=" . $this->m_ip);
curl_setopt($url_handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($url_handle, CURLOPT_TIMEOUT, 5); // 連接超時 5 秒
curl_setopt($url_handle, CURLOPT_CONNECTTIMEOUT, 3); // 總超時 3 秒
$this->m_content = curl_exec($url_handle);
$curl_errno = curl_errno($url_handle);
$curl_error = curl_error($url_handle);
curl_close($url_handle);
if ($curl_errno) {
throw new Exception("cURL error ({$curl_errno}): {$curl_error}");
}
if ($this->m_content) {
$this->m_content = json_decode($this->m_content);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("JSON 解析失敗: " . json_last_error_msg());
}
// code=0 成功,code=1 失敗(如 IP 格式錯誤、頻率超限)
if (isset($this->m_content->code) && $this->m_content->code == 1) {
throw new Exception("查詢失敗: " . ($this->m_content->msg ?? '未知錯誤'));
}
} else {
throw new Exception("接口返回空內容");
}
} else {
throw new Exception("IP 地址不能為空");
}
}
// Getter 方法
public function getRegion() { return $this->m_content->data->region ?? ''; } // 省份
public function getCity() { return $this->m_content->data->city ?? ''; } // 城市
public function getCountry() { return $this->m_content->data->country ?? ''; } // 國家
public function getIsp() { return $this->m_content->data->isp ?? ''; } // 運營商
public function getArea() { return $this->m_content->data->area ?? ''; } // 區域(如華東、內網)
public function getCounty() { return $this->m_content->data->county ?? ''; } // 區/縣
// 獲取完整數據
public function getAll() { return $this->m_content->data ?? null; }
}
?>
<?php
require_once 'TaobaoIPQuery.php';
try {
// 獲取客戶端真實 IP(考慮代理)
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
// 取第一個非私有 IP
$ip_list = array_map('trim', explode(',', $ip));
$real_ip = '127.0.0.1';
foreach ($ip_list as $client_ip) {
if (!filter_var($client_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
continue;
}
$real_ip = $client_ip;
break;
}
$ipquery = new TaobaoIPQuery($real_ip);
echo "IP: {$real_ip}\n";
echo "國家: " . $ipquery->getCountry() . "\n";
echo "省份: " . $ipquery->getRegion() . "\n";
echo "城市: " . $ipquery->getCity() . "\n";
echo "區縣: " . $ipquery->getCounty() . "\n";
echo "運營商: " . $ipquery->getIsp() . "\n";
// 完整數據
// print_r($ipquery->getAll());
} catch (Exception $e) {
error_log("IP 查詢異常: " . $e->getMessage());
// 降級處理:使用本地 GeoIP 庫或默認值
$region = '未知';
}
?>
<?php
class CachedTaobaoIPQuery extends TaobaoIPQuery {
private $redis;
private $ttl = 86400 * 30; // 30 天緩存
public function __construct($ip, $redis) {
$this->redis = $redis;
$cache_key = "ip_geo:{$ip}";
// 嘗試從緩存讀取
$cached = $this->redis->get($cache_key);
if ($cached) {
$this->m_content = json_decode($cached);
$this->m_ip = $ip;
return; // 直接返回,不發起 HTTP 請求
}
// 緩存未命中,調用父類構造函數查詢
try {
parent::__construct($ip);
// 寫入緩存
if ($this->m_content) {
$this->redis->setex($cache_key, $this->ttl, json_encode($this->m_content));
}
} catch (Exception $e) {
// 查詢失敗時,嘗試讀取過期緩存作為降級
$stale = $this->redis->get("{$cache_key}:stale");
if ($stale) {
$this->m_content = json_decode($stale);
}
throw $e;
}
}
}
?>
<?php
// 批量查詢(需自建 IP 庫或付費 API,淘寶接口不支援批量)
// 異步預熱:定時任務掃描活躍用戶 IP,提前查詢緩存
function warmupIpCache($redis, $ip_list) {
$pipe = $redis->multi();
foreach ($ip_list as $ip) {
$pipe->get("ip_geo:{$ip}");
}
$results = $pipe->exec();
$missed = [];
foreach ($results as $i => $result) {
if (!$result) $missed[] = $ip_list[$i];
}
// 併發查詢缺失的 IP(使用 curl_multi 或 Swoole 協程)
// 這裡簡單示意
foreach ($missed as $ip) {
// 異步投遞到查詢佇列...
}
}
?>
<?php
// composer require geoip2/geoip2:~2.0
// 下載 GeoLite2-City.mmdb: https://dev.maxmind.com/geoip/geoip2/geolite2/
use GeoIp2\Database\Reader;
class FallbackGeoIP {
private $reader;
public function __construct($db_path = '/usr/share/GeoIP/GeoLite2-City.mmdb') {
if (!file_exists($db_path)) {
throw new Exception("GeoIP 數據庫文件不存在: {$db_path}");
}
$this->reader = new Reader($db_path);
}
public function query($ip) {
try {
$record = $this->reader->city($ip);
return [
'country' => $record->country->isoCode,
'region' => $record->mostSpecificSubdivision->name,
'city' => $record->city->name,
'isp' => $record->traits->isp ?? '',
];
} catch (Exception $e) {
return null;
}
}
}
?>
<?php
function getIpLocation($ip, $redis = null) {
// 1. 嘗試 Redis 緩存
if ($redis) {
$cached = $redis->get("ip_geo:{$ip}");
if ($cached) return json_decode($cached, true);
}
// 2. 嘗試淘寶接口
try {
$query = new TaobaoIPQuery($ip);
$data = [
'country' => $query->getCountry(),
'region' => $query->getRegion(),
'city' => $query->getCity(),
'isp' => $query->getIsp(),
];
if ($redis) $redis->setex("ip_geo:{$ip}", 86400*30, json_encode($data));
return $data;
} catch (Exception $e) {
error_log("淘寶 IP 查詢失敗: " . $e->getMessage());
}
// 3. 降級:本地 GeoIP2
try {
$geoip = new FallbackGeoIP();
$data = $geoip->query($ip);
if ($data && $redis) $redis->setex("ip_geo:{$ip}", 86400*7, json_encode($data));
return $data;
} catch (Exception $e) {
error_log("本地 GeoIP 查詢失敗: " . $e->getMessage());
}
// 4. 終極降級
return ['country'=>'', 'region'=>'', 'city'=>'', 'isp'=>''];
}
?>
合理使用緩存、降級、異步預熱,能構建高可用、低延遲的 IP 地理定位服務。