新闻详情

新闻详情

首页 / 资讯中心 / 详情

手机号码归属地数据库:从号段查询到离线建库的完整指南

发布时间:2026/9/25 14:51:52来源:尧图网络
手机号码归属地数据库:从号段查询到离线建库的完整指南
简介手机号码归属地数据库包含38万余条号段记录涵盖省份、城市、运营商等字段适合需要做号码属地查询、业务风控或地域分析的开发者与数据从业者也便于学习MySQL关系表设计。压缩包共2个文件整体约8.27MB其中SQL文件可直接导入MySQL使用MDB文件可用Access等工具查阅满足不同环境下的数据接入与二次开发需求。该资源已有729人学习下载。库内表结构清晰既能支撑按号码快速定位归属地也可作为MySQL数据库设计、索引优化和ETL导入的练习素材。需注意号码段数据会随运营商调整而变化正式使用时应定期同步更新并遵循个人信息保护相关规范。1. 手机号码归属地数据库一张撑起风控与客服系统的离线表一个注册表单要自动填城市一个客服弹窗要显示来电归属一个风控策略要判断号码是否来自高风险地区背后都依赖同一张表手机号码归属地数据库。很多人以为这只是一份「下载下来就能用」的号段表实际上手才发现它会因为携号转网失真、因为虚拟运营商号段缺城市粒度、因为数据源注水而频繁出错。这篇文章会把这个方向拆透数据怎么选、库怎么建、查询怎么写、哪些坑会让你翻车以及离线环境下怎么验证和持续更新。适合正在做用户运营、短信服务、风控审核或 CRM 系统的开发者也适合需要把归属地能力离线化的数据工程团队。2. 号段数据从哪来三种数据源和一套清洗入表流程2.1 先理解 11 位号码的切分规则手机号归属地的核心不是「手机号」而是「号段」。国内 11 位手机号的结构是前 3 位网络识别号、中间 4 位地区编码、后 4 位用户号也就是说一个手机号的前 7 位在历史上唯一对应一个城市和一个运营商。归属地数据库的最小粒度就是这前 7 位一张十几万到几十万行的号段表就能覆盖全国所有在用号码段而不是把每个 11 位号码都存一行。这也是为什么「归属地查询」必须用前 7 位做 key而不是前 3 位。前 3 位只能区分运营商同一个 138 号段可能在全国几十个城市放号后 4 位更是与地区完全无关。很多新手把 11 位号码直接当作字符串去匹配或者建一张全量号码表这都是把简单问题做复杂。真实场景里离线库能回答的只是「这个号段发放在哪里」不是「这个人此刻在哪里」。后者需要访问运营商的位置寄存器HLR那是另一套按次计费的实时通道。2.2 三种数据来源怎么选从业者做这个方向数据来源无非三类。第一类是运营商官方发布的号段公告或号段查询页面权威性最高但信息分散需要自己按省份整理而且更新节奏不固定。第二类是电商平台或开源社区里别人整理好的离线库常见格式是 CSV、Excel、access、SQLite 单文件覆盖度高、开箱即用但质量参差后续要花大量时间做校验。第三类是权威查询 API走运营商或持牌机构的实时通道返回维度多、时效好但按调用量计费适合低频高价值场景不适合让离线服务长期依赖。我一般建议以第二类离线库为底座用第一类官方号段公告做交叉校正再维护一张增量变更表。这样做的好处是离线库保证低成本和高可用官方公告保证关键号段不错增量表保证行政区划调整后还能追溯。常见的第三方离线库行数在 10 万到 40 万之间导入 SQLite 后单文件只有几十 MB放到 MySQL 或国产关系库里也毫无压力。真正要花时间的不是导入而是洗干净。2.3 清洗入表别把脏号段直接导进数据库从网上下载的号段文件列名各不相同像mobile、prefix、segment、号段混着出有的行末尾带 \r有的城市列是空的还有大量重复号段。直接导入数据库只会让后续查询和更新变成灾难。先清洗再入库这一步不能省。import csv import sqlite3 # 原始文件列名统一为prefix, province, city, carrier, areacode, zip SOURCE_CSV mobile_prefix_master.csv def clean_source(raw_rows): 清洗号段只保留 1 开头的 7 位数字去重剔除空城市。 seen set() clean [] for row in raw_rows: prefix str(row.get(prefix, )).strip() if not (prefix.isdigit() and len(prefix) 7 and prefix.startswith(1)): continue if prefix in seen: continue if not row.get(province) or not row.get(city): continue seen.add(prefix) clean.append(row) return clean raw_rows [] with open(SOURCE_CSV, newline, encodingutf-8) as f: for row in csv.DictReader(f): raw_rows.append(row) rows clean_source(raw_rows) print(f清洗前 {len(raw_rows)} 行清洗后 {len(rows)} 行) conn sqlite3.connect(phone_region.db) conn.execute( CREATE TABLE IF NOT EXISTS region ( prefix TEXT PRIMARY KEY, province TEXT NOT NULL, city TEXT NOT NULL, carrier TEXT, areacode TEXT, zip TEXT ) ) conn.executemany( INSERT OR REPLACE INTO region VALUES (?,?,?,?,?,?), [(r[prefix], r[province], r[city], r.get(carrier, ), r.get(areacode, ), r.get(zip, )) for r in rows] ) conn.commit()这个脚本的逻辑很直接先用isdigit()和长度检查把非号段行挡在门外再用一个set去重防止同一号段在不同省份文件里重复出现。最后用INSERT OR REPLACE写入主键就是prefix本身避免自增 id 导致重复导入时同一号段出现多行。清洗前后行数对比是一个很有效的质量信号如果你下载的库号称几十万行清洗完只剩几万行那就得怀疑数据源注水了。2.4 字段设计八列足够别把数据库做成 Excel号段表不需要太多列核心信息就是号段本身、省份、城市、运营商再加区号和邮编两个附赠字段。很多第三方库会卖你「大区」「方言片区」「基站信息」这些列实际用处很小反而让导入脚本反复踩列名不一致的坑。region 表的结构如下字段类型说明prefixTEXT PRIMARY KEY手机号前 7 位查询主键provinceTEXT省 / 自治区 / 直辖市cityTEXT地级市或直辖市的区县carrierTEXT运营商名称areacodeTEXT长途区号如 0755zipTEXT邮编可选保留主键用文本类型而不是整数是有意的。号段虽然都是数字但它是「编号」不是「数值」未来如果要兼容前导符号或做字符串匹配文本类型更稳。排序、比较、索引在 SQLite 里对短文本的性能开销可以忽略没必要为了性能把它转成 int。建好表之后顺手把索引建上prefix已经是主键SQLite 会自动建索引查询时直接等值匹配就能命中。3. 归属地库的建库与查询从 SQLite 起步到高并发前的性能取舍3.1 存储选型SQLite、MySQL、内存表各到哪一步归属地库是典型的读多写少场景数据量又很小所以存储选型不该一上来就上大件。单机部署、离线包分发、给内部工具做查询SQLite 单文件是最省心的方案一个文件拷走就是整个库不需要额外服务备份就是复制文件。它的性能在本地查询时能达到单次 0.1ms 级别一个普通接口撑到每秒几百次查询没问题。如果多个服务要共享同一份数据或者有团队要同时维护更新再把数据迁到 MySQL 这类集中式数据库。这时要注意连接池参数常见做法是初始化连接 5 个、最大 20 到 50 个、空闲超时 300 秒连接验证语句用SELECT 1。归属地查询本身极快瓶颈往往不在 SQL 而在连接创建连接池小了高峰期排队大了浪费资源。更高并发场景比如每天几千万次查询的网关服务直接把号段表加载到进程内存里做一个dict比任何数据库都快。启动时读一次之后查内存配合定期重启或双 buffer 更新。存储方式适用场景单次查询量级更新方式SQLite单机、离线包、内部工具约 0.1ms替换文件或增量写MySQL多服务共享、集中管理约 0.2~1msSQL 更新需连接池内存 dict高并发网关微秒级重启加载或双 buffer 热切换3.2 查询 SQL 怎么写前缀直查不碰 LIKE查询归属地最忌讳的做法是WHERE mobile LIKE %号码段%。这会让 SQLite 放弃索引做全表扫描十万行表可能在毫秒级完成感觉还行但数据一多、查询一高频就立刻翻车。正确写法是把 11 位手机号截出前 7 位用主键等值查询。下面这段代码就是最小可用查询服务。import sqlite3 conn sqlite3.connect(phone_region.db) def lookup(mobile): 按 11 位手机号查归属地返回省份、城市、运营商。 if not (mobile.isdigit() and len(mobile) 11): raise ValueError(mobile 必须是 11 位数字) prefix mobile[:7] cur conn.execute( SELECT province, city, carrier, areacode FROM region WHERE prefix ?, (prefix,) ) row cur.fetchone() if not row: return None return { province: row[0], city: row[1], carrier: row[2], areacode: row[3], } print(lookup(13800138000))这里有两个参数细节值得注意。第一mobile[:7]截取的是前 7 位不是前 3 位或者前 4 位少了这个原则查询结果就会粗到运营商级别。第二SQL 里用?占位符传参而不是字符串拼接既避免注入风险也让 SQLite 能复用查询计划。返回值里没有放完整手机号因为这是归属地服务不是号码档案服务少回传个人信息能省很多合规麻烦。3.3 批量查询与缓存参数别把号码库做成逐个请求风控场景里经常会一批传几十个号码进来如果循环里逐个调lookup每个都要走一次 SQLite 的查询计划累积起来就是几百次数据库交互。更稳的做法是批量查先取出这批号码里所有不同的前 7 位号段拼一个IN查询再按原顺序回填。def batch_lookup(mobiles): 批量查询返回与原列表顺序一致的结果列表。 prefixes {m[:7] for m in mobiles if len(m) 11 and m.isdigit()} if not prefixes: return [] placeholders ,.join(? * len(prefixes)) cur conn.execute( fSELECT prefix, province, city, carrier FROM region fWHERE prefix IN ({placeholders}), tuple(prefixes) ) index {row[0]: row[1:] for row in cur.fetchall()} return [index.get(m[:7]) for m in mobiles] result batch_lookup([13800138000, 13912345678, 18800001111])一个容易踩的坑是IN子句的变量数量限制。SQLite 对单条 SQL 的变量数有限制旧版本默认大约 999 个新版本虽然放宽到 32766但生产环境不可保证。批量查询里号段数量超过几百个时最好按 500 个一批拆开分批查完再合并。这个约束在你用 Python 的executemany时不存在但IN写法里一定会遇到属于典型的「本地跑得好、上线就报错」。查询热路径上还能加一层lru_cache缓存。装饰器的maxsize不需要设得过大手机号前 7 位总共也就几十万个设 40000 到 60000 足够覆盖高频号段。缓存 key 用前 7 位而不是完整号码可以避免缓存被海量不同号码打爆。3.4 行政区划变更增量更新的正确顺序行政区划不是一成不变的。2019 年莱芜并入济南老库里「莱芜」这个城市突然就没了。如果不做增量更新查询结果会保留一个早已不存在的行政区用户看到会直接投诉。增量更新的流程应该是先拉取官方号段公告里的新增和变更记录写入一个临时表校验通过后再合并进主表最后重建索引并清掉查询缓存。def import_delta(delta_rows): 增量导入action 为 upsert 或 delete。 conn.execute(BEGIN) for r in delta_rows: prefix r[prefix] if r.get(action) delete: conn.execute(DELETE FROM region WHERE prefix ?, (prefix,)) else: conn.execute( INSERT INTO region(prefix, province, city, carrier, areacode, zip) VALUES(?,?,?,?,?,?) ON CONFLICT(prefix) DO UPDATE SET provinceexcluded.province, cityexcluded.city, carrierexcluded.carrier, areacodeexcluded.areacode, zipexcluded.zip , (prefix, r[province], r[city], r[carrier], r.get(areacode, ), r.get(zip, ))) conn.commit()这段代码用了事务包裹和ON CONFLICT DO UPDATE语法保证增量导入要么全部成功、要么全部回滚。SQLite 支持这个语法需要 3.24.0 以上版本老环境里要先跑SELECT sqlite_version();确认。更新线上库时更保险的顺序是把新数据导入临时表验证行数和抽样正确后用os.replace原子替换文件避免查询服务读到写了一半的库。4. 手机号码归属地数据库的避坑清单五条常见问题4.1 携号转网让「城市归属地」失真现象一个号码的号段属于北京移动查出来的归属地却是深圳用户投诉「你们的地理位置乱标」。原因携号转网后号码可以带着原来的号段迁移到其他运营商和地区而离线号段表记录的是号段初始发放地根本感知不到用户转网。解决对外展示口径必须叫「号段归属地」不能叫「用户所在地」。在产品文案里加一句「该信息为号段参考可能受携号转网影响」同时不要把归属地作为强风控依据尤其不要因为归属地和业务城市不一致就一刀切拒绝。4.2 虚拟运营商和物联网号段没有城市粒度现象查 170 开头的号码返回的城市五花八门同一号段在多个城市之间横跳查 14x 开头的物联网号段有时连城市都没有。原因170、171 以及后续新发的 16x、17x 中有一部分属于移动通信转售业务号段粒度只发到省甚至只标记为「转售」14x 号段多用于物联网卡并不遵循普通手机号的地区规划。解决在号段表里为这些特殊号段单独标记查询接口对它们只返回省份或返回「虚拟运营商」不强制拼城市。你可以加一个special_flags字段在清洗脚本里用白名单规则维护。4.3 数据源按行数注水清洗后剩一半现象下载的库号称几十万行导入后按prefix去重只剩几万行而且明显出现连续生成的假号段。原因部分卖家按文件行数计价会用程序批量生成「号段」凑数也有开源维护者在合并多个版本时忘了去重。解决清洗脚本必须做 prefix 唯一约束导入后用SELECT COUNT(DISTINCT prefix)对账再用运营商官方号段公告里能查到的号段列表做抽样比对覆盖率明显偏低的库直接放弃。另一个信号是文件里的号段连续成段、城市名对不上省份这种数据后续维护成本远高于重新整理。4.4 LIKE 查询和全量展开让性能翻车现象线上查询服务刚开始挺快数据量涨到几十万行后响应开始抖动还有人把 11 位号码全量展开存在数据库里几十 GB 内存都不够。原因查询用了LIKE %...%主键索引完全失效或者把 7 位号段表展开成了每个号码一行规模扩大了上千倍。解决查询只允许前 7 位等值匹配号码校验后再截断批量查询拆分IN子句对高并发服务启动时把号段表加载成内存字典不要指望数据库缓存。记住一个量级概念号段表只有几十万行展开成号码表就是几亿行存储和查询成本完全不是一个级别。4.5 区号和邮编列被旧库带错位现象查深圳的号段返回区号 020查广州的号段返回 0755风控系统按区号做判断时大面积误伤。原因很多老库的区号列是后来批量填充的按城市名匹配时出现了同名、省直辖县等边界问题导致列与城市错位。解决不要把区号和邮编当作权威数据只当作展示字段。更新时可以建一张「省份 城市 → 区号」的独立映射表用城市名精确匹配后批量回写匹配不到的置空而不是硬填。这样即使区号列错了也不会影响省份和城市这两个核心字段的正确性。5. 验证号段库的三种方法与离线增量更新流程先做三样验证再谈更新。第一样是行数审计SELECT COUNT(*),SELECT COUNT(DISTINCT prefix), 再按运营商GROUP BY carrier看分布是否合理。第二样是抽样比对拿一批真实在用的号码尤其是近一年新发的 19x 号段验证库里的省份和运营商与手机实际办理信息是否一致。第三样是行政区划校验把号段表里的城市名和最新行政区划代码表做一次外键比对城市名对不上的行单独列出来通常就是需要更新的地方。这三步跑完库能不能上线基本心里有数。增量更新我建议按固定节奏跑每季度拉一次官方号段公告对比现有表找出新增、变更、失效三类记录先在临时表合并验证后再原子替换主库文件最后清空查询服务的前缀缓存。整个流程用脚本自动化不要拿生产库直接开改。我自己的教训是有一次升级没走事务直接对线上主表执行大批量 UPDATE跑到一半连接超时查询服务全部阻塞最后花了一整晚才回滚干净。从那以后我坚持「先临时表、再原子切换、后清缓存」三个动作宁可发布窗口多五分钟也不让数据库处于不可读状态。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

华为Atlas 300V上部署YOLO:ONNX转OM与ACL推理实战 2026/9/25 15:27:22

华为Atlas 300V上部署YOLO:ONNX转OM与ACL推理实战

1. 先搞明白:Atlas 300V 24G 到底是个什么东西先说结论:它是运算加速卡,而且是专门冲着 AI 推理去的加速卡,但不是传统意义上的“显卡”。很多人一上来就把它和 GPU 划等号,这个理解方向对了一半,但如果不搞…

阅读更多 →
Atlas 300V Pro推理加速卡YOLO部署实战指南 2026/9/25 15:27:15

Atlas 300V Pro推理加速卡YOLO部署实战指南

1. 一块被误解最多的"运算加速卡":先给Atlas 300V Pro正名"atlas 300v 24g 是运算加速卡吗"——这个热搜词我太熟了,几乎每隔几天就会在技术社群里看到类似提问。包括"atlas部署yolo"这个搜索组合,说明很多人是…

阅读更多 →
Agent技能化改造:从杂乱工具到可复用技能库的工程实践 2026/9/25 15:27:15

Agent技能化改造:从杂乱工具到可复用技能库的工程实践

1. 从“有模型”到“会干活”:为什么我重新思考了Agent的技能组织方式大概从去年下半年开始,我就不太愿意跟人聊“你接入了几个大模型”这种话题了。原因是,模型本身的差距在缩小,真正拉开体验差距的,恰恰是模型外面那…

阅读更多 →
SSRF漏洞详解:从原理、绕过到内网渗透与修复实战 2026/9/25 15:27:02

SSRF漏洞详解:从原理、绕过到内网渗透与修复实战

先声明一句:我在安全测试这条路上认识SSRF有几年了,真正让我重视它的是某次授权渗透里,一个看似不起眼的URL输入框,直接让我拿到了内网一台数据库的血拼权限。SSRF全称是Server-Side Request Forgery,服务端请求伪造&a…

阅读更多 →
Codeg浏览器自动化原理:隔离世界+ARIA树的跨导航元素引用安全设计详解 2026/9/25 15:27:02

Codeg浏览器自动化原理:隔离世界+ARIA树的跨导航元素引用安全设计详解

Codeg浏览器自动化原理:隔离世界ARIA树的跨导航元素引用安全设计详解 【免费下载链接】codeg Collaborative multi-agent AI coding workspace: aggregate sessions from Claude Code, Codex, OpenCode, Pi, Grok Build, etc. Desktop app, self-hosted server, or …

阅读更多 →
体育赛事直播录屏黑屏的5种实战解决方案 2026/9/25 15:26:55

体育赛事直播录屏黑屏的5种实战解决方案

1. 问题本质与真实场景还原:黑屏不是故障,是信号链路上的“断点”“体育赛事直播录屏黑屏”这个标题,乍看像一个简单的技术故障,但实际踩过坑的人知道——它根本不是软件报错、不是硬盘满了、也不是显卡驱动崩了。它是一条完整信号…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉