Python 批量生成虚拟手机号:规则、代码与测试数据管理实践

📅 发布时间:2026/9/8 3:36:49
Python 批量生成虚拟手机号:规则、代码与测试数据管理实践 “谁想要本船长的电话号码”——这句话放在开发群里一般没人真敢要但放到测试环境里几乎每个后端、App开发、运营系统都会遇到同一个需求大批量、可重复、不惹麻烦的测试手机号。很多第一次写测试脚本的人直接random.randint(13000000000, 19999999999)一梭子下去结果发现数据是生成了但有的号码一看就不合法有的号段根本不存在还有的重复率高得离谱。更麻烦的是如果这些“假号码”被当成真实数据写进线上库后面做手机号正则校验、运营触达、用户隐私合规每一环都会出问题。这篇文章不聊段子聊清楚三件事手机号生成背后有哪些规则如何用 Python 写一个能直接用的批量生成工具以及测试数据在生产环境里到底该怎么处理。读完你可以直接复制代码在本地跑通一个虚拟号码生成器。1. 这篇文章真正要解决的问题先说痛点。开发过程中“需要手机号”的场景比想象中多注册登录联调、短信验证码测试、用户画像造数、批量导入测试、活动系统压测、支付流程模拟哪一个都绕不开。传统的做法有三种各有各的坑第一从网上找现成的测试手机号清单。这种清单要么老要么格式乱要么就是已经被无数人用烂的固定号码。联调时偶尔用一次还行压测和造数时分分钟露馅。第二手工拼一个看起来像手机号的数字。比如13800138000这类号码在测试文档里很常见但一个项目里反复用同一个号接口层根本看不出问题到数据处理层就暴露了归属地查询为空、运营商识别失败、数据库唯一索引冲突。第三直接用真实手机号。这是最危险的做法。只要涉及真实个人信息就要考虑授权、脱敏、存储责任。测试环境里用真实号码一旦日志外泄或库被拖走麻烦不是一句“只是测试数据”能带过的。所以本文要解决的是如何通过一段可控、可解释、可维护的 Python 代码快速生成符合规则的虚拟手机号并且能按前缀、按数量、按输出格式灵活调整。核心判断是虚拟手机号生成不是 random 一把梭它是一道数据构造规范题。2. 手机号生成的基础规则与常见误解在写代码之前先弄清楚要生成的目标长什么样。中国大陆手机号有两个硬规则和一个软规则2.1 硬规则长度与首位手机号必须是 11 位数字这基本没有争议。第一位目前是 1这也是通信行业多年形成的惯例。很多脚本在定义长度时喜欢用范围随机比如random.randint(10000000000, 19999999999)这种写法能凑出 11 位但容易生成出10000000000这种一看就有问题的号码。更合理的写法是先确定首位数再生成剩余位数。import random def generate_mobile_simple(): // 前 2 位固定为 1 随机第二位 first 1 second random.choice(3456789) rest .join(random.choice(0123456789) for _ in range(9)) return first second rest2.2 软规则第二位和号段当前开放的手机号第二位范围大致覆盖 3 到 9但不同地区、不同年份会动态调整。这意味着代码里最好不要写死“只有 13、15、18 才算合法”的老规则。一个比 second 是你该在工具里维护一份前缀库。比如把139、138、188、199等常见前缀放在配置文件或代码常量中生成时从前缀库中随机选一个开头再补 8 位随机数。这种做法比纯随机更贴近真实数据方便 QA 和联调方一眼看出号码归属地基本符合预期。2.3 误解手机号可以完全随机很多新手以为 11 位数字随便组合就是手机号。实际上号段资源受运营商管理不存在的号段即使格式正确在真实系统中也可能被判定为无效。所以在工具中前缀库的价值不仅是“看起来像真的”而是让生成的测试数据更接近生产环境的数据分布。你不需要覆盖所有真实号段只需要覆盖当前项目用到的号段范围。2.4 手机号校验兜底除了生成工具里最好带上校验函数。逻辑很简单长度 11 位首位 1第二位 3-9后 9 位数字。import re MOBILE_PATTERN re.compile(r^1[3-9]\d{9}$) def is_valid_mobile(mobile: str) - bool: if not isinstance(mobile, str): return False return bool(MOBILE_PATTERN.match(mobile))3. 环境准备与前置条件本文示例基于 Python 3不需要第三方框架。建议 Python 版本不低于 3.8因为后续示例会用到secrets模块以及类型注解。需要的工具Python 3.8 解释器命令行终端Windows 可用 PowerShellmacOS/Linux 可用 bash一个空目录用来放脚本文件不需要安装任何第三方包标准库足够完成核心功能。不过如果你希望命令行交互更顺滑可以自行安装click但这不是必须的。建议在动手之前先确认 Python 环境。python --version如果输出类似Python 3.10.12即可继续。如果你机器上同时存在 python 和 python3下文命令统一使用python3还是python请以实际环境为准这里不强行统一。4. 核心流程拆解我们要实现的小工具整体流程可以分成四步4.1 定义前缀库前缀库决定了生成的号码“像不像真的”。建议单独维护一个列表。实际项目中前缀库可以放在配置文件里由测试负责人维护避免每次改代码。PREFIXES [ 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 150, 151, 152, 153, 155, 156, 157, 158, 159, 166, 170, 171, 175, 176, 177, 178, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 198, 199 ]注意前缀库不是越多越好而是要根据你的测试目标决定。比如你主要测试某省用户就应该配置该省放号较多的前缀。这里无法提供每个省份的准确号段因为号段资源会动态变化直接到运营商公开渠道查询或由团队内部维护更稳妥。4.2 生成函数生成函数负责做两件事从前缀库选择一个前缀再生成后续 8 位随机数字。随机选择有两个模块可选random和secrets。如果是测试造数对安全性没有要求用random即可性能更好。如果这个号码将来会用于生成验证码、密码、令牌等敏感场景必须用secrets因为random是伪随机可预测存在安全隐患。4.3 批量去重批量生成时最大的坑是重复。当生成量大到上万级别纯随机方式的重复率会明显上升。工程上更稳妥的方式是用集合去重数量不够就继续生成。4.4 输出与校验生成结果可以有两种出口直接打印到命令行或写入文件。文件格式常见有 CSV、JSON、纯文本一行一个。为了方便后续导入数据库CSV 或 JSON 更合适。生成结束后必须用校验函数对结果做一遍全量校验避免前缀库或随机逻辑引入脏数据。5. 完整示例与代码实现下面按照上面的设计给出一个可以直接运行的完整脚本。为了方便阅读我会拆成多个文件结构但单文件方式也能用。5.1 核心文件结构fake_mobile/ ├── fake_mobile.py ├── prefixes.py └── output/prefixes.py只放前缀库# 文件路径fake_mobile/prefixes.py # 注意号段为常见号段示例实际请按项目需要维护 PREFIXES [ 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 150, 151, 152, 153, 155, 156, 157, 158, 159, 166, 170, 171, 175, 176, 177, 178, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 198, 199 ]5.2 主脚本fake_mobile.py提供四个能力单个生成、校验、批量生成、命令行入口。# 文件路径fake_mobile/fake_mobile.py import csv import json import random import re import secrets from collections.abc import Iterable from prefixes import PREFIXES MOBILE_PATTERN re.compile(r^1[3-9]\d{9}$) DEFAULT_COUNT 10 def random_number(length: int, secure: bool False) - str: 生成指定长度的数字字符串。 secure 为 True 时使用 secrets适合对随机性有安全要求的场景。 if secure: source secrets.choice(0123456789) return .join(secrets.choice(0123456789) for _ in range(length)) return .join(random.choice(0123456789) for _ in range(length)) def generate_one(prefix: str | None None, secure: bool False) - str: 生成一个虚拟手机号。 if prefix is None: prefix random.choice(PREFIXES) if len(prefix) ! 3 or not prefix.isdigit(): raise ValueError(f非法前缀: {prefix}) tail random_number(8, securesecure) return prefix tail def is_valid_mobile(mobile: str) - bool: 校验手机号格式。 if not isinstance(mobile, str): return False return bool(MOBILE_PATTERN.match(mobile)) def generate_batch( count: int, prefixes: Iterable[str] | None None, secure: bool False, unique: bool True, ) - list[str]: 批量生成虚拟手机号。 count max(0, count) prefix_list list(prefixes if prefixes is not None else PREFIXES) if not prefix_list: raise ValueError(前缀列表不能为空) result set() if unique else [] retry 0 max_retry count * 20 100 while len(result) count: if retry max_retry: break prefix random.choice(prefix_list) mobile generate_one(prefix, securesecure) if unique: result.add(mobile) else: result.append(mobile) retry 1 return list(result) def export_csv(mobiles: list[str], filepath: str) - None: 导出为 CSV。 with open(filepath, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([mobile]) for m in mobiles: writer.writerow([m]) def export_json(mobiles: list[str], filepath: str) - None: 导出为 JSON。 with open(filepath, w, encodingutf-8) as f: json.dump({mobiles: mobiles}, f, ensure_asciiFalse, indent2) def main(): import argparse parser argparse.ArgumentParser(description生成虚拟手机号) parser.add_argument(-n, --count, typeint, defaultDEFAULT_COUNT, help生成数量) parser.add_argument(--prefix, actionappend, help指定前缀可多次传入) parser.add_argument(--secure, actionstore_true, help使用安全随机) parser.add_argument(--output, help输出文件路径支持 .csv / .json / .txt) parser.add_argument(--no-unique, actionstore_true, help不去重) args parser.parse_args() prefixes args.prefix or None mobiles generate_batch( countargs.count, prefixesprefixes, secureargs.secure, uniquenot args.no_unique, ) invalid [m for m in mobiles if not is_valid_mobile(m)] if invalid: print(f发现非法号码 {len(invalid)} 个请检查前缀库) for m in invalid[:10]: print(m) return if args.output: if args.output.endswith(.csv): export_csv(mobiles, args.output) elif args.output.endswith(.json): export_json(mobiles, args.output) else: with open(args.output, w, encodingutf-8) as f: f.write(\n.join(mobiles) \n) print(f已生成 {len(mobiles)} 个号码 - {args.output}) else: for m in mobiles: print(m) if __name__ __main__: main()这份代码有几个值得注意的地方generate_batch里去重逻辑使用集合保证返回结果没有重复。retry上限是为了避免极端情况下无限循环。导出函数区分了 CSV、JSON、普通文本三种格式方便不同测试工具读取。校验逻辑放在导出前能及时暴露前缀库或生成逻辑的问题。5.3 命令行使用示例生成 5 个号码python fake_mobile.py -n 5生成 200 个号码固定使用 139、188 前缀并导出为 CSVpython fake_mobile.py -n 200 --prefix 139 --prefix 188 --output output/demo.csv生成 1000 个号码并导出为 JSONpython fake_mobile.py -n 1000 --output output/demo.json5.4 作为模块导入如果你不想用命令行也可以在其他 Python 项目中直接导入。# 文件路径fake_mobile/use_example.py from fake_mobile import generate_batch, is_valid_mobile mobiles generate_batch(20, prefixes[139, 138]) print(mobiles) print(all(is_valid_mobile(m) for m in mobiles))6. 运行结果与效果验证运行生成命令后正常情况下会输出指定数量的 11 位号码比如python fake_mobile.py -n 3 --prefix 139预期输出13948592014 13967201548 13935882017这里注意一点由于是随机生成每次运行输出的具体号码都不同这属于正常现象。判断工具是否成功不依赖具体号码值而看三点第一号码长度是否全部为 11 位。第二号码是否全部通过正则校验。第三指定前缀时号码开头是否与传入前缀一致。如果导出到文件可以直接用 wc 或 Python 打开检查。python fake_mobile.py -n 1000 --prefix 139 --output output/demo.csv wc -l output/demo.csv因为 CSV 带了表头所以wc -l结果应为 1001而不是 1000。如果你不需要表头可以把代码中的writer.writerow([mobile])注释掉。如果运行失败第一步先看命令行是否报语法错误重点检查 Python 版本是否低于 3.8。str | None这种类型注解语法在 Python 3.10 之前不可用如果你用的版本较低需要改成Optional[str]并导入typing。7. 常见问题与排查思路问题现象可能原因排查方式解决方案生成数量大于请求数量去重逻辑导致异常检查前缀列表是否太小增加前缀库或允许不去重生成的号码校验失败前缀库包含非法前缀打印前缀列表检查长度和内容只保留 3 位数字前缀导出 CSV 打开乱码编码问题用文本编辑器查看文件编码使用utf-8-sig编码打开或调整代码类型注解报错Python 版本过低执行 python --version升级 Python 或改写注解生成速度慢生成量过大且前缀少查看去重重试次数扩大前缀库关闭唯一约束号码看起来全是假号随机尾号太规整观察输出数据的分布使用更分散的前缀库这里补一个容易忽视的坑如果前缀列表里只有一两个前缀又要生成几万条不重复号码那么最终的随机空间被严重压缩脚本会陷入长时间重试。更合理的做法是同时放宽唯一性要求或大幅增加前缀范围。8. 最佳实践与工程建议8.1 测试数据不要直接污染生产库生成虚拟手机号的目的是造测试数据但造出来的数据一旦导入生产环境就会变成脏数据。工程上建议在导入之前明确标记测试归属比如在用户表的备注字段或独立测试标识中注明“虚拟数据”方便后续清理。8.2 不要在代码里硬编码前缀库前缀库属于易变信息建议放到配置中心、YAML 或数据库中由测试负责人维护。代码里只保留一份兜底默认前缀避免因业务变化反复改代码。8.3 区分随机数安全级别再次强调random模块生成的数据不能用于密码、验证码、令牌等安全敏感场景。这类场景必须使用secrets。本文代码已经预留了secure参数但默认仍走random这是为了造数性能。将来如果需求变成“生成一批可用于风控测试的虚拟号码”需要打开--secure。8.4 考虑号码的敏感性和管理责任虚拟手机号如果只是随机数字理论上不构成真实个人信息但要注意两个边界如果前缀库来自真实用户号段统计并且生成结果刚好与某个真实号码撞车那么在日志、监控等系统里它看起来仍然像一个真实号码。虽然概率不高但批量生成几百万条时撞号风险会上升。对于“号码是否真实存在、是否可注册、是否已注册”这类问题不要在非授权情况下批量探测。批量注册探测、批量短信发送、营销骚扰这些都是明确的高风险行为本文代码仅用于开发和测试环境的数据构造不能用于任何未经授权的线上行为。8.5 日志与脱敏在测试环境使用虚拟号码时建议日志中统一加上前缀标识比如TEST_13900000000或者在日志字段中增加data_typetest避免运营和数据分析同事把测试数据当成真实用户。8.6 与数据工厂结合如果你所在团队已经使用了 Faker 这类造数工具没必要重复造轮子可以将其作为自定义 Provider 接入。Faker 本身也提供了手机号生成能力但国内号段细节和自定义前缀扩展仍需要自己维护。本文示例代码的价值就在于此逻辑简单可定制性强不依赖大量第三方库。8.7 代码审查的小建议这类工具虽然小但也应该走代码审查。重点看三处前缀库是否含有过期号段随机逻辑是否用了安全模块导出文件是否包含了多余的真实数据。哪怕它是“一次性脚本”也建议放到仓库里管理而不是存在某台机器的/tmp下。9. 总结与后续学习方向回到开头那句话真正想要船长的电话号码的人不多但需要一个“安全、合规、可批量生成虚拟手机号工具”的开发者很多。本文从手机号格式规则入手讲清楚了虚拟手机号生成的核心逻辑前缀库决定真实性随机策略决定性能与安全性去重机制决定批量质量校验函数决定最终可靠性。然后给了一个可以直接运行的 Python 命令行脚本支持指定数量、指定前缀、CSV/JSON 导出并在最后强调了测试数据管理的工程注意点。如果你想继续深入可以往这几个方向走第一把生成器扩展成更通用的测试数据工厂不仅生成手机号还生成姓名、地址、邮箱、身份证脱敏数据形成一套造数平台。第二学习如何使用 Faker 自定义 Provider和本文的前缀库思路结合起来让团队造数体验更好。第三研究测试数据生命周期管理包括数据生成、数据标记、数据清理以及如何在 CI 流水线里自动生成和销毁测试数据。第四如果你的工作涉及数据库大批量造数可以结合copy、insert ... select等批量导入方式把生成结果直接灌入测试库但要注意充分利用事务和批量提交避免导入性能问题。最后提醒一句虚拟手机号是测试工具的辅助手段不是绕过验证、批量注册、短信轰炸的“万能钥匙”。把工具用在开发和测试的合规场景里它能成为团队效率提升的好帮手用在错误的地方只会给项目和自己带来风险。这一点比任何代码细节都重要。建议先把这份脚本收藏起来下次需要批量测试手机号时直接改改前缀库就能跑。