首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 使用站長之家 Chinaz API 獲取 IP 歸屬地的實現方案

     編輯:PHP 相關知識

PHP 使用站長之家 Chinaz API 獲取 IP 歸屬地的實現方案

獲取用戶 IP 歸屬地是個性化服務、風控分析、日誌審計的基礎需求。站長之家提供免費的 IP 查詢介面,本文介紹如何用 PHP 爬取或呼叫其 API,解析歸屬地資訊,並提供快取、異常處理、多源備援的生產級封裝。

一、介面分析與請求方式

站長之家 IP 查詢頁面:https://tool.chinaz.com/ip/?ip=8.8.8.8

  • 請求方式:GET
  • 參數:ip (可選,預設為訪問者 IP)
  • 回應:HTML 頁面,需正則解析
  • 頻率限制:未公開,建議緩存、控制並發

二、基礎爬取實現

<?php
function getIpLocationChinaz(string $ip = ''): ?array {
    $url = 'https://tool.chinaz.com/ip/?ip=' . urlencode($ip ?: getClientIp());
    
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; IPLocationBot/1.0)',
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => false, // HTTP 不需驗證
    ]);
    $html = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode !== 200 || !$html) return null;
    
    // 解析 HTML:尋找 "IP歸屬地: XX省XX市" 等資訊
    // 頁面結構可能變動,以下為常見模式
    $patterns = [
        'location' => '/IP歸屬地[::]\s*([^<\n]+)/i',
        'isp' => '/運營商[::]\s*([^<\n]+)/i',
        'area' => '/地區[::]\s*([^<\n]+)/i',
    ];
    
    $result = ['ip' => $ip];
    foreach ($patterns as $key => $pattern) {
        if (preg_match($pattern, $html, $m)) {
            $result[$key] = trim($m[1]);
        }
    }
    
    // 更穩健:針對具體 DOM 結構解析 (需引入 DOMDocument)
    // $dom = new DOMDocument(); @$dom->loadHTML($html); $xpath = new DOMXPath($dom);
    // $result['location'] = $xpath->evaluate('string(//div[@class="result"]/p[1])');
    
    return $result ?: null;
}

function getClientIp(): string {
    foreach (['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
        if (!empty($_SERVER[$key])) {
            $ips = explode(',', $_SERVER[$key]);
            return trim($ips[0]);
        }
    }
    return 'unknown';
}

三、生產級封裝:快取 + 多源備援 + 非同步

<?php
class IpLocationService {
    private $redis;
    private $ttl = 86400; // 24 小時快取
    private $sources = [
        'chinaz' => 'https://tool.chinaz.com/ip/?ip={ip}',
        'ipapi' => 'https://ip-api.com/json/{ip}?lang=zh-CN', // 免費、JSON、支援 HTTPS
        'ipinfo' => 'https://ipinfo.io/{ip}/json', // 需 Token、精度高
        'taobao' => 'https://ip.taobao.com/outGetIpInfo?ip={ip}', // 淘寶 IP 庫
    ];
    
    public function __construct(\Redis $redis = null) {
        $this->redis = $redis;
    }
    
    public function getLocation(string $ip = ''): array {
        $ip = $ip ?: $this->getClientIp();
        $cacheKey = "ip:location:$ip";
        
        // 1. 嘗試快取
        if ($this->redis && ($cached = $this->redis->get($cacheKey))) {
            return json_decode($cached, true) + ['cached' => true];
        }
        
        // 2. 依序嘗試資料源
        foreach ($this->sources as $name => $urlTemplate) {
            try {
                $url = str_replace('{ip}', urlencode($ip), $urlTemplate);
                $result = $this->fetchAndParse($name, $url);
                if ($result) {
                    $result['source'] = $name;
                    $result['ip'] = $ip;
                    // 3. 寫入快取
                    if ($this->redis) {
                        $this->redis->setex($cacheKey, $this->ttl, json_encode($result, JSON_UNESCAPED_UNICODE));
                    }
                    return $result;
                }
            } catch (Throwable $e) {
                error_log("IP location source '$name' failed for $ip: " . $e->getMessage());
                continue; // 嘗試下一個源
            }
        }
        
        return ['ip' => $ip, 'error' => 'All sources failed'];
    }
    
    private function fetchAndParse(string $source, string $url): ?array {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
            CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; IPLocationService/1.0)',
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode !== 200 || !$response) return null;
        
        switch ($source) {
            case 'chinaz':
                return $this->parseChinazHtml($response);
            case 'ipapi':
            case 'ipinfo':
                $data = json_decode($response, true);
                return $data ? $this->normalizeIpapi($data) : null;
            case 'taobao':
                $data = json_decode($response, true);
                return ($data['code'] ?? 1) === 0 ? $this->normalizeTaobao($data['data']) : null;
        }
        return null;
    }
    
    private function parseChinazHtml(string $html): ?array {
        // 使用 DOMDocument 更穩健
        $dom = new DOMDocument();
        libxml_use_internal_errors(true);
        $dom->loadHTML('' . $html);
        libxml_clear_errors();
        $xpath = new DOMXPath($dom);
        
        // 根據實際頁面結構調整 XPath
        $location = $xpath->evaluate('string(//div[contains(@class,"result")]//text()[contains(.,"IP歸屬地")])');
        $isp = $xpath->evaluate('string(//div[contains(@class,"result")]//text()[contains(.,"運營商")])');
        
        return [
            'country' => '中國',
            'region' => trim(str_replace('IP歸屬地:', '', $location)),
            'isp' => trim(str_replace('運營商:', '', $isp)),
        ];
    }
    
    private function normalizeIpapi(array $data): array {
        return [
            'country' => $data['country'] ?? '',
            'region' => $data['regionName'] ?? '',
            'city' => $data['city'] ?? '',
            'isp' => $data['isp'] ?? '',
            'lat' => $data['lat'] ?? null,
            'lon' => $data['lon'] ?? null,
        ];
    }
    
    private function normalizeTaobao(array $data): array {
        return [
            'country' => $data['country'] ?? '',
            'region' => $data['region'] ?? '',
            'city' => $data['city'] ?? '',
            'isp' => $data['isp'] ?? '',
            'area' => $data['area'] ?? '',
        ];
    }
    
    private function getClientIp(): string { /* 同前 */ }
}

四、使用範例

<?php
require 'vendor/autoload.php';

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$service = new IpLocationService($redis);

// 同步查詢
$loc = $service->getLocation('8.8.8.8');
print_r($loc);
/*
Array
(
    [ip] => 8.8.8.8
    [country] => 美國
    [region] => 加利福尼亞州
    [city] => 山景城
    [isp] => Google LLC
    [source] => ipapi
    [cached] => 
)
*/

// 批量查詢 (非同步佇列)
$ips = ['1.1.1.1', '8.8.8.8', '114.114.114.114'];
foreach ($ips as $ip) {
    // 推送到 Redis 佇列,Worker 背景處理
    $redis->lPush('queue:ip_location', $ip);
}

五、資料源對比與選型建議

資料源格式HTTPS免費額度精度(國內)精度(國際)推薦場景
站長之家HTML否未限制高中國內項目、無預算
ip-api.comJSON是45 req/min中高中小專案、快速接入
ipinfo.ioJSON是50k req/mo (免費)高高生產環境、需精度
淘寶 IPJSON是內部/限制極高低阿里系、國內電商
MaxMind GeoIP2MMDB本地付費/免費版高高高效能、離線、大流量

六、進階:本地 MMDB 離線查詢 (最高效能)

<?php
// composer require geoip2/geoip2:~2.0
use GeoIp2\Database\Reader;

$reader = new Reader('/usr/share/GeoIP/GeoLite2-City.mmdb');
$record = $reader->city('8.8.8.8');

echo $record->country->isoCode; // US
echo $record->country->names['zh-CN']; // 美國
echo $record->mostSpecificSubdivision->names['zh-CN']; // 加利福尼亞州
echo $record->city->names['zh-CN']; // 山景城
echo $record->traits->isp; // Google LLC
echo $record->traits->organization; // Google LLC

根據專案規模、預算、精度需求選擇適合方案:中小專案用 ip-api.com + Redis 快取;大型生產環境建議部署 MaxMind GeoIP2 本地庫,毫秒級響應、無外部依賴、資料可控。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved