简介:全国手机号码段归属地数据库收录了360569条手机号码段记录,以7.48MB的zip压缩包形式打包,共4个文件,包括SQL脚本、CSV数据表和两份txt说明文档。SQL脚本提供建表与插入语句,可在MySQL等数据库内快速创建号码段表并按前7位号码查询归属地;CSV文件可直接用Excel打开,支持按号码段、运营商、省份、城市、区号、邮编等字段排序、过滤和透视。这份资源面向开发人员、数据分析师及运营人员,适用于反欺诈风控、区域精准营销、客服本地化识别以及通信消费特征研究等场景。txt说明详细列出了字段含义与导入注意事项,方便使用者理解数据结构,省去自行采集、清洗和整理数据的繁琐步骤,基于此可在业务系统中实现毫秒级号码归属查询,或用于构建地域维度的用户画像。资源已有5054人学习下载,质量经过充分验证,是快速获取全国手机号码段归属地信息的实用数据集。
1. 手机号归属地资源:36万条离线数据,先解决“号段能不能查准”
做短信通道号段清洗时,我吃过一次亏:按手机号前三位判断归属地和运营商,结果虚拟运营商号段和固网号段混在一起,一次营销活动把错误归属地的用户全筛了进去。从那以后,我坚持用离线号段归属地数据库做预处理,而不是在线接口或随手抄来的号段表。
这份资源是“全国手机号码段归属地数据库”,Excel格式,共360569条记录。给定一个手机号或其前7位号段,能查到对应省份、城市、运营商。36万条记录覆盖绝大多数在网号段,查询不依赖在线API,没有频次和费用限制,适合批量离线清洗。
适合谁?做短信外呼运营的按归属地分通道,做风控反欺诈的甄别虚拟运营商号段,做用户画像的给手机号打地域标签。只查一两个号请直接百度,要查几万条时这份库的价值才体现出来。
2. 号码段的内在结构:11位怎么拆、字段粒度怎么定,这步决定了查询快慢
拿到手先别急着写SQL。手机号11位,可以拆成三段:前3位是网络识别号,粗略对应运营商;第4到第7位是HLR识别号,归属地信息主要藏在这4位里;后4位是用户随机号,跟归属地无关。所以号码段归属地查询的真正匹配键是前7位,不是前3位,更不是完整11位。这也是为什么很多网上流传的“按前3位查归属地”工具不准——前3位只能区分运营商,区分不了城市。
2.1 先认清字段:一份表里通常藏了四五个有效列
我拿到这类资源后,第一步不是写查询,而是先用一个脚本把文件读一遍,看表头和前几行数据。以这份库为例,常见的字段组织方式是:号段前缀、省份、城市、运营商,偶尔还有一列“卡类型”(比如区分普通卡、物联网卡)。
要特别说明一个坑:网上流传的号段库版本非常多,常见三种。一种是只有前7位号段和省市的精简表,条目一般几万条;一种是带完整11位号码明细的版本,几十万条;还有一种是加了虚拟运营商标记的扩展版。这份资源是360569条记录,属于中等偏全的级别——既不是简陋的几万条前缀表,也不是纯粹拿随机号凑数的明细表,适合直接集成到业务库中当字典用。
如果下载后打开发现表头和我说的不完全一样,不用慌,改一下脚本里的列名映射就行。关键列就四个:号码前缀(前7位)、省份、城市、运营商。前7位缺失或格式不对,这份数据基本没法用。
2.2 数据校验:先查文件完整性,再按省份看分布
确认字段后,做两个快速校验:文件是否完整、记录分布是否符合预期。一个36万行的文件,如果来源不正规,经常会有截断、空行、重复前缀问题。
我用下面这段Python脚本做基础体检,下载解压后可以直接跑:
import pandas as pd from collections import Counter # 读入数据,sep按实际文件分隔符调整 df = pd.read_csv("phone_location.csv", sep=",", dtype=str, encoding="utf-8") print("总记录数:", len(df)) print("空值统计:\n", df.isna().sum()) # 按前7位去重:归属地查询的最小粒度 df["prefix7"] = df["phone"].str[:7] unique_prefix = df.drop_duplicates(subset="prefix7") print("去重后号段数:", len(unique_prefix)) # 按省份看记录分布,检验是否覆盖全部省级行政区 prov_cnt = Counter(unique_prefix["province"]) print("省份数量:", len(prov_cnt)) for prov, cnt in prov_cnt.most_common(10): print(prov, cnt)这段脚本做的事情有四个:第一行读入原始文件,把手机号统一按字符串处理,避免Excel自动转成科学计数法;第二步输出总记录数和各列空值数量,如果空值比例过高,说明文件在传输过程中有损坏;第三步把手机号截取前7位并去重,得到真正的号段数目;最后按省份统计,看看是不是覆盖了全国所有省级行政区。
参数说明:读文件时dtype=str很关键,手机号超过11位数字会被pandas默认当成数值类型,前导0会被吞掉;sep和encoding要根据实际拿到的文件调整,常见的是逗号分隔加UTF-8,也有用TAB分隔加GBK的。
这一步做完,基本就掌握了这份库的“边界”:总共有多少个有效号段、哪些省份记录多、哪些省份记录少。比如虚拟运营商号段多的省份,记录数会明显偏高,这是号段分配所致,不是数据错误。
2.3 虚拟运营商号段:这部分数据最容易出幺蛾子
17开头的号段(170、171、162、165、167等)在归属地数据里经常“一人多身份”。原因很简单:这些号段最初分配给某家基础运营商,后来又转售给虚拟运营商经营,导致同一号段在不同版本的数据库里归属信息不一致。
我在实际项目中遇到过:同一个170号段,在这份库里显示是运营商A、某南方城市,在另一份老库里却是运营商B、某北方城市。这种不一致不是数据“错”了,而是号段转售时间点不同,数据库快照时间不同。
处理方式建议这样:把170、171、162、165、167这类已知虚商号段单独拎出来建一张表,不要混在主表里参与精确归属判断。主表用来查普通号段,虚商表用来做二次甄别。两边的更新时间可以不同,虚商表按季度维护就行。
3. Excel落地实操:CSV转码、去重、按省份拆表,通讯录清洗直接抄
3.1 打开前先处理编码:中文乱码和行数截断两个老坑
拿到Excel版的资源,10个人里有4个直接双击打开,然后遇到乱码或者只有65536行。先说乱码:很多数据库导出文件是UTF-8无BOM编码,Excel在中文Windows下默认按ANSI(GBK)打开,中文就变乱码。
我的处理方式:先用Python把文件统一转成带BOM的UTF-8,再双击打开。代码很短:
# 把无BOM的UTF-8文件转成带BOM,解决Excel打开中文乱码 with open("phone_location.csv", "rb") as f: raw = f.read() # 如果文件头不是UTF-8 BOM,就补上 if not raw.startswith(b"\xef\xbb\xbf"): with open("phone_location_bom.csv", "wb") as f: f.write(b"\xef\xbb\xbf" + raw) print("转换完成,用Excel打开 phone_location_bom.csv")这段脚本只处理编码头,不做其他改动。注意不要用记事本打开再另存为,那会丢失原始编码信息,还把36万行数据过一遍内存。
再说行数截断:旧版Excel(2003及更早)单个工作表最多65536行,这份库36万条记录会被直接截掉大半。新版Excel虽然能打开上百万行,但打开36万行本身就挺慢。我的建议是:Excel只用来小范围抽查和人工核对,真正批量查数放到数据库里做。
3.2 数据清洗:去重、截取前7位、统一运营商名称
确认编码没问题后,第一步是去重。资源包里的Excel不一定只有一个Sheet,有的会按年份或来源分Sheet,合并后重复号段很常见。我在Excel里处理时习惯这样操作:
- 在“手机号”列后插入辅助列,输入
=LEFT(A2,7)截取前7位号段 - 选中全部数据,按“前7位号段”列排序
- 用“数据—删除重复值”勾选号段列,把重复号段删掉
- 如果省份和城市是合并在一列(比如“省份-城市”),用“数据—分列”按分隔符拆开
一个容易忽略的细节:Excel的删除重复值是按整行比较的,不是只按一列。如果两行号段相同但运营商字段略有差异,它不会删。这种情况我一般保留第一条,把运营商的差异丢到备注列里,不阻塞主流程。
清洗完记得再看一眼总行数。如果去重后从36万掉到不足20万,说明原始文件里将近一半是重复记录,这是正常的,因为同一号段可能对应多个手机号明细;如果掉到几万条,说明这份文件本质是前7位号段表而非全量号码明细,使用时要调整预期。
3.3 按省份拆分成多个工作表:一个VBA宏搞定
通讯录清洗、按省份分渠道下发这类场景,经常要把36万条记录按省份拆开。手工筛选复制太慢,用VBA写个小宏:
Sub SplitByProvince() Dim src As Worksheet, dst As Worksheet Dim lastRow As Long, i As Long Dim prov As String Dim dstSheet As Worksheet Dim targetRow As Long Set src = ThisWorkbook.Sheets("数据") lastRow = src.Cells(src.Rows.Count, "B").End(xlUp).Row ' 假设省份在B列 ' 先删掉旧的拆分结果表 Application.DisplayAlerts = False For Each dstSheet In ThisWorkbook.Worksheets If dstSheet.Name Like "省_*" Then dstSheet.Delete Next Application.DisplayAlerts = True For i = 2 To lastRow ' 从第2行开始,第1行是表头 prov = src.Cells(i, "B").Value If Trim(prov) <> "" Then Set dst = Nothing On Error Resume Next Set dst = ThisWorkbook.Worksheets("省_" & prov) On Error GoTo 0 If dst Is Nothing Then Set dst = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count)) dst.Name = "省_" & prov src.Rows(1).Copy dst.Rows(1) ' 复制表头 End If targetRow = dst.Cells(dst.Rows.Count, "B").End(xlUp).Row + 1 src.Rows(i).Copy dst.Rows(targetRow) End If Next i MsgBox "拆分完成" End Sub这段宏有几个参数要按实际情况改:第一行“数据”是源Sheet名,改成你实际的工作表名;省份列是B列,如果你的省份在C列,把两处“B”改成“C”;生成的拆分表统一带“省_”前缀,方便识别。
逻辑说明:先获取源数据最后一行行号,遍历每一行取出省份;如果以“省_省份名”命名的工作表不存在就新建并复制表头,存在就直接把整行复制过去。这里用On Error Resume Next而不是Exists检查,是为了兼容旧版Excel没有Worksheet对象Exists方法的限制。
拆分完成后,强制做一次行数核对:把拆分前总行数(减1行表头)和所有“省_*”表的行数求和对比,不一致就说明宏里目标行定位有问题。我吃过这个亏——有一次漏了表头行,拆分结果对所有数据都错位了一行,前3万条用户归属地全部对错,排查到天亮。
如果你不想用VBA,还有一个替代方案:插入数据透视表,把“省份”拖到筛选区域,逐个筛选复制。数据量小无所谓,36万行明显用宏更省事,而且能重复跑。
4. 把36万条记录推进MySQL:建表、批量导入与三种查询场景
4.1 建表:varchar还是char,字符集和索引怎么选
不要直接用Excel当生产库。36万行放Excel里,多人同时查卡死不说,每次清洗都要全表加载。推进MySQL是标准做法。表结构建议这样建:
CREATE TABLE phone_location ( id INT NOT NULL AUTO_INCREMENT, prefix7 CHAR(7) NOT NULL COMMENT '手机号前7位', province VARCHAR(32) NOT NULL COMMENT '省份', city VARCHAR(32) NOT NULL COMMENT '城市', operator VARCHAR(32) NOT NULL COMMENT '运营商', source_tag VARCHAR(16) DEFAULT 'base' COMMENT '数据来源标记', updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_prefix (prefix7) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='手机号段归属地字典';几个选择理由:prefix7用char(7)而不是varchar,因为前7位长度固定,char省去变长字段的额外开销,等值查询更快;省份城市运营商用varchar(32),长度收住防止脏数据写爆;唯一索引uk_prefix直接保证同一号段只有一条数据,重复导入时会报错而不是静默累积。最后加source_tag字段,用来标记数据版本,后面增量更新时有用。
字符集必须utf8mb4,不要用utf8。utf8在MySQL里是utf8mb3的别名,遇到生僻字或emoji会报错或乱码,省市区字段里偶尔会出现生僻地名,别在这里省空间。
导入前记得清理数据:同一前7位有多条记录时,uk_prefix会挡住导入。不要用INSERT IGNORE跳过错误,否则看不出脏数据。正确做法是先按前缀去重合并,再导入。
4.2 批量导入:LOAD DATA和Python两条路
36万行说多不多,说少不少。最快的是LOAD DATA:
LOAD DATA LOCAL INFILE '/data/phone_location.csv' INTO TABLE phone_location CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (prefix7, province, city, operator) SET source_tag = 'v2025';LOAD DATA是按“行”导入,36万行通常几秒完成。如果中间报错,说明CSV里有转义问题,比如某个城市名里带了逗号没加引号。我一般会先用Python做一次校验再LOAD,不然排查CSV的坑很费时间。
如果你不熟LOAD DATA,用Python + pandas也够快:
from sqlalchemy import create_engine import pandas as pd df = pd.read_csv("phone_location.csv", dtype=str) # 同一个前缀只保留第一条归属信息 df = df.drop_duplicates(subset="prefix7", keep="first") df["source_tag"] = "v2025" engine = create_engine("mysql+pymysql://user:pass@localhost/phone_db?charset=utf8mb4") df.to_sql("phone_location", engine, if_exists="append", index=False, chunksize=5000)这段代码里chunksize=5000表示每5000条提交一次,避免一次性提交36万行把内存撑爆。if_exists="append"是追加模式,适合增量更新;第一次跑建议用"replace"或先把表DROP掉再建。
导入完查一下总数:
SELECT COUNT(*), COUNT(DISTINCT prefix7) FROM phone_location;如果两个数相同,说明没有重复号段;如果差异很大,回去看看Excel里是否有同一号段多条记录没合并干净。
4.3 三种查询场景:单号、批量IN、联表清洗
查单号归属地:
SELECT province, city, operator FROM phone_location WHERE prefix7 = LEFT('13900000001', 7);注意这里不是直接写死前7位,而是用LEFT函数统一截取,避免查询时手算出错。这个写法在存储过程或视图里用很稳。
批量查几万个号:
-- 假设有一张待清洗表 temp_phones(phone VARCHAR(11)) SELECT p.phone, t.province, t.city, t.operator FROM temp_phones p LEFT JOIN phone_location t ON t.prefix7 = LEFT(p.phone, 7);LEFT JOIN而不是INNER JOIN,是为了把没匹配到的号码也带出来,方便看哪部分号码是库里没有的新号段。新号段的比例能告诉你这份库是不是该更新了。
匹配不上时,按号段年龄段看未命中分布:
SELECT LEFT(phone, 3) AS seg3, COUNT(*) AS miss_cnt FROM temp_phones p LEFT JOIN phone_location t ON t.prefix7 = LEFT(p.phone, 7) WHERE t.prefix7 IS NULL GROUP BY LEFT(phone, 3) ORDER BY miss_cnt DESC;如果未命中集中在19x和20x开头的号段,基本可以判断是新放号的号段还没收录;如果未命中是随机分布,先检查手机号列有没有混入固话或非法字符。
4.4 两种常见误用:前3位分组和LIKE模糊查询
最容易翻车的是这两类写法:
-- 错误写法1:只用前3位当归属地 SELECT LEFT(phone,3), COUNT(*) FROM temp_phones GROUP BY LEFT(phone,3); -- 错误写法2:全字段模糊匹配 SELECT * FROM phone_location WHERE prefix7 LIKE '%139%';第一种不是查归属地,是查运营商;第二种LIKE '%139%'会放弃uk_prefix索引,36万行全表扫描,慢不说,还容易匹配到错误行。统计运营商分布可以用前3位,但要查省市必须用前7位,这是原则。
如果业务上非要支持前缀模糊查询,给prefix7保留普通索引,并且用“prefix7 LIKE '139%'”这种前缀匹配,索引才用得上。36万行上没有性能压力,但写法从一开始就要正确,否则换个百万级表直接卡死。
5. 避坑指南:归属地数据使用中常见的五个翻车现场
用这类数据一年半载,踩过的坑值得记下来。每一条我都标注了现象、原因和解决方式,按优先级排的。
5.1 虚拟运营商号段“一查两归属”
现象:查170号段,有时候返回运营商A某南方城市,有时候返回运营商B某华东城市。同一批数据两条记录都对不上。
原因:虚商号段是基础运营商划转给虚拟运营商经营的,号段资源登记表在不同时间点生成了不同快照。这份库如果混用了两个版本的数据,就会同时存在两条归属记录。
解决:把17开头(170/171/162/165/167等)号段单独拆出来,建一张虚商号段表。日常查询默认用主表,只有主表未命中时才查虚商表;两张表各自维护更新时间,不要强行合并成一张。
5.2 携号转网让运营商字段“过期”
现象:某号码在库里的运营商标记是A,实际用户半年前转到了B。短信通道按运营商路由时,大批量短信被退回。
原因:归属地数据库的运营商字段是从发卡时刻记录的,携号转网后不会自动回写。这类离线库天然滞后于实时网络状态。
解决:原则上把“运营商”当参考字段,不做强路由依据。运营商强敏感场景用运营商侧接口单独核验,或定期把转网用户名单拉回来在库里打标。归属地(省、市)不受携号转网影响,可以放心用。
5.3 36万行在旧版Excel里只剩65536行
现象:双击打开文件,拉到最下面只有65536行,另一半数据不知所踪。
原因:旧版Excel(2003及更早)单工作表行数上限就是65536,超过的写不进工作表。新版Excel虽然支持104万行,但打开时如果文件是xls老格式,同样可能触发兼容模式截断。
解决:确认文件版本,不要用第三方小工具强行转格式。建议只用Excel做抽查和人工核对,整库查询放到MySQL或SQLite里。如果你是拿Excel直接跑VLOOKUP做清洗,36万行的VLOOKUP本身就是性能灾难,等它算完不如喝杯咖啡。
5.4 CSV导入Excel后中文乱码
现象:导入CSV后,省份和城市全是“鍖椾含”“娴峰崡”这类乱码。
原因:文件是UTF-8无BOM编码,Excel按本机ANSI代码页解析,字节流错位了。这个不是数据损坏,是编码声明缺失。
解决:按第3章的方法,把文件转成带BOM的UTF-8或直接存成GBK。转码脚本5分钟搞定,不值得在这个问题上浪费时间。转完之后抽查几个省份词,比如“某省”“某市”,确认没有乱码再往下走。
5.5 把归属地当实时位置,风控误杀一片
现象:某用户注册地在某北方城市,出差到南方某市后登录,风控系统按号码归属地判断“异地登录”,直接触发限制,用户投诉到客服。
原因:号码归属地等于发卡地,不等于当前所在城市。这张表的“城市”是办卡时登记的归属地,和基站的实时位置完全是两回事。
解决:归属地只用来做离线画像,比如沉默用户的地域分布、注册地分析、短信分省下发;实时风控用授权定位或基站信令,不要把归属地当成LBS字段参与实时决策。哪个维度用哪个数据,一开始就要在表设计上分开。
6. 进阶校验:一个批量自检脚本和增量更新策略
6.1 一个可复用的自检脚本
每拿一批新手机号,先跑一遍自检脚本,再决定是否进清洗流程:
import re import sqlite3 conn = sqlite3.connect("phone_location.db") phones = ["13900000001", "17012345678", "12345", "15800000123"] for p in phones: if not re.fullmatch(r"1[3-9]\d{9}", p): print(p, "格式不合法") continue row = conn.execute( "SELECT province, city, operator FROM phone_location WHERE prefix7=?", (p[:7],) ).fetchone() if row: print(p, row) else: print(p, "未命中号段,需检查是否为新号段")正则只做最基础的格式校验,不判断号段是否真实分配。匹配不上时先确认是不是新号段,而不是直接打日志忽略。
6.2 增量更新的维护习惯
离线归属地库的保质期大概在半年到一年。每月对照主管部门的新号段公告,把新增前7位合并进主表。建议每次更新带版本号,旧文件按日期归档,不要覆盖。
有一次我图省事直接覆盖导入,结果新库和旧库混在一起,查出来的归属地各说各话,花了一下午才把数据恢复回来。从那以后我每次更新都强制走三件事:备份旧文件、跑一遍自检脚本、核对新增号段数量。希望帮到你。
本文还有配套的精品资源,点击获取