基于号段表的手机归属地查询:Excel数据清洗、MySQL导入与Python工具实现
简介全国手机号码段归属地数据库是面向开发者、数据分析师及运营人员的实用数据包覆盖国内主要运营商号段可用于手机号属地识别、地域统计与快速查询等场景。压缩包内共4个文件包含SQL脚本、CSV数据表及TXT说明文档整体大小7.48MBSQL脚本提供建表语句和INSERT导入数据可直接在MySQL等关系型数据库执行CSV文件则可用Excel或WPS打开实现排序、筛选、透视等操作尤其适合不熟悉数据库查询的日常办公人员。数据总计360569条记录每条包含手机号码段、运营商、省份、城市、区号、邮编等关键字段覆盖移动、联通、电信全量号段信息既可作为反欺诈风控中的号码风险识别依据也能用于地域化营销、客户服务及通信消费习惯分析。目前已有5053人学习下载说明该数据集在开发者和业务人员中具备较高的实用价值。读者可直接将SQL文件导入数据库以构建查询接口或用CSV文件做离线统计从而快速为业务系统增加手机号归属地能力。1. 全国手机号码段归属地数据库一张36万条记录的表解决归属地查询做风控标注、客服工单分拣或者快递发货校验的时候拿到一个陌生手机号先确认它属于哪个省哪个市几乎是每天都要重复的操作。有人去搜网页接口有人装个归属地查询APP折腾半天还把手机号喂给了第三方。其实一张本地的号段表就够了这份全国手机号码段归属地数据库收录了360569条记录按手机号前7位匹配省、市、运营商和区号以Excel格式交付。适合做离线批量查询、数据标注、测试号码生成的人数据是静态快照拿来当离线字典用非常顺手。2. 号码段数据的底层结构与Excel实操前7位规则、字段清洗与快速验证2.1 前7位决定归属地手机号的数据划分逻辑国内手机号共11位结构拆开看是三层前3位是网络识别号标识运营商和网络制式第4到第7位是HLR识别码标识归属地交换局最后4位是用户号。所以运营商把一个号段投入市场时实际上是以“前7位”为单位申请的号段表的匹配键天然就是前7位。这份Excel数据里的每条记录对应一个已分配的号段后面挂着省份、城市、运营商和区号。理解了这点就知道为什么号段表不是按“每个手机号一行”来组织的——11位号码全量的组合是千亿级而号段数量只有几十万级别。360569条这个量级基本覆盖了已经分配的公众移动通信网号段。拿到一个完整手机号时截取前7位去查表就行不需要也不可能为每个号码建一条数据。字段上这份表通常包含号段前7位、省份、城市、运营商、区号。区号这个字段容易被忽略但实际作用不小做客服中心选址、判断外呼号码是否异地、拼接本地号码格式时都能用到。运营商字段一般标的是移动、联通、电信遇到虚拟运营商和广电号段时会有差异这一点在第4章展开。2.2 Excel里的常用字段与清洗习惯拿到Excel后第一件事不是急着查数据而是先做三步清洗。第一步检查表头。常见表头可能是“号段/省份/城市/运营商/区号”也可能叫“号码段/所属省/所属市/运营商/长途区号”。字段名不同没关系关键是确认列顺序后面导入数据库或写脚本时按列顺序映射。第二步把号段列格式设为文本。Excel对纯数字列有类型推断虽然前7位号段没有前导零问题但如果之后要在这个表旁边维护一张完整手机号表11位手机号会被Excel自动转成科学计数法显示到那时再匹配就会出各种幺蛾子。我一般会把号段列和手机号列全部设为文本避免后续匹配时由于格式不一致导致#N/A。第三步去重和空值检查。号段表理论上不该有重复但手工整合过的Excel不一定干净。用COUNTIF对号段列做重复检测比如在辅助列写COUNTIF(A:A,A2)筛选出大于1的行逐条确认。空值检查用筛选看看省份、城市、运营商有没有空行。空行会影响后续的VLOOKUP和LOAD DATA导入提前清掉比在数据库里抓错省事得多。2.3 用Excel快速验证一条数据清洗完了先用VLOOKUP验证一下表能不能用。假设号段在A列从第2行开始手机号在另一个工作表或本表F列通过LEFT函数截取前7位再匹配公式是这样VLOOKUP(LEFT(F2,7),$A:$E,2,FALSE)注意这里匹配列是A列号段返回第2列是省份。如果要把城市、运营商也带出来把列索引改成3、4继续写就行。LEFT(F2,7)先从完整手机号里切出前7位再放进VLOOKUP里和号段列做精确匹配FALSE保证匹配方式为精确查找不要用近似匹配。我在实际工作中经常用这个公式快速核对一批测试号码。把几百个手机号粘进去拉一下公式哪个号段查不到立刻就能筛出来。查不到的号段别急着认定数据有问题先用第4章里的方法排查是不是新发放号段或者虚拟运营商。3. 把Excel号段表导入数据库从建表到查询的全流程配置3.1 建表结构与字段类型选择Excel适合手动验证但要支撑线上查询或者批量JOIN还是得落到数据库里。拿MySQL举例表结构我会这样建CREATE TABLE phone_prefix ( prefix CHAR(7) NOT NULL COMMENT 手机号前7位含前导零, province VARCHAR(32) NOT NULL COMMENT 省份, city VARCHAR(32) NOT NULL COMMENT 城市, carrier VARCHAR(16) NOT NULL COMMENT 运营商, area_code VARCHAR(8) DEFAULT NULL COMMENT 区号, PRIMARY KEY (prefix) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT手机号码段归属地表;字段类型这里有几个关键选择。prefix用CHAR(7)而不是INT或BIGINT因为号段虽然在当前是7位数字但本质是标识符不是数值用字符串类型可以避免将来出现前导零号段时被隐式转换吃掉。province和city用VARCHAR(32)够用运营商名称最长也就几个字VARCHAR(16)留了余量。area_code允许为空有些号段数据里没填区号用DEFAULT NULL比存空字符串更干净。主键直接设在prefix上这个字段天然唯一同时作为查询索引用不需要额外加索引。字符集选utf8mb4而不是utf8原因很实际utf8在MySQL里是utf8mb3存不了生僻字和部分特殊符号utf8mb4是完整实现。省份城市字段虽然大概率不涉及生僻字但导入工具或后续手工UPDATE时可能混入特殊字符选utf8mb4省的将来报错。3.2 Excel导入MySQL的两种方式导入有两种常见路径。第一种是图形化工具用Navicat的“导入向导”选Excel文件像这样操作导入类型选Excel文件指定文件路径目标表选phone_prefix字段映射时把Excel列按顺序拖到表字段上勾选“首行作为字段名”跳过表头字符集选UTF-8最后点开始。这种方式适合Excel文件里有合并单元格、宏一类复杂格式的情况工具会自己处理一部分兼容问题。第二种是命令行LOAD DATA适合服务器环境或者要把导入步骤固化成脚本的场景LOAD DATA LOCAL INFILE /data/phone_prefix.csv INTO TABLE phone_prefix FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (prefix, province, city, carrier, area_code);用LOAD DATA之前有个前置动作把Excel另存为CSV文件。注意另存时编码要选UTF-8Excel默认存成GBK直接LOAD进utf8mb4的表会导致中文乱码这一步在第4章会详细说。FIELDS TERMINATED BY ,指定CSV的列分隔符是英文逗号OPTIONALLY ENCLOSED BY 处理字段值里可能带双引号的情况常见于城市名或运营商名被Excel自动加了引号。IGNORE 1 LINES跳过第一行表头然后按顺序把CSV的五个列喂给表的五个字段。3.3 日常查询SQL与性能验证数据导进去之后最常用的查询是给定一个手机号找归属地SELECT province, city, carrier FROM phone_prefix WHERE prefix LEFT(13812345678, 7);LEFT函数从完整手机号里截取前7位和prefix主键做等值匹配。因为prefix是主键这步查询走的是索引查找单条数据耗时在毫秒级。我习惯用EXPLAIN确认一下执行计划看到type列为const或ref就说明命中了索引如果看到ALL就需要回头检查主键是否设置成功。批量查询的场景更常见比如一次给几百个号码打归属地标签。这时候不要一条条查用临时表JOIN效率高得多CREATE TEMPORARY TABLE tmp_phones (phone VARCHAR(11) NOT NULL PRIMARY KEY); INSERT INTO tmp_phones VALUES (13812345678), (15912345678), (18612345678); SELECT p.phone, pf.province, pf.city, pf.carrier FROM tmp_phones p LEFT JOIN phone_prefix pf ON pf.prefix LEFT(p.phone, 7);这里LEFT JOIN比INNER JOIN更适合业务场景查不到归属地的号码也要返回结果只是后面三个字段为NULL一眼就能看出哪些号码的号段不在表里。查出来之后把NULL的行汇总一下就是需要人工核实的号段清单。4. 避坑与常见问题排查号段数据的边界与五个常见误用4.1 虚拟运营商号段查不到或运营商不准现象输入170、171、165、167、162开头的号码返回的省份能匹配但运营商一栏标的是“虚拟运营商”或者干脆查不到。原因虚拟运营商租用三大运营商的网络资源号段码本身是单独分配的传统号段表里要么没收录要么归到某个基础运营商名下导致显示与实际套餐的虚商品牌不一致。解决先确认表里是否包含这些号段记录包含的话按现有标记用即可不包含的话自己维护一张虚商号段映射表把170~1709等号段对应到实际虚商品牌因为虚商在不同地区的放号情况不同这块没有统一标准只能根据业务实际覆盖范围补充。4.2 携号转网让归属地“失真”现象一个号码原来是移动的用户办理携号转网后变成了联通但查询结果还是显示移动。原因号段表是以“号段发放时的归属运营商”为基准建立的携号转网改变的是用户当前的签约运营商不改变号码本身。号段归属是静态属性用户归属是动态状态一张静态表无法反映后者。解决不要用号段表判断用户当前的真实运营商这类判断必须依赖运营商侧接口或用户自助上报。号段表适合的是“归属地省份/城市”这类几乎不随转网变化的属性。做业务规则时把“运营商”字段当成“号段原始运营商”理解不要在风控策略里用它做唯一决策依据。4.3 Excel里号码变成科学计数法导致VLOOKUP查不到现象在Excel里维护手机号表输入13812345678后单元格显示成1.38123E11用VLOOKUP去号段表里匹配明明号段存在却返回#N/A。原因Excel默认把超过一定长度的纯数字列转成科学计数法格式单元格显示变了实际存储值也被转成浮点数。号段表里的prefix如果没设成文本两边类型不一致VLOOKUP精确匹配直接失败。解决选中手机号列和号段列单元格格式统一设成“文本”。已经变成科学计数法的用“数据→分列→文本”方式恢复分列向导里第1步选“分隔符号”第2步不选任何分隔符第3步列数据格式选“文本”就能把科学计数法还原成完整号码。匹配公式里再包一层TEXT(A2,0)强制转文本双保险。4.4 导入数据库后中文乱码现象用LOAD DATA导入CSV后SELECT查询发现省份城市全是问号或乱码但Excel里看是正常的。原因Excel另存为CSV时默认编码是GBK而MySQL表用了utf8mb4LOAD DATA按表的字符集解析GBK文件时直接解码失败中文全变“???”。解决另存CSV时在“另存为”对话框里选择“CSV UTF-8”格式而不是默认的“CSV”。如果文件已经存成CSV用文本编辑器另存为UTF-8编码再导入。LOAD DATA语句里也可以追加CHARACTER SET utf8mb4声明指定按该字符集解析文件同一条语句示例LOAD DATA LOCAL INFILE /data/phone_prefix.csv INTO TABLE phone_prefix CHARACTER SET utf8mb4 FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (prefix, province, city, carrier, area_code);4.5 新号段不断发放导致数据过期现象用门店近期办理的新号码测试发现号段表里查不到或者省份显示完全不对。原因号段不是一次性分配完的运营商和主管部门会不定期批复新号段。手头这份36万条的数据是某个时间点的快照之后新增的号段自然不在覆盖范围里。解决把号段表当成“基线数据”而不是“实时权威数据”。每隔一段时间抽样测试用最近三个月的新办号码跑一遍查询查不到的存进补录表。如果业务对覆盖敏感可以定期手动在Excel末尾追加新号段再重新导入数据库。注意甄别网上流传的“最新号段”整理贴最好以可验证的真实号码为准。5. 进阶用法用Python搭一个本地的号段归属地查询工具数据库查询适合复杂关联但有时候只是想在本地快速跑一批号码开MySQL显得笨重。我一般会用Python写一个几十行的查询工具把号段表载入内存字典查询复杂度O(1)处理几万条号码也不慢。import csv import sys PREFIX_FILE phone_prefix.csv def load_db(path: str) - dict: db {} with open(path, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: db[row[prefix]] row return db def locate(db: dict, phone: str): prefix phone[:7] return db.get(prefix) if __name__ __main__: db load_db(PREFIX_FILE) phones sys.argv[1:] or [13812345678] for p in phones: info locate(db, p) if info: province info.get(province, 未知) city info.get(city, 未知) carrier info.get(carrier, 未知) print(f{p}: {province} {city} {carrier}) else: print(f{p}: 未匹配到号段)脚本逻辑分三块。load_db读CSV建字典KEY是号段VALUE是整行数据locate截取目标手机号前7位做字典查询主流程里遍历传入的号码批量输出。需要注意CSV第一行必须是表头且字段名包含prefix、province、city、carrier如果表头命名不同把DictReader的字段名对应改成Excel里的实际列名即可。运行时直接传手机号参数比如python locate.py 13812345678 15912345678脚本会逐条打印归属地。不想启动MySQL又需要跑批量的场景下这个工具比打开Excel逐行VLOOKUP顺手得多。如果要更进一步把数据导成SQLite文件把上面的字典替换成sqlite3查询就能在Python里同时享受索引和零部署。做成HTTP接口也很容易用Flask或FastAPI包一层GET方法/locate?phone13812345678就能接到现有系统里。我这套工具在本地给运营做了一版现在每次接到新的号码样本我都会强制走一遍“先查号段表、再抽非对称样本人工复核”的流程把新增字段的匹配结果和线上老数据的统计口径做一致性对齐。这一套从Excel到字典的转换路径踩过不少坑慢慢成型之后后续再碰任何“静态字典型数据”都能直接复制这套思路。希望帮到你。本文还有配套的精品资源点击获取