
在软件测试、大数据开发、算法训练或教学演示中,经常需要大量模拟的用户身份信息。其中,身份证号与姓名是最基础的组合。然而,直接使用真实数据不仅违反《个人信息保护法》,还可能带来严重的法律后果。因此,掌握如何安全、批量地生成合法格式的虚拟身份证号与姓名,是每个技术从业者的必备技能。本文将从身份证号的结构与校验算法入手,逐步构建一套可落地、可扩展的生成方案,并给出Python代码示例,最终产出10000条不重复、符合校验规则的数据。
中国大陆居民身份证号采用18位编码,由四部分组成:地址码(6位)、出生日期码(8位)、顺序码(3位)和校验码(1位)。最后一位可能是数字0-9或字母X。
要生成“合法”的虚拟身份证号,必须严格按照校验算法计算最后一位。否则,生成的号码会被系统拒绝,无法用于测试场景(如校验逻辑、数据库唯一约束等)。
校验码的计算步骤如下:
例如,前17位为11010119900101001,计算得S=?最终校验码为?这个过程可以写成一个函数,方便批量生成时调用。
注意:校验码为X通常写作大写字母,但部分系统也接受小写。生成时统一使用大写。
我们的目标是生成10000个不同的、符合校验规则的身份证号。关键在于如何保证唯一性和分布合理性。
可以从国家统计局公布的行政区划代码表中选取若干个常用区县代码。测试数据通常不需要覆盖所有地区,选出10个左右即可(如北京市东城区、上海市浦东新区、广州市天河区等)。每个地址码生成约1000个号码,总数即可达到10000。
为了模拟真实分布,出生日期应覆盖一定的年限范围,例如从1970年到2005年。可以随机生成合法的年月日,注意闰年2月29日只出现于闰年。年份范围越小,同一天出生的人数越多,但顺序码最多只有999个(000-999),所以需要控制密度。如果同一地址码和同一天出生的人数超过999,则必须使用不同的地址码或日期来区分。
一个简单策略:为每个地址码预设一个起始日期,然后每天递增,每张身份证号对应一个日期,这样顺序码从001开始递增,不易重复。
顺序码最后一位决定性别。我们可以随机分配,比如50%男性(奇数),50%女性(偶数)。但注意顺序码范围000-999,其中000-099通常保留,实际可用001-999。为避免与真实号码冲突,建议从001开始。
将前17位拼接后调用校验函数,得到最终18位身份证号。
姓名生成需要遵循两个原则:看起来像真名,但绝对不与真实人物关联。可以采用“常见姓氏 + 常见名字组合”的方式,并加入随机化避免重复。
收集中国最常见的100个姓氏,按频率加权。例如王、李、张、刘、陈等排在前列。也可以加入一些复姓(欧阳、司马等),增加多样性。
准备单字名和双字名。单字名可以从常用字中选取(如伟、芳、娜、强等),双字名可以组合(如志强、秀英、丽华等)。注意避免使用过于生僻或敏感的字。
随机选择姓氏,再随机选择单字或双字名(可设定比例,比如70%双字名,30%单字名)。每次生成后检查是否与已有姓名重复,若重复则重新生成。由于10000个姓名相对于组合空间很小,重复概率极低。
下面给出一个完整的Python脚本示例,可以生成10000个虚拟身份证号与姓名,并输出为CSV文件。代码中已包含关键注释,方便读者理解与修改。
import random
import datetime
# 地址码列表(示例,可扩展)
area_codes = ['110101', '310115', '440305', '330102', '510105', '210202', '320102', '440106', '500101', '120101']
# 校验码表
check_code_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8', 5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
def calc_check_digit(id17):
s = sum(int(ch) * w for ch, w in zip(id17, weights))
return check_code_map[s % 11]
def generate_id(area_code, birth_date, seq):
id17 = area_code + birth_date + seq
return id17 + calc_check_digit(id17)
# 姓名字典
sumames = ['王','李','张','刘','陈','杨','赵','黄','周','吴','徐','孙','胡','朱','高','林','何','郭','马','罗','梁','宋','郑','谢','韩','唐','冯','于','董','萧','程','曹','袁','邓','许','傅','沈','曾','彭','吕','苏','卢','蒋','蔡','贾','丁','魏','薛','叶','阎','余','潘','杜','戴','夏','钟','汪','田','任','姜','范','方','石','姚','谭','廖','邹','熊','金','陆','郝','孔','白','崔','康','毛','邱','秦','江','史','顾','侯','邵','孟','龙','万','段','漕','钱','汤','尹','黎','易','常','武','乔','贺','赖','龚','文']
single_names = ['伟','芳','娜','秀英','敏','静','丽','强','磊','军','洋','勇','艳','杰','娟','涛','明','超','秀兰','霞','平','刚','桂英','文','华','飞','红','建华','玉兰','国','志强','丽华','志明','建华','秀英','卫国','国平','国庆','庆华','建国','国栋','志伟','秀梅','海燕','金花','桂芳','德明','海涛','宏','亮','峰','强','健','辉','杰','凯','俊','帅','鹏','浩','磊','雷','涛','波','斌','勇','军','伟','超','明','旭','阳','东','林','彬','森','荣','华','富','贵','祥','瑞','福','禄','寿','喜','财','吉','昌','盛','新','颖','睿','智','慧','敏','捷','思','远','博','雅','墨','涵','琳','瑶','瑶','萱','婷','媛','颖','悦','欣','乐','怡','倩','佳','琪','琳','蕾','洁','莹','雪','霜','露','云','霞','虹','月','星','辰','晨','曦','光','辉','明','亮','丽','美','艳','芬芳','香','兰','菊','梅','竹','松','枫','柏','楠','桂','榕','桦','杨','柳','桃','李','杏','梨','樱','海棠','牡丹','玫瑰','百合','茉莉','荷花','莲花','紫薇','紫荆','木棉','榕树','柏树','梧桐','银杏','枫树','桦树','松树','杉树','橡树','柳树','杨树','桃树','李树','杏树','梨树','樱桃','海棠','牡丹','玫瑰','百合','茉莉','荷花','莲花','紫薇','紫荆','木棉']
# 双字名由两个单字名组合,这里简化,直接使用预定义双字名列表
double_names = ['志强','秀英','丽华','建国','国强','桂英','秀兰','明辉','伟杰','俊杰','晓明','晓红','晓丽','晓东','晓峰','晓燕','晓霞','晓娟','晓玲','晓芳','晓华','晓敏','晓静','晓婷','晓洁','晓琳','晓蕾','晓雪','晓雯','晓晨','晓光','晓曦','晓月','晓星','晓云','晓虹','晓露','晓霜','晓冰','晓雪','晓枫','晓楠','晓柏','晓松','晓梅','晓兰','晓菊','晓竹','晓荷','晓莲','晓蓉','晓芙','晓薇','晓萱','晓瑶','晓琪','晓琳','晓蕾','晓洁','晓莹','晓雪','晓霜','晓露','晓云','晓霞','晓虹','晓月','晓星','晓晨','晓曦','晓光','晓辉','晓明','晓亮','晓阳','晓晴','晓雨','晓风','晓雪','晓霜','晓露','晓云','晓霞','晓虹','晓月','晓星','晓晨','晓曦','晓光','晓辉','晓明','晓亮','晓阳','晓晴','晓雨','晓风','晓雪','晓霜','晓露','晓云','晓霞','晓虹','晓月','晓星','晓晨','晓曦','晓光','晓辉','晓明','晓亮','晓阳','晓晴','晓雨','晓风','晓雪','晓霜','晓露','晓云','晓霞','晓虹','晓月','晓星','晓晨','晓曦','晓光','晓辉','晓明','晓亮','晓阳','晓晴','晓雨','晓风','晓雪','晓霜','晓露','晓云','晓霞','晓虹','晓月','晓星','晓晨','晓曦','晓光','晓辉','晓明','晓亮','晓阳','晓晴','晓雨','晓风']
def generate_name():
surname = random.choice(sumames)
# 70%概率双字名,30%单字名
if random.random() < 0.7:
given = random.choice(double_names)
else:
given = random.choice(single_names)
return surname + given
# 主生成逻辑
def generate_dataset(num=10000):
data = []
# 每个地址码分配大致均匀
avg = num // len(area_codes)
remainder = num % len(area_codes)
start_date = datetime.date(1970, 1, 1)
end_date = datetime.date(2005, 12, 31)
total_days = (end_date - start_date).days + 1
for idx, area in enumerate(area_codes):
count = avg + (1 if idx < remainder else 0)
# 为每个地址码生成一个不重复的日期序列和顺序码序列
# 使用日期递增,顺序码从001到999循环
# 先计算需要多少天:每天最多999个,所以需要的天数 = ceil(count/999)
days_needed = (count + 998) // 999
# 随机选取days_needed个日期(不重复)
# 为避免日期重叠,可以随机选择起始日期,然后每天递增
# 简单方法:随机选一个起始日期,然后每天递增,直到填满
# 但需要确保日期在范围内
# 更好的做法:随机选择days_needed个不同的日期
# 这里使用打乱所有日期,取前days_needed个
all_dates = [start_date + datetime.timedelta(days=i) for i in range(total_days)]
random.shuffle(all_dates)
selected_dates = all_dates[:days_needed]
selected_dates.sort() # 排序方便顺序码分配
seq_counter = 1
for date in selected_dates:
# 每天最多生成999个,如果还剩余小于999,则生成剩余数量
remaining = count - (days_needed - 1) * 999 if date == selected_dates[-1] else 999
for _ in range(remaining):
birth = date.strftime('%Y%m%d')
# 顺序码:3位,从001开始,奇数男性偶数女性(随机)
gender = random.choice(['1','3','5','7','9','0','2','4','6','8']) # 最后一位决定性别,这里随机
# 更严谨:生成3位数字,最后一位奇偶控制性别,但这里简化
seq = f'{seq_counter:03d}'
id_number = generate_id(area, birth, seq)
name = generate_name()
data.append((id_number, name))
seq_counter += 1
if seq_counter > 999:
seq_counter = 1
# 确保总数正确,但可能因为余数问题略有偏差,可调整
return data[:num]
# 运行
dataset = generate_dataset(10000)
# 输出到文件
with open('virtual_id_name.csv', 'w', encoding='utf-8') as f:
f.write('ID,Name\n')
for id_, name in dataset:
f.write(f'{id_},{name}\n')
print('生成完成,共10000条记录。')
代码说明:
注意:每次运行脚本结果不同,因为使用了随机化。也可以固定随机种子以便复现。
生成虚拟数据只是为了测试和开发,必须严格遵循以下原则:
法律提示:根据《中华人民共和国刑法》第253条之一,侵犯公民个人信息罪,非法获取、出售或提供公民个人信息,情节严重的,处三年以下有期徒刑或拘役,并处或单处罚金。因此,绝对不要尝试收集、使用真实身份证号与姓名。
生成的数据还需要验证其可用性。例如,检查身份证号是否通过校验、出生日期是否合法、顺序码是否有效。可以编写一个验证函数,对于每个生成的ID,再次使用校验算法确认。同时,可以检查姓名中是否包含不常见的用字,避免测试时出错。
此外,还可以生成带关联关系的测试数据,例如同一家庭住址、同一手机号段等,用于模拟更真实的数据分布。但需注意,这些关联信息也应虚拟生成,不可使用真实数据。
对于大数据量(如10000条),应考虑性能:使用Python生成10000条记录只需几秒,但若需生成百万级,建议使用更高效的语言(如Go、C++)或并行生成。
由于算法相同,生成的号码可能与真实号码重合。概率约为1/10^17,忽略不计。但如果担心,可以在生成时使用非法地址码(如999999)或非法出生日期(如2025年),但这样会破坏校验。更推荐:内部测试时允许,生产环境禁止。
顺序码最后一位奇数为男,偶数为女。在生成顺序码时,可以控制最后一位:若期望性别为男,则最后一位随机选奇数;若期望女,则随机选偶数。代码中可加入性别参数。
10000个姓名在几万个组合中重复概率很低,但可能重复。可以在生成时维护一个集合,检查是否已存在,若存在则重新生成。但为了效率,可允许少量重复,因为真实姓名也有重名。
本文从身份证号校验算法、姓名生成策略到批量Python实现,完整讲述了如何安全生成10000个虚拟身份证号与姓名。核心要点:严格遵循校验规则、使用常见姓氏和名字库、保证数据唯一性、绝不含真实信息。这套方案可用于自动化测试、压力测试、数据脱敏教学等场景,帮助开发者在合法合规的前提下获得海量测试数据。最后再次强调,切勿将此类技术用于非法获取或滥用他人信息。技术是工具,使用得当方为硬核。
你是不是也遇到过这种情况?煤电厂建好了,传送带拉满了,结果出门探索半小时回来,整个基地一片漆黑。发电机全停了,生产线全部卡死,连重新启动都得靠手挖煤炭。这几乎是每个《幸福工厂》玩家都会经历的一次“大停...
你刚进地下城被盗贼秒了。换战士又被游侠放风筝到死。试了法师,法术没读完就被打断。这不是你菜,是你还没摸透这套硬核生存逻辑。 Dark and Darker是一款死亡率极高的地下城掠夺游戏。新手前10小...
穿上警服,发动警车,对讲机里传来调度指令。这是《The Precinct》——一款让你体验上世纪80年代美国警员日常的开放世界游戏。但别以为穿上制服就能立刻当英雄。巡逻流程怎么走、案件处理该用什么步骤...
你刚从飞船残骸里爬出来。周围全是毒雾和废铁,背包里只有几块干粮,飞艇只剩一个空壳。这时候该做的第一件事是什么?大部分人选择先捡垃圾。结果呢?食物吃完了,燃料烧光了,飞艇还没飞起来,角色饿死在半路上。...
刚入坑《白荆回廊》的玩家,最常问的一句话就是:“开局到底怎么玩才不会浪费资源?”这游戏的养成线比你想的深——角色等级、技能、突破、战术刻痕,每条线都吃资源。加上六大职业和元素反应机制,头三天很容易踩坑...
刚进《Coral Island》是不是感觉有点懵?农场一片废墟,背包空荡荡,NPC一个都不认识。别慌,这不是你一个人遇到的问题。大多数玩家在春季第一周都会陷入同样的困境:体力不够用、钱不够花、不知道每...