技术博客
手机归属地查询:免费档位下如何做缓存,本地缓存手机号→归属地减少调用

手机归属地查询:免费档位下如何做缓存,本地缓存手机号→归属地减少调用

作者: 万维易源
2026-08-27
手机归属地查询本地缓存号段缓存档位限制
# 手机归属地查询:免费档位下如何做缓存,本地缓存手机号→归属地减少调用 > **接口**:手机归属地查询 `6-1` | **是否免费**:是(注册默认可免费调用,有使用档次限制)| **请求方式**:POST / GET | **返回格式**:JSON | **适用人群**:批量查询/CRM/风控接入者 | **阅读时间**:约 7 分钟 ## TL;DR - 缓存**按号段(手机号前 7 位)**做 key:同一号段归属地相同,命中即跳过调用,直接省下免费档位额度。 - 官方号段**每半年更新一次**,缓存 TTL 设 180 天左右即可与更新频率对齐,无需逐日刷新。 - 缓存只存"已查询过的结果",**新号段首次命中仍需调用**;不支持携号转网,所以无需按实时刷新。 ## Why:免费档位为什么要缓存? 本接口是免费服务,注册默认可调用,但**设有使用档次限制(具体档位数字以官方为准,本文不编造)**。一旦你的场景是批量查(通讯录去重、订单风控、Excel 逐条补归属地),调用量会很快触顶。 而手机归属地的本质规律是:**同一号段(前 7 位)归属地基本不变**。一个号段下可能几十万用户,但归属地只有一份。所以把"号段 → 省/市/运营商"缓存到本地,重复号段直接命中,**调用量随重复率下降而下降**,免费档位就能撑更久。再加上官方号段**每半年才更新一次**,缓存完全可以设较长 TTL,不必每次现查。 还有一层安全垫:**失败不扣点数**。即便你没缓存、偶尔查错格式,也不会浪费额度——但缓存解决的是"有效查询的重复消耗",和失败扣费是两回事。 ## What:前置条件与缓存设计速览 | 项目 | 内容 | |------|------| | 接口名称 | 手机归属地查询 | | 接入点 | `6-1`(仅 1 个接入点,同步请求-响应) | | 必填参数 | `num`(手机号,字符串) | | 缓存 key | 手机号**前 7 位**(号段),如 `1890871` | | 缓存 value | `showapi_res_body` 中业务字段(prov/city/name/type/...) | | TTL 建议 | ~180 天(对齐官方"半年更新一次") | | 计费 | 免费服务;**失败不扣点数** | | 能力边界 | **不支持携号转网**,故归属地不随用户实时变化 | ### 缓存设计三要点 1. **key = 号段前 7 位**:`18908711111` → key `1890871`。号段相同则归属地相同,粒度正合适。 2. **value = 业务字段快照**:存 `prov/city/name/type/areaCode/postCode/...`,调用方直接读缓存,不再解析接口。 3. **TTL ≈ 180 天**:官方半年更新一次号段归属地,缓存设 180 天既贴合更新节奏,又避免频繁回源。 ## How:两种实现(内存 LRU / Redis) ### 步骤 1 · 先写带缓存的查询封装(Python,内存 LRU) ```python import requests from functools import lru_cache API_URL = "https://route.showapi.com/6-1" APP_KEY = "YOUR_APPKEY" @lru_cache(maxsize=20000) # 进程内缓存,key 为号段字符串 def lookup_by_segment(seg7: str) -> dict: """按号段查询归属地,返回业务字段字典;失败返回空 dict(不扣点数)。""" # 用号段构造一个示例完整号去查(接口按号段判定,任意同号段号码结果一致) sample = seg7 + "0000" resp = requests.get( API_URL, params={"appKey": APP_KEY, "num": sample}, timeout=10, ) data = resp.json() body = data.get("showapi_res_body", {}) if body.get("ret_code") != 0: return {} # 失败不扣点数,缓存空结果避免反复回源 return { "prov": body.get("prov"), "city": body.get("city"), "name": body.get("name"), "type": body.get("type"), "areaCode": body.get("areaCode"), "postCode": body.get("postCode"), } def query_phone(num: str) -> dict: seg = num[:7] # 取前 7 位号段 return lookup_by_segment(seg) # 命中 lru_cache 即跳过 HTTP 调用 ``` > `lru_cache` 适合单进程、中等量级。它是内存缓存,进程重启即清空;TTL 需自行控制(`lru_cache` 不带过期,可在 value 里包一层时间戳实现,或改用下方 Redis 方案)。 ### 步骤 2 · 跨进程 / 多实例用 Redis ```python import json import redis import requests API_URL = "https://route.showapi.com/6-1" APP_KEY = "YOUR_APPKEY" r = redis.Redis(host="127.0.0.1", port=6379, db=0) TTL_DAYS = 180 def query_phone_redis(num: str) -> dict: seg = num[:7] key = f"phone_attr:{seg}" cached = r.get(key) if cached: return json.loads(cached) # 命中:跳过调用 sample = seg + "0000" resp = requests.get( API_URL, params={"appKey": APP_KEY, "num": sample}, timeout=10, ) body = resp.json().get("showapi_res_body", {}) if body.get("ret_code") != 0: return {} # 失败不扣点数 value = { "prov": body.get("prov"), "city": body.get("city"), "name": body.get("name"), "type": body.get("type"), "areaCode": body.get("areaCode"), "postCode": body.get("postCode"), } r.set(key, json.dumps(value), ex=TTL_DAYS * 86400) # 对齐半年更新 return value ``` ### 步骤 3 · 命中率与档位保护 - **先查缓存再查接口**:上面两段代码都先 `get` 缓存,未命中才发请求并回写。重复号段越多,命中率越高,调用量越低。 - **新号段首查必走接口**:缓存只存"已查询过"的结果,全新号段第一次仍要调接口,命中后自然沉淀。 - **失败不入"假命中"**:`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": "昆明" } } ``` | 缓存相关 | 取值说明 | |----------|----------| | 缓存 key | `num` 前 7 位 `1890871` | | 缓存 value | `prov=云南` `city=昆明` `name=电信` `type=2` `areaCode=0871` `postCode=650000` | | 回源触发 | 新号段首查 / 缓存过期(TTL≈180 天) | > 字段完整含义见 [《手机归属地返回字段全解:prov/city/type/postCode 一文读懂》](https://www.showapi.com/guides/phone-attribution-response-fields-6)。缓存 value 即来自该表所列业务字段。 ## 进阶 / 边界 - **不支持携号转网**:用户转网后归属地按原号段返回,不随实际网络变,所以**无需按实时刷新缓存**,180 天 TTL 足够稳。 - **缓存只存已查结果**:新号段首次仍需调用接口;命中率随数据积累上升,不是"零调用"。 - **失败不扣点数**:即便未命中缓存、调用失败(ret_code!=0)也不消耗额度,可放心做格式校验与回源。 - **免费档位限制**:缓存的核心收益就是压低有效调用次数,把免费档位留给真正的新号段;档位具体数字以官方为准。 - **多实例一致性**:单机用 `lru_cache`,多实例/跨进程用 Redis,避免各自回源导致缓存碎片化。 ## FAQ **Q1:缓存 key 用完整手机号还是号段?** 用**号段前 7 位**。同一号段归属地相同,按号段缓存命中率远高于完整号,且接口本身也是按号段判定。 **Q2:TTL 设多久合适?** 建议约 **180 天**,对齐官方"半年更新一次新号段归属地"。不必逐日刷新,过长则可能错过号段更新,过短则回源频繁。 **Q3:缓存会把失败结果也存下来吗?** 不应存为"业务命中"。`ret_code != 0` 时不写业务缓存(或只写空标记),避免把"查不到"误当归属地;且失败本就不扣点数。 **Q4:携号转网用户缓存会过期吗?** 不会。接口不支持携号转网,返回按原号段,不随用户实时变化,因此无需为转网做实时刷新。 **Q5:缓存能完全不调用接口吗?** 不能。缓存只覆盖"已查询过的号段",全新号段首次仍要调用。命中率随号段覆盖度提升而提升,但永远有首查回源。 **Q6:Redis 和 lru_cache 怎么选?** 单进程、中小量级用 `lru_cache`(自带,零依赖);多实例、需共享命中率用 Redis(带 TTL,跨进程一致)。 ## 相关能力 / 下一步阅读 - [《Excel 批量查归属地:用脚本替代手工逐条查》](https://www.showapi.com/guides/phone-attribution-excel-batch-6) —— 批量场景配合缓存效果最佳 - [《手机归属地查询:从注册到第一条返回》](https://www.showapi.com/guides/phone-attribution-quickstart-6) —— 还没跑通接口先看这篇 - [《手机归属地返回字段全解:prov/city/type/postCode 一文读懂》](https://www.showapi.com/guides/phone-attribution-response-fields-6) —— 缓存 value 字段来源 - **本系列共 12 篇**:查看[手机归属地查询 · 官方指南总目录](https://www.showapi.com/guides/phone-attribution-guides-6)