技术博客
手机归属地查询:注册/表单防错,用手机号归属地做实时校验与运营商识别

手机归属地查询:注册/表单防错,用手机号归属地做实时校验与运营商识别

作者: 万维易源
2026-08-27
手机归属地查询表单校验运营商识别注册防错
# 手机归属地查询:注册/表单防错,用手机号归属地做实时校验与运营商识别 > **接口**:手机归属地查询 `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)