资讯动态

手机号归属地查询:36万条本地号段库批量处理与风控实战

发布时间:2026/10/18 9:09:21 来源:尧图企业网站定制
简介这是一份面向开发者、数据分析人员及反欺诈风控从业者的全国手机号码段归属地数据资源用于快速查询号码对应的运营商、省份、城市、区号与邮编等信息可支撑地域分析、客户画像与本地化服务等场景。压缩包共4个文件以sql脚本、csv数据表与txt说明为主另有Excel格式便于非技术人员直接排序筛选整体约7.48MB涵盖360569条号码段记录字段结构清晰、便于导入数据库或做二次加工。目前已有5056人学习下载说明其在号码归属地查询与数据清洗类需求中具备一定参考价值。使用者可借助SQL脚本快速建表并批量导入也可通过表格工具直接检索目标号段适合需要自建归属地库或做地域维度分析的中初级技术人员参考使用。1. 手机号归属地查询36 万条记录的数据包能解决哪些实际问题做用户注册风控的同行大概率遇到过这种场景运营提过来一批手机号要求按省份拆分做短信触达或者风控策略需要根据号段判断某笔订单是否属于异地登录。这时候去调第三方 API量小还行量一上来按次计费的成本直接劝退。另一个常见需求是数据清洗CRM 系统里存了十几万条客户手机号格式乱七八糟需要批量补全归属地字段再做区域分析。这份全国手机号码段归属地数据库核心价值就在于把查询能力从线上 API 搬到本地。36 万条记录覆盖了国内三大运营商及各虚拟运营商的号段分配情况Excel 格式意味着不需要写代码也能直接筛选对非技术岗同样友好。适合的人群很明确做风控策略的、做数据清洗的、做区域化运营的以及需要离线批量处理手机号归属信息的开发者。它不是实时接口的替代品但在批量场景下本地查表的速度和成本优势是碾压级的。2. 号段数据库的结构拆解从字段设计看查询效率2.1 核心字段与数据组织方式拿到一份号段数据第一件事不是急着写查询代码而是先搞清楚它的字段结构。常见的号段库一般包含这几个核心字段号段前缀通常是前 7 位、省份、城市、运营商名称、区号、邮编。部分版本还会附带卡类型如移动全球通、联通沃德和号段状态标记。这份数据以 Excel 形式提供36 万条记录的体量意味着单文件大概在 20-40MB 之间打开时建议用 pandas 而不是 Excel 客户端直接双击否则滚动和筛选会非常卡。字段命名上要注意一个坑不同来源的号段库对“城市”字段的粒度不一致有的写到地级市有的只到省份。使用前先抽样看几十行确认粒度是否满足你的业务需求。号段前缀的长度也值得关注。国内手机号是 11 位前 3 位标识运营商前 7 位基本能定位到具体城市。如果数据里只存了前 3 位那只能判断运营商判断不了归属地如果存了前 7 位精度就足够覆盖绝大多数业务场景。这份数据既然有 36 万条大概率是按前 7 位组织的因为三大运营商在用的 7 位号段总量大致就在这个量级。2.2 为什么选本地库而不是 API本地库和 API 的取舍本质上是三个维度的权衡成本、延迟、数据新鲜度。成本维度不用多说API 按调用次数计费十万次查询就是实打实的支出本地库一次获取无限次查询。延迟方面本地查表在内存里做哈希匹配单次查询在微秒级API 调用即使走内网往返也要几毫秒到几十毫秒批量场景下差距会被放大到不可忽视。数据新鲜度是本地库的短板——号段会随着运营商放号不断新增本地库需要定期更新。但实际业务中号段变更的频率并不高月度或季度更新一次基本够用。我一般建议的做法是线上实时查询用 API 兜底离线批处理和风控策略用本地库。两者结合既控制了成本又保证了覆盖率。2.3 用 pandas 加载并验证数据完整性拿到 Excel 文件后第一步是加载并做基本校验。下面这段代码演示了完整的加载和验证流程import pandas as pd # 加载号段数据指定 dtype 避免号段前缀被推断为整数丢失前导零 df pd.read_excel( phone_segment.xlsx, dtype{号段: str, 省份: str, 城市: str, 运营商: str}, engineopenpyxl ) # 基本校验总行数、字段名、空值情况 print(f总记录数: {len(df)}) print(f字段列表: {df.columns.tolist()}) print(f各字段空值数:\n{df.isnull().sum()}) # 检查号段前缀长度分布确认数据粒度 df[前缀长度] df[号段].str.len() print(f号段长度分布:\n{df[前缀长度].value_counts()}) # 去重检查号段前缀是否唯一 dup_count df[号段].duplicated().sum() print(f重复号段数: {dup_count})这段代码有几个关键点。dtype{号段: str}是必须的因为手机号段如 130 开头pandas 默认会推断为整数导致 0130 这类前导零丢失。engineopenpyxl用于读取 .xlsx 格式如果是 .xls 老格式需要换成xlrd。号段长度分布能帮你确认数据是按 3 位还是 7 位组织的直接决定后续查询逻辑怎么写。重复号段检查则是为了确认数据质量如果存在重复前缀查询时需要决定取第一条还是做合并。3. 批量归属地查询实战从单条匹配到百万级处理3.1 构建号段索引与查询函数数据校验通过后下一步是构建高效的查询结构。最直接的方式是把号段前缀作为 key归属地信息作为 value构建一个字典。对于 36 万条记录Python 字典的内存占用大约在 50-80MB完全可以接受。# 构建号段到归属地的映射字典 segment_map {} for _, row in df.iterrows(): prefix row[号段] segment_map[prefix] { province: row[省份], city: row[城市], carrier: row[运营商] } def lookup_phone(phone_number: str) - dict: 查询单个手机号的归属地支持 7 位前缀匹配 phone_number str(phone_number).strip() if len(phone_number) ! 11: return {error: 号码长度不是 11 位} # 优先用前 7 位匹配匹配不到再降级到前 3 位 for prefix_len in (7, 3): prefix phone_number[:prefix_len] if prefix in segment_map: return segment_map[prefix] return {error: 号段未收录} # 测试几条 for num in [13800138000, 15912345678, 17012345678]: print(num, -, lookup_phone(num))这里的设计思路是分级匹配先用 7 位前缀查查不到再降级到 3 位。原因是部分老号段或虚拟运营商号段可能只收录了前 3 位降级匹配能提高覆盖率。str(phone_number).strip()是为了处理从 Excel 或 CSV 读进来时可能带的空格和类型问题。返回结构用字典而不是元组是为了后续扩展字段时不用改调用方代码。3.2 用向量化操作处理十万级数据单条查询的函数写好后如果直接对 DataFrame 用apply十万条数据大概要跑十几秒。更高效的方式是用 pandas 的字符串切片加map做向量化匹配# 假设有一个待查询的 DataFrame orders pd.DataFrame({ order_id: range(1, 100001), phone: [13800138000] * 100000 # 示例数据 }) # 提取前 7 位作为匹配键 orders[prefix7] orders[phone].str[:7] orders[prefix3] orders[phone].str[:3] # 构建两个映射字典分别对应 7 位和 3 位 map7 df[df[号段].str.len() 7].set_index(号段)[城市].to_dict() map3 df[df[号段].str.len() 3].set_index(号段)[城市].to_dict() # 向量化匹配先查 7 位未命中再用 3 位填充 orders[city] orders[prefix7].map(map7) mask orders[city].isnull() orders.loc[mask, city] orders.loc[mask, prefix3].map(map3) print(f未匹配数: {orders[city].isnull().sum()})向量化的核心优势在于避免了逐行 Python 循环。str[:7]在 pandas 底层是 C 实现的比apply(lambda x: x[:7])快一个数量级。map方法直接利用哈希表查找也是 C 层实现。十万条数据的匹配时间大概在 0.5-1 秒比逐行 apply 快 10 倍以上。最后用isnull()做掩码再填充 3 位匹配结果逻辑清晰且不会覆盖已匹配的值。3.3 结果导出与增量更新策略查询完成后导出结果时要注意编码问题。如果下游是 Excel 用户直接to_excel即可如果是数据入库建议用to_csv并指定encodingutf-8-sig避免中文乱码。# 导出为 CSVutf-8-sig 保证 Excel 打开不乱码 orders[[order_id, phone, city]].to_csv( orders_with_city.csv, indexFalse, encodingutf-8-sig ) # 增量更新新号段追加到原数据 new_segments pd.DataFrame({ 号段: [1990000, 1990001], 省份: [某省, 某省], 城市: [某市, 某市], 运营商: [某运营商, 某运营商] }) df_updated pd.concat([df, new_segments], ignore_indexTrue) df_updated.drop_duplicates(subset[号段], keeplast, inplaceTrue) df_updated.to_excel(phone_segment_updated.xlsx, indexFalse)增量更新时用drop_duplicates配合keeplast保证新号段覆盖旧记录。这个习惯在号段库维护中很重要因为运营商偶尔会调整号段归属保留最新版本能避免查询结果过时。4. 避坑与排查号段查询中容易翻车的五个细节4.1 号段前缀被当成整数导致前导零丢失现象用 pandas 读取 Excel 后号段列显示为 130 而不是 0130或者某些以 0 开头的号段直接变成了 NaN。原因pandas 默认的类型推断会把纯数字字符串转为 int64前导零被丢弃。如果号段列混合了数字和文本还可能被推断为 object 但值已经变了。解决读取时显式指定dtype{号段: str}或者在读取后用df[号段] df[号段].astype(str).str.zfill(7)补齐位数。最稳妥的做法是在数据源头就把号段列设为文本格式。4.2 虚拟运营商号段匹配不到归属地现象170、171、165 等开头的号码查询返回空但这些号码明明是正常在用的。原因虚拟运营商MVNO的号段分配比较分散部分号段库只收录了三大基础运营商的号段虚拟运营商的号段要么缺失要么只记录了运营商名称没有归属地信息。解决先确认数据中是否包含虚拟运营商号段可以用df[df[号段].str.startswith((170, 171, 165))]快速检查。如果确实缺失需要单独补充虚拟运营商号段表或者在业务逻辑中对这类号码做降级处理——只返回运营商不返回城市。4.3 携号转网导致运营商判断错误现象某个号码查询结果显示是移动但用户实际用的是联通套餐。原因携号转网政策实施后号码的运营商归属和号段分配不再严格对应。号段库只能反映号段的初始分配无法追踪转网状态。解决这是号段库的固有局限不是数据质量问题。如果业务对运营商判断有强需求需要接入运营商的实时查询接口。本地库的场景下建议在返回结果中标注“号段归属”而非“当前运营商”避免误导。4.4 Excel 行数上限导致数据截断现象加载后发现只有 104 万行但实际数据应该更多。原因老版本 Excel 的单表行数上限是 1048576 行如果数据超过这个数会被截断。36 万条虽然没超但如果后续做数据合并可能触达上限。解决处理大数据量时优先用 CSV 格式没有行数限制。如果必须用 Excel拆分成多个 sheet 或升级到支持更大行数的格式。读取时用pd.read_excel(..., nrowsNone)确保不限制行数。4.5 内存溢出与分块处理现象加载 36 万条数据时内存占用飙升或者和其他数据合并时直接 MemoryError。原因iterrows()逐行遍历会创建大量临时对象内存占用远高于数据本身。如果同时加载多个大 DataFrame内存很容易打满。解决用itertuples()替代iterrows()速度更快内存更省。如果数据量继续增长用pd.read_excel(..., chunksize50000)分块读取每块处理完就释放。构建映射字典时用df.set_index(号段)[城市].to_dict()而不是循环底层优化更好。5. 进阶技巧把号段库做成可复用的查询服务5.1 用 SQLite 做持久化查询层如果每次查询都要加载 Excel启动开销太大。更优雅的做法是把数据导入 SQLite做成一个轻量级的本地查询服务。SQLite 单文件、零配置非常适合这种场景。import sqlite3 # 将 DataFrame 写入 SQLite conn sqlite3.connect(phone_segment.db) df.to_sql(segments, conn, if_existsreplace, indexFalse) # 创建索引加速查询 conn.execute(CREATE INDEX IF NOT EXISTS idx_prefix ON segments(号段)) conn.commit() # 查询示例 def query_by_sql(phone: str) - dict: prefix7, prefix3 phone[:7], phone[:3] cursor conn.execute( SELECT 省份, 城市, 运营商 FROM segments WHERE 号段 ? OR 号段 ? LIMIT 1, (prefix7, prefix3) ) row cursor.fetchone() return {province: row[0], city: row[1], carrier: row[2]} if row else {}SQLite 的优势在于索引查询的稳定性36 万条数据加索引后单次查询在毫秒级且不占常驻内存。CREATE INDEX那一步很关键没有索引的话每次查询都是全表扫描数据量再大就会明显变慢。5.2 用正则做号段有效性预校验在查询归属地之前先做一轮格式校验能过滤掉大量脏数据。国内手机号的格式规则是1 开头第二位是 3-9总共 11 位。import re PHONE_PATTERN re.compile(r^1[3-9]\d{9}$) def validate_and_lookup(phone: str) - dict: phone str(phone).strip().replace(-, ).replace( , ) if not PHONE_PATTERN.match(phone): return {error: 格式不合法, raw: phone} return lookup_phone(phone)这个正则能过滤掉固定电话、国际号码、位数不对的脏数据。replace链是为了处理用户输入时带的横线或空格实际业务中这类脏数据占比不低。5.3 覆盖率验证与数据质量监控上线前建议做一轮覆盖率验证拿一批已知归属地的号码做测试集看查询准确率。同时监控未匹配率如果某段时间未匹配率突然上升可能是运营商新放了号段而本地库没更新。监控指标计算方式告警阈值建议未匹配率未匹配数 / 总查询数超过 5% 触发更新格式错误率格式校验失败数 / 总查询数超过 10% 检查上游数据查询延迟 P99单次查询耗时排序后 99 分位超过 50ms 检查索引这套监控不复杂但能帮你及时发现数据老化问题。我一般会在每次批量查询后打印一行统计日志记录匹配率和耗时时间长了就能看出趋势。5.4 定期更新与版本管理号段库不是一劳永逸的建议每季度更新一次。更新时保留旧版本用日期做文件名后缀比如phone_segment_202501.xlsx。更新流程走一遍数据校验、去重、覆盖率测试确认没问题再替换线上版本。从那以后我每次更新号段库都强制走一遍校验脚本宁可多花十分钟也不想因为一条脏数据导致批量查询结果出错。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑