Python 批量生成虚拟手机号:规则、代码与测试数据管理实践
2026/9/8 3:36:47 网站建设 项目流程

“谁想要本船长的电话号码?”——这句话放在开发群里,一般没人真敢要,但放到测试环境里,几乎每个后端、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 + rest

2.2 软规则:第二位和号段

当前开放的手机号第二位范围大致覆盖 3 到 9,但不同地区、不同年份会动态调整。这意味着代码里最好不要写死“只有 13、15、18 才算合法”的老规则。

一个比 second 是你该在工具里维护一份前缀库。比如把139138188199等常见前缀放在配置文件或代码常量中,生成时从前缀库中随机选一个开头,再补 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 可用 PowerShell,macOS/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 位随机数字。

随机选择有两个模块可选:randomsecrets

如果是测试造数,对安全性没有要求,用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, secure=secure) 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, secure=secure) 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="", encoding="utf-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", encoding="utf-8") as f: json.dump({"mobiles": mobiles}, f, ensure_ascii=False, indent=2) def main(): import argparse parser = argparse.ArgumentParser(description="生成虚拟手机号") parser.add_argument("-n", "--count", type=int, default=DEFAULT_COUNT, help="生成数量") parser.add_argument("--prefix", action="append", help="指定前缀,可多次传入") parser.add_argument("--secure", action="store_true", help="使用安全随机") parser.add_argument("--output", help="输出文件路径,支持 .csv / .json / .txt") parser.add_argument("--no-unique", action="store_true", help="不去重") args = parser.parse_args() prefixes = args.prefix or None mobiles = generate_batch( count=args.count, prefixes=prefixes, secure=args.secure, unique=not 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", encoding="utf-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 前缀,并导出为 CSV:

python fake_mobile.py -n 200 --prefix 139 --prefix 188 --output output/demo.csv

生成 1000 个号码并导出为 JSON:

python fake_mobile.py -n 1000 --output output/demo.json

5.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_type=test,避免运营和数据分析同事把测试数据当成真实用户。

8.6 与数据工厂结合

如果你所在团队已经使用了 Faker 这类造数工具,没必要重复造轮子,可以将其作为自定义 Provider 接入。Faker 本身也提供了手机号生成能力,但国内号段细节和自定义前缀扩展仍需要自己维护。本文示例代码的价值就在于此:逻辑简单,可定制性强,不依赖大量第三方库。

8.7 代码审查的小建议

这类工具虽然小,但也应该走代码审查。重点看三处:前缀库是否含有过期号段,随机逻辑是否用了安全模块,导出文件是否包含了多余的真实数据。哪怕它是“一次性脚本”,也建议放到仓库里管理,而不是存在某台机器的/tmp下。

9. 总结与后续学习方向

回到开头那句话:真正想要船长的电话号码的人不多,但需要一个“安全、合规、可批量生成虚拟手机号工具”的开发者很多。

本文从手机号格式规则入手,讲清楚了虚拟手机号生成的核心逻辑:前缀库决定真实性,随机策略决定性能与安全性,去重机制决定批量质量,校验函数决定最终可靠性。然后给了一个可以直接运行的 Python 命令行脚本,支持指定数量、指定前缀、CSV/JSON 导出,并在最后强调了测试数据管理的工程注意点。

如果你想继续深入,可以往这几个方向走:

第一,把生成器扩展成更通用的测试数据工厂,不仅生成手机号,还生成姓名、地址、邮箱、身份证脱敏数据,形成一套造数平台。

第二,学习如何使用 Faker 自定义 Provider,和本文的前缀库思路结合起来,让团队造数体验更好。

第三,研究测试数据生命周期管理,包括数据生成、数据标记、数据清理,以及如何在 CI 流水线里自动生成和销毁测试数据。

第四,如果你的工作涉及数据库大批量造数,可以结合copyinsert ... select等批量导入方式,把生成结果直接灌入测试库,但要注意充分利用事务和批量提交,避免导入性能问题。

最后提醒一句:虚拟手机号是测试工具的辅助手段,不是绕过验证、批量注册、短信轰炸的“万能钥匙”。把工具用在开发和测试的合规场景里,它能成为团队效率提升的好帮手;用在错误的地方,只会给项目和自己带来风险。这一点,比任何代码细节都重要。

建议先把这份脚本收藏起来,下次需要批量测试手机号时,直接改改前缀库就能跑。

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

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

立即咨询