手机归属地查询错误码排查:-2 非11位 / -3 非数字 / -4 格式 / -5-6 找不到
# 手机归属地查询错误码排查:-2 非11位 / -3 非数字 / -4 格式 / -5-6 找不到
> **接口**:手机归属地查询 `6-1` | **是否免费**:是(注册默认可免费调用,有使用档次限制)| **请求方式**:POST / GET | **返回格式**:JSON | **适用人群**:前后端开发、表单/风控接入者 | **阅读时间**:约 6 分钟
## TL;DR
- 4 类错误码都来自 `showapi_res_body.ret_code`:**-2 非11位 / -3 非数字 / -4 格式错 / -5-6 找不到归属地**,全部 < 0。
- **失败时(ret_code != 0)不扣点数**——所以不怕试,但更该在客户端先做格式校验,省下调用量。
- 一条正则 `^1[3-9]\d{9}$` 就能拦掉绝大多数无效请求,把错误挡在调用之前。
## Why:为什么要把错误码讲透?
接入任何外部接口,第一道坎从来不是"怎么成功",而是"失败了怎么办"。手机归属地查询的错误码集中在 `showapi_res_body.ret_code` 上:**成功是 0,失败全是负数**。负数又分两类——
- 一类是**你传错了**(号码不是 11 位、含非数字字符、格式不对):`-2 / -3 / -4`;
- 一类是**接口查不到**(号段库里没有这个归属地):`-5 / -6`。
区别在哪?前者是**客户端能提前校验拦掉的**,后者是**数据本身缺失**。把两类分开,你就能决定:前者在提交前就拦,后者按"未知"分支处理。而且最关键的一点——**失败不扣点数**,这意味着你可以放心做格式校验、甚至批量试,但更聪明的做法是"先校验、再调用",把免费档位的额度留给真正有效的查询。
## What:前置条件与错误码速览
| 项目 | 内容 |
|------|------|
| 接口名称 | 手机归属地查询 |
| 接入点 | `6-1`(仅 1 个接入点,同步请求-响应) |
| 接口地址 | `https://route.showapi.com/6-1?appKey={your_appKey}` |
| 必填参数 | `num`(手机号,字符串) |
| 成功标志 | `showapi_res_body.ret_code == 0` |
| 失败标志 | `ret_code < 0`(不扣点数) |
| 计费 | 免费服务;**失败不扣点数** |
### 错误码对照表
| ret_code | 含义 | 类别 | 客户端能否提前拦 | 建议处理 |
|----------|------|------|------------------|----------|
| `0` | 成功 | — | — | 读取 prov/city/type 等业务字段 |
| `-2` | 不是 11 位 | 格式(输入) | 能(长度校验) | 提示"请输入 11 位手机号" |
| `-3` | 含非数字字符 | 格式(输入) | 能(纯数字校验) | 提示"手机号只能含数字" |
| `-4` | 格式错误 | 格式(输入) | 能(正则校验) | 统一提示格式不符 |
| `-5` | 找不到归属地 | 数据缺失 | 否 | 标记"未知",走未知分支 |
| `-6` | 找不到归属地 | 数据缺失 | 否 | 标记"未知",走未知分支 |
> 注意:`ret_code` 是**业务状态码**,位于 `showapi_res_body` 内;最外层的 `showapi_res_code` 是系统级状态码(成功为 0)。两者都要看:**外层决定请求是否送达,内层决定业务是否成功**。本文关注内层的 `ret_code`。
## How:客户端前置校验 + 调用与判断
### 步骤 1 · 客户端先做格式校验(强烈建议)
在把号码发给接口之前,用一行正则拦掉 `-2 / -3 / -4` 三类错误,直接省掉一次调用:
```python
import re
PHONE_RE = re.compile(r"^1[3-9]\d{9}$") # 11 位、纯数字、1 开头第 2 位 3-9
def is_valid_phone(num: str) -> bool:
return bool(PHONE_RE.match(num or ""))
# 用法
raw = input("手机号:")
if not is_valid_phone(raw):
print("格式不合法(应为 11 位纯数字),已拦截,未发起调用")
else:
print("格式通过,准备调用接口")
```
正则说明:`^1` 限定 1 开头,`[3-9]` 覆盖当前号段第二位范围,`\d{9}` 接后续 9 位,正好 11 位。它能在客户端拦截**非 11 位(-2)、含非数字字符(-3)、格式错误(-4)**三类问题。
### 步骤 2 · 发起调用并读取 ret_code
```python
import requests
API_URL = "https://route.showapi.com/6-1"
APP_KEY = "YOUR_APPKEY"
PHONE = "18908711111"
# 先校验,再调用
if not is_valid_phone(PHONE):
raise ValueError("手机号格式不合法,未发起调用")
resp = requests.get(
API_URL,
params={"appKey": APP_KEY, "num": PHONE},
timeout=10,
)
data = resp.json()
body = data.get("showapi_res_body", {})
rc = body.get("ret_code")
if rc == 0:
print("归属地:", body.get("prov"), body.get("city"),
"运营商:", body.get("name"))
elif rc in (-5, -6):
# 数据缺失:查不到归属地
print("查不到归属地(ret_code=%s),按未知处理(失败不扣点数)" % rc)
else:
# -2 / -3 / -4 等格式类错误
print("格式/输入错误(ret_code=%s),应已在客户端拦截(失败不扣点数)" % rc)
```
### 步骤 3 · 排查路径(出问题照这张图走)
1. **看外层 `showapi_res_code`**:非 0 说明请求没到接口(网络/鉴权/appKey 问题),先查 `showapi_res_error` 文案。
2. **看内层 `ret_code`**:
- `-2 / -3 / -4` → 检查传入 `num` 是否为合法 11 位纯数字,补上步骤 1 的正则。
- `-5 / -6` → 号段库暂无该归属地,属数据缺失,按"未知"分支处理,不要重试同号。
3. **确认不扣点数**:无论哪类失败,`ret_code != 0` 都不消耗额度,可安心校验与核对。
## 返回示例与解析
```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": "昆明"
}
}
```
| 字段 | 类型 | 含义 | 本例值 |
|------|------|------|--------|
| `showapi_res_code` | Number | 系统级状态码,0 成功 | `0` |
| `showapi_res_error` | String | 系统级错误信息 | `""` |
| `ret_code` | Number | 业务状态码(0 成功,<0 失败) | `0` |
| `prov` / `city` / `name` | String | 省 / 市 / 运营商名 | 云南 / 昆明 / 电信 |
> 字段完整含义见 [《手机归属地返回字段全解:prov/city/type/postCode 一文读懂》](https://www.showapi.com/guides/phone-attribution-response-fields-6)。失败时只看 `ret_code`,其余业务字段不可信。
## 进阶 / 边界
- **失败不扣点数**是核心福利:`-2/-3/-4/-5/-6` 全部不消耗额度,但更优解是客户端先校验,把免费档位留给有效查询。
- **正则不是万能**:`^1[3-9]\d{9}$` 能拦格式,拦不掉 `-5/-6`(号段库缺失),后者需业务侧按"未知"兜底。
- **别重试 -5/-6**:数据缺失不是偶发,对同一号码重试通常仍失败,且浪费调用量。
- **免费档位限制**:免费调用有使用档次上限,配合客户端校验 + 本地缓存(按号段前 7 位)能显著降低调用次数。
## FAQ
**Q1:ret_code 和 showapi_res_code 有什么区别?**
`showapi_res_code` 是系统级(请求是否送达/鉴权是否通过),`ret_code` 是业务级(查询是否成功)。两者为 0 才是真成功;`ret_code < 0` 即本文讲的 4 类错误。
**Q2:失败真的不扣点数吗?**
不扣。文档明确"失败时不扣点数",`ret_code != 0` 即失败且不扣费,可放心做格式校验与核对。
**Q3:正则能拦掉所有错误吗?**
能拦 `-2/-3/-4`(格式类),但拦不掉 `-5/-6`(号段库缺失)。后者需业务侧按"未知/查不到"分支处理,不要靠正则。
**Q4:返回的 num 是完整手机号吗?**
不是。`num` 返回的是**号段(前 7 位)**,如 `1890871`,不是完整号码,做校验时请用你自己的原始输入,别拿返回去回比。
**Q5:-5 和 -6 需要分别处理吗?**
两者都是"找不到归属地",业务上可统一按"未知"分支处理,无需区分。文档未给二者更细的差异定义。
**Q6:客户端校验后还需要判断 ret_code 吗?**
需要。校验只拦格式类错误,数据缺失(-5/-6)和偶发情况仍会出现,调用后必须读 `ret_code` 再决定分支。
## 相关能力 / 下一步阅读
- [《手机归属地查询:从注册到第一条返回》](https://www.showapi.com/guides/phone-attribution-quickstart-6) —— 还没跑通接口先看这篇
- [《手机归属地返回字段全解:prov/city/type/postCode 一文读懂》](https://www.showapi.com/guides/phone-attribution-response-fields-6) —— 字段类型与失败时的可信度
- [《注册/表单防错:用手机号归属地做实时校验与运营商识别》](https://www.showapi.com/guides/phone-attribution-form-validation-6) —— 把正则校验落到表单实时校验
- **本系列共 12 篇**:查看[手机归属地查询 · 官方指南总目录](https://www.showapi.com/guides/phone-attribution-guides-6)