☰
360569条手机号码段归属地数据:从MySQL建表到Excel查询全指南
2026/10/14 7:40:41 网站建设 项目流程

简介:全国手机号码段归属地数据库是一份面向开发者、数据分析人员和市场运营者的实用数据包,用于快速查询手机号码归属地、识别运营商及区域信息。包内共4个文件,包含SQL脚本、CSV数据表以及两个txt辅助文档,压缩包整体仅7.48MB。其中SQL脚本可导入MySQL等关系型数据库实现高效管理,CSV数据可直接用Excel打开进行排序、筛选和查找。该数据库收录360569条手机号码段记录,涵盖号码前7位、运营商、省份、城市、区号、邮编等关键字段,能够支撑反欺诈风控、地域化营销和客户服务本地化等场景。目前已有5054人学习下载,适合需要构建号码归属地查询系统或开展区域分析的人员直接调用。数据字段结构清晰,配合说明文档可快速理解字段含义,减少人工清洗成本。

1. 360569 条手机号码段归属地记录,先把它的边界说清楚

一份收录了 360569 条号码段记录的全国手机号码段归属地数据库,核心价值是把「某个手机号大概从哪来」从人工猜测变成秒级查询。实际数据里存的是号码前 7 位——号段加中间四位——对应省份、城市和运营商,属于号段级覆盖,不是逐个号码定位。对运营做客户分地域运营、对风控做号码归属地初筛、对数据分析做省份分布统计,它都是最省成本的底表。

这套数据适合三类人:一是要快速上线归属地查询接口的后端开发,二是需要给几百万号码打地域标签的数据分析师,三是用 Excel 做客户回访分区的运营。我会把重点放在落库、查询、避坑和维护四条线上,照着抄就能跑通。先把一个观念摆正:360569 条看着多,实际是「号段组合」的规模,真正要建好的是索引和更新机制,而不是数据本身。

2. 号码段归属地数据库的数据结构:11 位号码拆开看

2.1 号码前 7 位决定归属地,后 4 位只是用户序号

国内手机号一共 11 位,这 11 位不是随机分配的,结构上有明确分工:

  • 前 3 位:网络识别号,也就是我们常说的号段,比如 138、159、188;
  • 中间 4 位:地区编码,标记这个号码在哪个本地网开户;
  • 最后 4 位:用户序号,由运营商在本地网内顺序放号。

所以一条归属地记录的粒度,应该是「前 3 位 + 中间 4 位」组成的 7 位前缀。比如某个号码前 7 位是 1381234,那么它的前 7 位就能确定省份和城市;后 4 位只有区分用户的作用,不携带地域信息。网上流传的号码归属地数据,常见的也是按 7 位前缀存储,一行记录代表 10000 个连续号码。360569 条记录对应的就是号段级的覆盖规模,不是手机号数量。

把字段理解到这个层面,你就不会犯「用前 3 位去查城市」的低级错误。前 3 位只能区分到运营商级别,同一个 138 号段在多个省份都有放号,必须再带上中间 4 位才能落到城市。这也是为什么拿到数据后第一个处理动作,是把前 3 位和中间 4 位拼成一个完整的 7 位前缀字段。

2.2 核心字段与表结构设计

拿到一份号码段归属地数据,我一般先看它有没有这些字段:

字段含义说明
prefix号码前 7 位查询主键,必须唯一
province省份归属地省份名称
city城市归属地城市名称
carrier运营商基础运营商或虚拟运营商
area_code区号固定电话区号,可选
zip_code邮编可选,实际查询用得少

设计表结构时,主键毫不犹豫选 prefix。常见错误是用自增 id 做主键,再给 prefix 建普通索引。这样也能跑,但每次查询都多一次回表,纯属浪费;在 36 万行的规模下差距不大,可一旦后面增量更新到百万行,前缀作为主键的优势会越来越明显。

数据类型上,prefix 用 char(7) 而不是 varchar(7)。因为这个字段长度固定,char 的检索效率更高,而且不会因为长度可变产生碎片。省份、城市字段用 varchar(32) 就够,运营商字段 varchar(16) 足够。整个表没有数字类型的排序需求,所以不要为了省空间把 prefix 转成 bigint,字符类型在匹配时更直观,也不会出现前导零丢失这类隐患。

2.3 存储选型:为什么不用「起止区间表」,而用前缀精确匹配

我见过不少人拿到号段数据后,第一反应是设计一张「起始号码—结束号码」的区间表,然后查询时用where start_num <= phone and phone <= end_num。这个设计看着通用,实际坑很大:手机号码分配不是连续块,中间有大量未放号和回收号段,区间表会在边界处反复命中多条记录,还得额外处理重叠区间。

另一个常见选型是按前 3 位做聚合表,只存 138、159 这种三位号段。这样做查询极快,数据量也很小,但代价是城市信息必然丢失,因为同一 3 位号段横跨多个城市。如果你的业务只需要省份粒度,可以接受;一旦需要城市粒度,就必须回到 7 位前缀。

所以我的建议很直接:用 7 位前缀作主键的精确匹配表。36 万行数据量对 MySQL、SQLite、PostgreSQL 都是小意思,单次查询就是主键等值查找,毫秒级返回。如果查询量极大,还可以把整表加载到 Redis 哈希结构里,key 是前缀,value 是归属地字符串,或者用内存前缀树做最长前缀匹配——但那是十万级 QPS 的场景,绝大多数项目用 MySQL 加主键查询就足够了。

2.4 建表语句与一次查询示例

下面这段建表语句可以直接执行,字符集选择 utf8mb4,避免后面遇到生僻城市名或特殊符号时出现乱码:

CREATE TABLE phone_location ( prefix CHAR(7) NOT NULL COMMENT '手机号前7位', province VARCHAR(32) NOT NULL DEFAULT '' COMMENT '省份', city VARCHAR(32) NOT NULL DEFAULT '' COMMENT '城市', carrier VARCHAR(16) NOT NULL DEFAULT '' COMMENT '运营商', area_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '区号', zip_code VARCHAR(10) NOT NULL DEFAULT '' COMMENT '邮编', PRIMARY KEY (prefix), KEY idx_province (province) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='手机号码段归属地表';

查询时先截取前 7 位,再走主键精确匹配:

SELECT province, city, carrier FROM phone_location WHERE prefix = LEFT('13812345678', 7);

LEFT函数截取前 7 位后直接命中主键,执行计划里会显示主键查找,不需要全表扫描。这里有一个值得注意的细节:不要在prefix上套函数再比较,比如WHERE UPPER(prefix) = ...,一旦对索引字段做函数运算,索引就失效了。正确做法是先算出 7 位前缀,再用等值条件去匹配。

3. 把 360569 条记录从 Excel/CSV 落进 MySQL

3.1 格式检查与编码转换

从网上下载的号码归属地数据通常以 Excel 或 CSV 形式分发。Excel 文件可以直接用工具导出为 CSV,但导出时的编码经常是 GBK 或 GB18030,直接导入 MySQL 会出现中文乱码。我一般先在命令行确认编码格式:

file phone_location.csv

如果输出里带有ISO-8859或GB2312字样,就需要先转码。转成 UTF-8 的常用做法是用 iconv:

iconv -f GB18030 -t UTF-8 phone_location.csv > phone_location_utf8.csv

编码转换完成后,再用 head 命令检查前几行,确认中文正常、分隔符一致:

head -n 5 phone_location_utf8.csv

这一步不要跳过。很多导入翻车不是 SQL 写错,而是文件里混了 BOM 头或分隔符不统一。看到第一行开头有类似\ufeff的字符,说明带 BOM,导入时需要额外处理。

3.2 使用 LOAD DATA 批量导入真实数据

用 MySQL 的LOAD DATA导入比逐行 INSERT 快数量级,36 万行数据通常几秒完成。假设 CSV 的表头是prefix,province,city,carrier,area_code,zip_code,导入语句如下:

LOAD DATA LOCAL INFILE '/data/phone_location_utf8.csv' INTO TABLE phone_location CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (prefix, province, city, carrier, area_code, zip_code);

IGNORE 1 LINES跳过表头;OPTIONALLY ENCLOSED BY '"'处理字段值里带引号的情况;字符集必须和转码后的文件一致。这里容易踩的坑是文件路径权限:LOCAL关键字表示文件在客户端本地,MySQL 服务端需要有对应配置才能读取。

导入完成后立即做一次计数和去重检查:

SELECT COUNT(*) AS total, COUNT(DISTINCT prefix) AS unique_prefix FROM phone_location;

正常情况下 total 和 unique_prefix 应该相等,如果 distinct 数量比 total 少,说明源文件里有重复的前缀记录,后面需要做去重处理。

3.3 缺运营商、缺城市字段的修正

导入后还有一道常见工序:补全缺失字段。虚拟运营商号段经常在源数据里运营商字段为空,或者被标记成不明。这种情况下可以用 UPDATE 语句批量填充:

UPDATE phone_location SET carrier = '虚拟运营商' WHERE prefix LIKE '170%' OR prefix LIKE '171%';

注意这里用LIKE '170%'是合理的,因为前缀是定长 7 位,170开头的号段确实属于虚拟运营商范畴。但如果你的业务要求精确到具体运营商,就会遇到一个更细的问题:同是 170 开头,不同中间四位分属不同基础运营商,后面在避坑章节我会专门展开。

还有一部分记录城市字段为空,这种情况查询时展示效果很差。处理策略有两种:一是用省份补全,让前端展示「某省 未知市」;二是保持空值并在查询时做兜底。我一般选择前者,因为业务方看到空值会误以为数据有问题,而「省份 + 未知市」至少能给出可用信息。

3.4 封装一个查询函数

数据落库后,业务代码里最常见的需求是「传入手机号,返回归属地」。用 Python 封装一个最小可用的查询函数:

import pymysql def query_location(phone, conn=None): """ 根据手机号查询号码段归属地 :param phone: 11位手机号字符串 :param conn: pymysql 连接对象,为空时自动创建 :return: dict,包含省份、城市、运营商 """ if not phone or len(phone) != 11 or not phone.isdigit(): return None prefix = phone[:7] sql = "SELECT province, city, carrier FROM phone_location WHERE prefix = %s" if conn is None: conn = pymysql.connect(host='127.0.0.1', user='user', password='pass', database='phone_db', charset='utf8mb4') try: with conn.cursor() as cursor: cursor.execute(sql, (prefix,)) row = cursor.fetchone() finally: conn.close() else: with conn.cursor() as cursor: cursor.execute(sql, (prefix,)) row = cursor.fetchone() if row: return {"province": row[0], "city": row[1], "carrier": row[2]} return None

参数说明里最关键的一点:不要把拼接好的 SQL 直接传给 execute。上面用%s占位符传参,由驱动处理转义,能避免注入风险。另外phone[:7]是在 Python 侧完成的,这样 SQL 里只做等值匹配,索引一定用得上。函数返回 None 的情况要单独处理,业务侧一般提示「未知归属地」,而不是直接报错。

提示:如果查询量日均百万次,不建议每次查询都新建连接。把连接对象做成全局复用,或使用连接池,能显著降低握手开销。

4. 不会写代码?Excel 里也能做归属地查询与省份统计

4.1 避免 CSV 打开乱码:用「自文本/CSV」导入而不是双击打开

拿到 CSV 文件后直接双击,Windows 上的 Excel 通常会用系统默认编码打开,中文十有八九变成乱码。正确做法是先打开一个空白工作簿,然后通过「数据」→「自文本/CSV」导入,在向导里把文件编码选为 UTF-8,分隔符选逗号。

导入时有一个细节:如果源文件第一行是表头,务必勾选「我的数据具有标题」。这样表头会被识别为字段名,后续用 VLOOKUP 时可以直接引用列名,减少公式出错的可能。

如果导入后发现某个列变成了科学计数法,比如 7 位前缀显示成1.38123E+06,说明 Excel 把它当成数字了。解决方法是选中这一列,把单元格格式设为文本,然后重新导入。不要把已经是科学计数法的数据强行改格式,那样数值精度已经丢了。

4.2 VLOOKUP 按前 7 位查归属地

Excel 里查归属地的核心公式是 VLOOKUP。假设:

  • 手机号放在 Sheet1 的 A2 单元格;
  • 号码段数据放在 Sheet2 的 A 到 D 列,A 列是 prefix,B 列是省份,C 列是城市,D 列是运营商;
  • 数据有 360569 条记录,加上表头一行,所以数据范围是$A$2:$D$360570。

公式写为:

=VLOOKUP(LEFT(A2,7), 号码段!$A$2:$D$360570, 2, FALSE)

这里四个参数分别是:查找值、查找区域、返回列序号、匹配方式。LEFT(A2,7)从手机号里截取前 7 位作为查找键;返回列序号 2 表示取省份,改成 3 取城市,改成 4 取运营商。关键在最后的FALSE,它表示精确匹配。如果漏掉或写成 TRUE,VLOOKUP 会走近似匹配,结果完全不可控,这是 Excel 查询归属地最隐蔽的翻车点。

公式填充到整列后,对于查不到记录的行,会得到#N/A。这时候可以用 IFERROR 包一层,让结果显示为「未知」:

=IFERROR(VLOOKUP(LEFT(A2,7), 号码段!$A$2:$D$360570, 2, FALSE), "未知")

4.3 用数据透视表快速统计省份分布

数据量大了以后,想在 Excel 里看各省分布,直接 COUNTIF 逐个数既慢又容易错。更高效的方式是插入数据透视表:

  1. 将手机号列表和一列「省份」结果放在同一个工作表;
  2. 选中数据区域,插入透视表;
  3. 「省份」拖到行区域,「手机号」拖到值区域,值汇总方式选「计数」。

这样几秒就能得到每个省份的号码数量,还能按计数降序排序,找出客户最集中的区域。需要注意的是,如果手机号这一列在 Excel 里被识别成了数字,透视表计数时可能因为精度问题出现重复计数,所以源数据里的手机号列必须设置成文本格式,或者导入时指定列类型。

提示:Excel 处理 36 万行数据会明显变卡,如果你的号码数量超过几十万,还是建议走 MySQL 或数据库方案,Excel 适合做抽样观察和临时分析。

5. 号码段归属地数据在实战中的 5 个高频翻车点

5.1 虚拟运营商号段被当成「未知」丢弃

现象:170、171、167 开头的手机号查询结果为空,或者运营商字段显示「未知」,但其他号段一切正常。

原因:很多来源数据的虚拟运营商号段是分批补充的,早期版本里要么缺失,要么被统一标成了「其他」。直接拿旧库上线,这批号码全部查不出来。

解决:先统计缺失情况,SELECT prefix, province, city FROM phone_location WHERE carrier = '',再把虚拟运营商号段单独建一张映射表,或者用 UPDATE 批量补上。注意 170 和 171 不是唯一入口,167、162 等新段也要留意。遇到查询不到的情况,最稳的做法不是手工加一条,而是从运营商公示信息核对后补录。

5.2 携号转网后归属地与当前运营商对不上

现象:某个号码的号段归属地明明在某省,但用户实际使用中显示的运营商和数据库不一致,甚至客服系统里查到的信息与数据库冲突。

原因:号码段归属地数据记录的是「号段初始分配时的开户地」,而不是号码当前状态。携号转网实施后,号码可以带着原号段转移到另一家运营商,数据库里的 carrier 字段就不再等于用户当前运营商。

解决:在业务上把「号段归属地」和「当前运营商」拆成两个概念。风控场景里,号段归属地只能作为辅助特征,不能作为拒绝的唯一依据;可以在数据表里额外加一个字段记录更新批次,提示数据是某个时间点的快照。如果业务强依赖当前运营商,必须走运营商侧查询接口,而不是拿号段库硬顶。

5.3 Excel 把号码段变成科学计数法

现象:CSV 打开后,7 位前缀显示成1.38123E+06,后面的位数变成 0。查询时匹配不到任何记录。

原因:Excel 对超过一定位数的数字自动转科学计数法,并且只保留 15 位有效数字。手机号 11 位、前缀 7 位看着不长,但在某些列被识别为数字后,处理过程会截断精度。

解决:导入 CSV 时,把所有号码列和前缀列设为文本格式;如果是已经打开的文件,使用「分列」功能,第一步选「分隔符号」,第三步把列格式选为「文本」,再点完成。这个操作相当于把 A 列重新按文本解析,能找回被科学计数法吃掉的部分。记住一个原则:手机号、号段这类标识符永远按文本处理,不要按数值处理。

5.4 去重之后数量对不上账

现象:源数据 360569 条,做完去重后只剩 35 万多条,或者反过来,去重后比源数据还多,对不上题目描述的记录数。

原因:号码段数据里可能存在一 prefix 对应多行的情况,比如同一个前 7 位在不同批次里归属地有修正,源文件没做合并;也可能源文件本身把号段和城市做了笛卡尔展开,导致同一 prefix 出现多次。

解决:去重逻辑要按 prefix 做主键,而不是按整行所有字段。正确的做法是保留每组 prefix 里更新时间最新、信息最完整的一条。用 SQL 实现就是:

UPDATE phone_location p JOIN ( SELECT prefix, MAX(id) AS keep_id FROM phone_location GROUP BY prefix HAVING COUNT(*) > 1 ) t ON p.prefix = t.prefix AND p.id != t.keep_id SET p.deleted_flag = 1;

跑完后把 deleted_flag 为 1 的行删掉,再重新统计总数。如果你拿到的是新数据并且还没建表,直接在建表阶段把 prefix 设为主键,重复记录会在导入时报错,从源头挡住。

5.5 新号段入库不及时造成的「查无此段」

现象:某天业务反馈,一批新办的手机号全部查不到归属地,人工一查发现这些号码的前 7 位在数据库里完全没有记录。

原因:号码段是动态资源,运营商不断推出新号段。静态的归属地数据库如果长时间不更新,对新增号段就是盲区。早期数据里没有 191、192、193、195、196、197、198、199 这些新段很常见。

解决:建立了增量更新机制,后面第 6 章会细说。这里先提醒一个判断技巧:查不到记录时,先用肉眼确认前 7 位是否存在,再确认是否是近几个月新增的号段,不要一上来就怀疑程序写错了。排查时先看数据更新时间,超过半年没更新的库,新号段查不到是正常的。

6. 让这张 360569 条的表长期可用:更新与验证

6.1 抽样核对校准法

数据库里的归属地信息不是一成不变的,定期校准是刚需。我的习惯是每月随机抽取 500 个号码段,去运营商官方线上渠道核对省份城市。核对结果做一张准确率统计表,记录三项指标:省份准确率、城市准确率、运营商准确率。

校准的抽样不能只抽头部号段,要用随机数分散到各个号段区间。方法是先SELECT prefix FROM phone_location ORDER BY RAND() LIMIT 500,再把这批 prefix 交给人工或半自动脚本核对。如果城市准确率低于某个阈值,比如 95%,就说明这份库时效性不足,需要整体更换数据源。准确率长期维持在很高水平,也不要掉以轻心,新增号段不在旧库里,抽样覆盖不到,得靠增量更新补盲区。

6.2 增量合并新号段

拿到新增号段后,合并动作要做得比全量替换轻量。使用INSERT IGNORE或ON DUPLICATE KEY UPDATE都能实现「有则更新、无则插入」:

INSERT INTO phone_location (prefix, province, city, carrier, area_code, zip_code) VALUES ('1991234', '某省', '某市', '运营商A', '', '') ON DUPLICATE KEY UPDATE province = VALUES(province), city = VALUES(city), carrier = VALUES(carrier);

每次增量合并前,先把新号段存到一个临时表里,核对无误后再合并,避免把脏数据带进主表。合并后跑一遍覆盖率检查,确认新增号段已经入库:

SELECT COUNT(*) FROM phone_location WHERE prefix LIKE '199%';

覆盖率检查还需要对比运营商公示的号段清单,做差集。这个差集就是下一次要补的增量。整个过程建议写成脚本,每月执行一次,输出新增记录数、更新记录数和异常记录数。

我之前接过一次归属地查询的项目,上线前图省事拿了一份旧数据直接跑,结果虚拟运营商号段整段查不到,被业务方追着问了很久。后来把抽样核对和增量更新做成例行步骤,才真正稳下来。号码段归属地数据本身不复杂,难的是别把它当成静态字典——用起来越早建立更新节奏,后面越省心。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询