手机归属地查询:注册/表单防错,用手机号归属地做实时校验与运营商识别
# 手机归属地查询:注册/表单防错,用手机号归属地做实时校验与运营商识别
> **接口**:手机归属地查询 `6-1` | **是否免费**:是(注册默认可免费调用,有使用档次限制)| **请求方式**:POST / GET | **返回格式**:JSON | **适用人群**:前端/后端开发者、表单与注册流程负责人 | **阅读时间**:约 8 分钟
## TL;DR
- 用手机归属地接口,能在用户输入手机号时**实时识别运营商(type)并校验号段真实性**,把"乱填号码"挡在提交之前。
- 客户端先正则校验"11 位纯数字"再调用,能大幅减少无效请求;**失败时(ret_code 非 0)不扣点数**,可放心做实时校验。
- 对 `type=-1`(未知运营商)的异常号段直接拦截或强提醒,并可与本地号段库联动给出前端提示。
## Why:为什么要把归属地带进表单校验?
注册、下单、绑卡、营销留资……几乎所有采集手机号的地方,都会遇到两类麻烦:
1. **号码是假的**:用户随手填 `13800000000` 之类凑数号,后续短信发不出去、数据脏。
2. **运营商没识别**:风控、套餐推荐、话费充值等场景,需要先知道号码属于移动/电信/联通/广电,才能走后续逻辑。
自己维护号段库成本很高——三网号段半年一变,广电(192 段)等新业态一出来就得追。把"归属地 + 运营商识别"交给手机归属地查询接口,你只需要传一个 `num` 参数,就能实时拿回 `prov`/`city`/`name`/`type`。它**只要求一个必填参数**,非常适合做"输入即查"的实时校验。
更友好的是:**失败时(ret_code != 0,如非 11 位、含非数字字符)不扣点数**。这意味着你可以放心地把校验请求放在用户输入环节,既保护数据质量,又不浪费调用额度。
## What:前置条件与接口速览
| 项目 | 内容 |
|------|------|
| 接口名称 | 手机归属地查询 |
| 接入点 | `6-1`(本接口仅 1 个接入点,同步请求-响应) |
| 接口地址 | `https://route.showapi.com/6-1?appKey={your_appKey}` |
| 请求方式 | POST 或 GET |
| 鉴权方式 | Query 参数 `appKey` |
| 必填参数 | `num`(手机号,字符串,示例 `18908711111`) |
| 返回格式 | JSON,业务数据在 `showapi_res_body` 内 |
| 关键字段 | `prov`/`city`/`name`/`type`/`ret_code` |
| 运营商枚举 `type` | 1=移动、2=电信、3=联通、4=广电、-1=未知 |
| 计费 | 免费服务(有使用档次限制);**失败不扣点数** |
| 更新频率 | 半年更新一次新号段归属地 |
| 注意 | **不支持携号转网查询** |
**前端校验建议顺序:** 先本地正则 `^1\d{10}$` 拦截明显错误的号码(避免无谓请求),通过后再调用接口做"运营商识别 + 号段真实性"的二次确认。
## How:从输入到拦截的可运行代码
### 步骤 1 · 客户端先做本地格式校验(防抖)
下面是一段带防抖的前端逻辑:用户输入停止 400ms 后才发起查询,减少请求抖动。
```javascript
let timer = null;
function onPhoneInput(raw) {
clearTimeout(timer);
const phone = raw.trim();
// 本地先拦一道:必须 11 位且纯数字,避免无效请求
if (!/^1\d{10}$/.test(phone)) {
showHint("请输入 11 位手机号", "warn");
return;
}
timer = setTimeout(() => verifyPhone(phone), 400);
}
async function verifyPhone(phone) {
try {
const resp = await fetch(
`https://route.showapi.com/6-1?appKey=YOUR_APPKEY&num=${encodeURIComponent(phone)}`,
{ method: "GET", signal: AbortSignal.timeout(10000) } // 超时 10s
);
const data = await resp.json();
const body = data.showapi_res_body || {};
if (body.ret_code !== 0) {
// 失败不扣点数,可安全提示"号码不可识别"
showHint("号码格式或号段不可识别", "error");
return;
}
const typeName = { 1: "移动", 2: "电信", 3: "联通", 4: "广电", "-1": "未知" }[body.type];
if (body.type === -1) {
// 异常号段:未知运营商,直接拦截或强提醒
showHint(`号段未知(${body.prov} ${body.city}),请确认号码`, "error");
return;
}
showHint(`归属地:${body.prov} ${body.city} · 运营商:${typeName}`, "ok");
fillOperator(body.type); // 把运营商带回表单,供后续逻辑使用
} catch (e) {
showHint("查询超时或网络异常", "warn");
}
}
```
### 步骤 2 · 后端再校验一次(Python 片段)
前端校验可被绕过,提交落库前务必在后端再查一次。下面是一个最小封装:
```python
import requests
API_URL = "https://route.showapi.com/6-1"
APP_KEY = "YOUR_APPKEY" # 替换为你的真实 AppKey
def check_phone(phone: str):
"""返回 (ok, info)。ok=False 时 info 为错误说明。"""
# 后端同样先本地拦截,省一次请求
if not str(phone).strip().isdigit() or len(str(phone).strip()) != 11:
return False, "不是 11 位纯数字"
try:
resp = requests.get(
API_URL,
params={"appKey": APP_KEY, "num": phone},
timeout=10,
)
body = resp.json().get("showapi_res_body", {})
except requests.RequestException:
return False, "查询超时或网络异常"
if body.get("ret_code") != 0:
# 失败不扣点数,可安心返回不可识别
return False, f"号段不可识别(ret_code={body.get('ret_code')})"
if body.get("type") == -1:
return False, "未知运营商号段"
return True, {
"prov": body.get("prov"),
"city": body.get("city"),
"type": body.get("type"),
"name": body.get("name"),
}
# 用法
ok, info = check_phone("18908711111")
if ok:
print("通过:", info)
else:
print("拦截:", info)
```
### 步骤 3 · 与本地号段库联动做前端提示
当接口返回 `type=-1`(未知)时,可进一步对照本地号段前缀白名单做提示。例如维护一份前缀→建议运营商的轻量映射,命中未知号段时给出"请核对号码,可能输错"的强提醒,而不是直接放行进库。
## 返回示例与解析
```json
{
"showapi_res_code": 0,
"showapi_res_error": "",
"showapi_res_id": "ce135f6739294c63be0c021b76b6fbff",
"showapi_res_body": {
"num": 1890871,
"prov": "云南",
"ret_code": 0,
"areaCode": "0871",
"name": "电信",
"cityCode": "530100",
"postCode": "650000",
"provCode": "530000",
"type": 2,
"city": "昆明"
}
}
```
| 字段 | 含义 | 校验用途 |
|------|------|----------|
| `prov` | 省 | 归属地区域提示 |
| `city` | 市 | 归属地区域提示 |
| `name` | 运营商名称 | 直接展示给用户 |
| `type` | 运营商枚举(1移动/2电信/3联通/4广电/-1未知) | **核心**:分流与拦截依据 |
| `ret_code` | 业务状态码,0 成功,其他失败 | 失败则不扣点数 |
| `num` | 号段(前 7 位) | 辅助核对前缀 |
> 字段的完整含义、类型与取值见 [《手机归属地返回字段全解》](https://www.showapi.com/guides/phone-attribution-response-fields-6)。
## 进阶 / 边界
- **失败不扣点数**:`ret_code != 0` 时不消耗额度,可放心把校验请求放在输入环节。
- **type=-1 的处理**:属于"查到了号码但无法识别运营商/归属地",应作为异常号段拦截或强提醒,而不是放行。
- **不支持携号转网**:号码携转后返回的仍是原号段归属,不能据此判断"当前在用运营商"。
- **免费档位限制**:实时校验是高频场景,务必先做本地正则校验、再做接口查询,避免无意义请求把档位打满。
- **前端校验不可信**:任何前端校验都能被绕过,落库前必须在后端用同一接口复核一次。
## FAQ
**Q1:前端实时校验会不会把免费档位很快用光?**
会。所以本文强调"客户端先正则校验 11 位纯数字再调用",把明显错误号码挡在请求之外;再加上防抖(停止输入 400ms 才查),能显著降低请求量。失败不扣点数,但成功调用会计入档位。
**Q2:type=-1 是什么意思,能放行吗?**
`type=-1` 表示未知运营商,通常是查不到归属地的异常号段。建议拦截或强提醒让用户核对,不要直接当有效号码入库。
**Q3:接口能告诉我号码是不是携号转网的吗?**
不能。官方明确不支持携号转网查询,返回结果按原号段归属,无法判断当前实际运营商。
**Q4:返回里的 num 是完整手机号吗?**
不是。`num` 返回的是**号段(前 7 位)**,如 `1890871`,不是用户输入的原号,不要当作完整号码使用。
**Q5:本地正则通过后,接口还会返回失败吗?**
会。例如号码是 11 位纯数字但号段根本不存在,接口会返回 `ret_code` 负数(找不到归属地)。所以"本地校验 + 接口复核"两道都不可少。
**Q6:失败(ret_code 非 0)会扣我的点数吗?**
不会。文档明确"失败时(ret_code!=0)不扣点数"。
## 相关能力 / 下一步阅读
- [《5 分钟接入手机归属地查询:从注册到第一条返回》](https://www.showapi.com/guides/phone-attribution-quickstart-6) —— 还不会发请求?从注册到第一条返回 5 分钟跑通
- [《手机归属地返回字段全解:prov/city/type/postCode 一文读懂》](https://www.showapi.com/guides/phone-attribution-response-fields-6) —— 每个字段的类型、取值与坑位
- [《错误码排查:-2 非11位 / -3 非数字 / -4 格式 / -5-6 找不到》](https://www.showapi.com/guides/phone-attribution-error-codes-6) —— 4 类错误对照与排查路径
- [《type 字段全解:1移动/2电信/3联通/4广电/-1未知怎么用》](https://www.showapi.com/guides/phone-attribution-type-field-6) —— 运营商枚举在业务分流中的实战用法
> **本系列共 12 篇**:查看[手机归属地查询 · 官方指南总目录](https://www.showapi.com/guides/phone-attribution-guides-6)