手机归属地查询:Excel 批量查归属地,用脚本替代手工逐条查
# 手机归属地查询:Excel 批量查归属地,用脚本替代手工逐条查
> **接口**:手机归属地查询 `6-1` | **是否免费**:是(注册默认可免费调用,有使用档次限制)| **请求方式**:POST / GET | **返回格式**:JSON | **适用人群**:运营/数据/客服人员、需要批量处理通讯录的开发者 | **阅读时间**:约 10 分钟
## TL;DR
- ShowAPI **没有官方批量接口**,本文用脚本循环读取 Excel 里的手机号、逐条调用 `6-1`、再把结果写回——本质是"用户侧脚本循环调用"。
- 循环调用**受免费档位限制**:号码量大时要先评估档位,或先做本地去重/缓存,避免一遍跑光额度。
- 对 `type=-1` 或 `ret_code` 负数的记录**跳过并记入失败清单**,不要硬写空值覆盖原表。
## Why:手工逐条查太慢了
市场部有一张几万行的客户手机号表,想加一列"归属地""运营商"做区域分析;客服有一份通讯录要补"省市"标签。如果打开网页一个个粘贴查询,几百条就够喝一壶,上万条根本不现实。
但必须说清楚一件事:**ShowAPI 的手机归属地查询没有官方批量/异步能力,也无订阅推送、无回调**。所谓"批量",只能是你在自己机器上写个脚本,**循环读取 Excel 每行手机号,逐条调一次接口,再把结果写回**。这是用户侧行为,不是平台能力。
好消息是接口**只要求一个必填参数 `num`**、且**失败时(ret_code!=0)不扣点数**,所以写脚本成本很低;坏消息是循环调用会累积成功请求数,受免费档位上限约束,大批量前务必规划速率与缓存。
## 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`/`areaCode` |
| 运营商枚举 `type` | 1=移动、2=电信、3=联通、4=广电、-1=未知 |
| 计费 | 免费服务(有使用档次限制);**失败不扣点数** |
| 批量方式 | **无官方批量**,仅用户侧脚本循环调用 |
| 注意 | **不支持携号转网查询** |
**关键认知:** 本文所有"批量"都是你的脚本在循环发单条请求,平台侧仍是逐条同步响应。设计脚本时一定要加速率控制、失败重试与缓存,否则既打满档位又容易超时。
## How:读取 Excel → 循环调用 → 写回
### 步骤 1 · 准备环境与输入表
假设 `phones.xlsx` 里有一列名为 `phone` 的手机号(A 列也可以,按列名读取更稳)。需要安装:`pip install openpyxl pandas requests`。
### 步骤 2 · 循环调用并写回(Python)
下面脚本读取 `phone` 列,逐条查询,把 `prov`/`city`/`name`/`type` 写回新列,并对失败/未知记录单独记录:
```python
import time
import requests
import pandas as pd
API_URL = "https://route.showapi.com/6-1"
APP_KEY = "YOUR_APPKEY" # 替换为你的真实 AppKey
TYPE_NAME = {1: "移动", 2: "电信", 3: "联通", 4: "广电", -1: "未知"}
def lookup(phone: str):
"""返回 (ok, row)。ok=False 时 row 为失败原因。"""
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, "type=-1 未知运营商"
return True, {
"prov": body.get("prov"),
"city": body.get("city"),
"name": body.get("name"),
"type": body.get("type"),
"typeName": TYPE_NAME.get(body.get("type"), "未知"),
"areaCode": body.get("areaCode"),
}
def main():
df = pd.read_excel("phones.xlsx")
results, failures = [], []
for idx, row in df.iterrows():
phone = str(row.get("phone", "")).strip()
ok, res = lookup(phone)
if ok:
results.append(res)
else:
failures.append({"phone": phone, "reason": res})
results.append({}) # 占位,保持与 df 行数对齐
# 速率控制:每条约 0.2s,避免过快;按你的档位调整
time.sleep(0.2)
# 简单进度
if (idx + 1) % 100 == 0:
print(f"已处理 {idx + 1} 行")
res_df = pd.DataFrame(results)
out = pd.concat([df, res_df], axis=1)
out.to_excel("phones_with_attr.xlsx", index=False)
if failures:
pd.DataFrame(failures).to_excel("phones_failed.xlsx", index=False)
print(f"完成,{len(failures)} 行失败,已写入 phones_failed.xlsx")
if __name__ == "__main__":
main()
```
### 步骤 3 · 加失败重试(指数退避)
网络抖动时不应直接判失败。把 `lookup` 包一层重试:
```python
def lookup_with_retry(phone: str, max_retry: int = 3):
delay = 1.0
for attempt in range(max_retry):
ok, res = lookup(phone)
if ok or "网络/超时" not in res: # 业务失败(非网络)不重试
return ok, res
time.sleep(delay) # 指数退避
delay *= 2
return ok, res
```
把步骤 2 主循环里的 `lookup(phone)` 换成 `lookup_with_retry(phone)` 即可。
## 返回示例与解析
```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未知) | 运营商分流依据 |
| `areaCode` | 城市区号 | 辅助核对地域 |
| `ret_code` | 业务状态码,0 成功,其他失败 | 失败则跳过不写 |
> 字段的完整含义、类型与取值见 [《手机归属地返回字段全解》](https://www.showapi.com/guides/phone-attribution-response-fields-6)。
## 进阶 / 边界
- **无官方批量**:本文所有"批量"都是脚本循环单条请求,平台侧仍是逐条同步响应,不存在批量入口或异步队列。
- **免费档位限制**:循环调用会累积成功请求数,受档位上限约束。**大批量前先评估档位**;可对号码去重、对近期查过的号码做本地缓存,减少重复调用(详见系列缓存篇)。
- **失败不扣点数**:`ret_code != 0` 不消耗额度,但成功调用会计入档位,别把"重试"当成零成本无限打。
- **type=-1 / ret_code 负数 → 跳过并记录**:这类行不写归属地、不写错值,单独落到失败表,方便后续人工核对或二次处理。
- **不支持携号转网**:返回的归属地/运营商按原号段,无法反映携转后实际状态。
- **无经纬度**:只返回省/市文字,无法直接在地图打点。
## FAQ
**Q1:ShowAPI 有官方的批量查询接口吗?**
没有。本接口仅 1 个同步接入点 `6-1`,无订阅推送、无回调、无官方批量能力。所谓批量只能是你用脚本循环调用单条接口实现,属于用户侧行为。
**Q2:几万条号码一次性跑,会不会把免费档位用光?**
会。循环调用每成功一次都计入档位。建议先对号码去重、做本地缓存,并评估你的档位上限;必要时分批分天跑。
**Q3:某些号码查询失败(ret_code 负数)会扣费吗?**
不会。文档明确"失败时(ret_code!=0)不扣点数"。但成功调用会计入档位,重试也要算成功次数。
**Q4:type=-1(未知运营商)的记录怎么处理?**
建议跳过写归属地、单独记到失败/待核对表,不要硬写空值或错误运营商,避免污染分析结果。
**Q5:接口能批量返回经纬度做地图分布吗?**
不能。本接口不返回经纬度,只给省/市文字归属地;地图可视化需另行配合外部地理编码服务。
**Q6:返回里的 num 是完整手机号吗?可以拿它回填原表吗?**
不可以。`num` 返回的是**号段(前 7 位)**,如 `1890871`,不是完整号码,写回时务必使用你 Excel 里自己的原号码列。
## 相关能力 / 下一步阅读
- [《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) —— 每个字段的类型、取值与坑位
- [《免费档位下如何做缓存:本地缓存手机号→归属地,减少调用》](https://www.showapi.com/guides/phone-attribution-cache-6) —— 大批量前必看,用缓存扛住档位限制
- [《错误码排查:-2 非11位 / -3 非数字 / -4 格式 / -5-6 找不到》](https://www.showapi.com/guides/phone-attribution-error-codes-6) —— 4 类错误对照与排查路径
> **本系列共 12 篇**:查看[手机归属地查询 · 官方指南总目录](https://www.showapi.com/guides/phone-attribution-guides-6)