電商、物流、風控、表單驗證等場景常需即時查詢手機號歸屬地(省份、運營商)。本文匯總六大免費公開接口,對比參數、返回格式、穩定性,並提供生產級 PHP 封裝類,支援自動降級、快取與並發控制。
| 提供方 | 請求示例 | 參數 | 返回格式 | 穩定性 | 備註 |
|---|---|---|---|---|---|
| 淘寶 | https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13800138000 |
tel | JSONP | ⭐⭐⭐⭐⭐ | 官方維護,支援 HTTPS,攜帶 t 參數防快取 |
| 騰訊財付通 | https://life.tenpay.com/cgi-bin/mobile/MobileQueryAttribution.cgi?chgmobile=13800138000 |
chgmobile | XML | ⭐⭐⭐⭐ | 需解析 XML,編碼 GBK |
| 百付寶 | https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13800138000 |
phone, callback, cmd | JSONP | ⭐⭐⭐ | 需固定 cmd=1059 |
| 拍拍 | https://virtual.paipai.com/extinfo/GetMobileProductInfo?mobile=13800138000&amount=10000&callname=cb |
mobile, amount, callname | JSONP | ⭐⭐ | amount 必填但無實際意義 |
| 115 網盤 | https://cz.115.com/?ct=index&ac=get_mobile_local&callback=jsonp&mobile=13800138000 |
mobile, callback | JSONP | ⭐⭐ | 需 Referer,偶爾限流 |
| 有道 | https://www.youdao.com/smartresult-xml/search.s?type=mobile&q=13800138000 |
type=mobile, q | XML/JSONP | ⭐⭐⭐ | 支援 jsFlag=true 返回 JSONP |
<?php
declare(strict_types=1);
class MobileLocation {
private const CACHE_TTL = 86400 * 30; // 30 天,歸屬地極少變更
private const TIMEOUT = 3; // 秒
private array $providers = [
'taobao' => [
'url' => 'https://tcc.taobao.com/cc/json/mobile_tel_segment.htm',
'param' => 'tel',
'parser' => 'parseTaobao',
],
'tenpay' => [
'url' => 'https://life.tenpay.com/cgi-bin/mobile/MobileQueryAttribution.cgi',
'param' => 'chgmobile',
'parser' => 'parseTenpay',
],
'youdao' => [
'url' => 'https://www.youdao.com/smartresult-xml/search.s',
'param' => 'q',
'extra' => ['type' => 'mobile', 'jsFlag' => 'true'],
'parser' => 'parseYoudao',
],
];
public function __construct(
private \Redis $redis,
private \GuzzleHttp\Client $http
) {}
public function get(string $mobile): ?array {
$mobile = $this->normalize($mobile);
if (!$mobile) return null;
$cacheKey = "mobile:loc:{$mobile}";
// 1. 快取命中
if ($cached = $this->redis->get($cacheKey)) {
return json_decode($cached, true);
}
// 2. 併發鎖防止快取擊穿
$lockKey = "lock:{$cacheKey}";
$lock = $this->redis->set($lockKey, '1', ['nx', 'ex' => 10]);
if (!$lock) {
// 等待並重試快取
usleep(200000);
return $this->redis->get($cacheKey) ? json_decode($this->redis->get($cacheKey), true) : null;
}
try {
// 3. 依序嘗試提供商
foreach ($this->providers as $name => $cfg) {
$data = $this->fetch($name, $cfg, $mobile);
if ($data) {
$this->redis->setex($cacheKey, self::CACHE_TTL, json_encode($data));
return $data;
}
}
return null;
} finally {
$this->redis->del($lockKey);
}
}
private function fetch(string $name, array $cfg, string $mobile): ?array {
$params = [$cfg['param'] => $mobile];
if (!empty($cfg['extra'])) $params = array_merge($params, $cfg['extra']);
try {
$resp = $this->http->get($cfg['url'], [
'query' => $params,
'timeout' => self::TIMEOUT,
'headers' => ['User-Agent' => 'MobileLocation/1.0'],
]);
$body = (string)$resp->getBody();
return $this->{$cfg['parser']}($body);
} catch (\Throwable $e) {
error_log("[MobileLocation] {$name} failed: {$e->getMessage()}");
return null;
}
}
// 解析器:統一返回 ['province' => '', 'carrier' => '']
private function parseTaobao(string $body): ?array {
// 返回格式:__GetZoneResult_ = {mts:'13800138000', province:'北京', catName:'中国移动', ...};
if (preg_match("/province\s*:\s*'([^']+)'/", $body, $m)) {
$province = $m[1];
} elseif (preg_match('/"province"\s*:\s*"([^"]+)"/', $body, $m)) {
$province = $m[1];
} else {
return null;
}
if (preg_match("/catName\s*:\s*'([^']+)'/", $body, $m)) {
$carrier = $m[1];
} elseif (preg_match('/"catName"\s*:\s*"([^"]+)"/', $body, $m)) {
$carrier = $m[1];
} else {
$carrier = '';
}
return ['province' => $province, 'carrier' => $carrier];
}
private function parseTenpay(string $xml): ?array {
$xml = simplexml_load_string($xml);
if (!$xml) return null;
return [
'province' => (string)($xml->province ?? ''),
'carrier' => (string)($xml->isp ?? ''),
];
}
private function parseYoudao(string $body): ?array {
// JSONP: fYodaoCallBack(1, {'product':'mobile','phonenum':'...','location':'北京 移动'}, "");
if (preg_match('/location[\'"]\s*:\s*[\'"]([^\'"]+)[\'"]/', $body, $m)) {
$parts = explode(' ', $m[1], 2);
return [
'province' => $parts[0] ?? '',
'carrier' => $parts[1] ?? '',
];
}
return null;
}
private function normalize(string $mobile): string {
$mobile = preg_replace('/\D/', '', $mobile);
return (strlen($mobile) === 11 && $mobile[0] === '1') ? $mobile : '';
}
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$http = new GuzzleHttp\Client();
$locator = new MobileLocation($redis, $http);
$result = $locator->get('13800138000');
var_dump($result);
// ['province' => '北京', 'carrier' => '中国移动']
timeout=3s,防止慢查詢拖垮主流程。若極度追求效能與可控性,可採用「號段庫 + 定期更新」:
免費接口適合中低頻、非核心業務;核心鏈路建議自建離線庫。無論哪種方案,**快取、降級、超時、監控** 四件套缺一不可。本文提供的封裝類可直接納入項目,按需擴展提供商與解析器。