1. 项目背景与需求解析
最近在整理公司设计部门的素材库时遇到一个典型场景:需要将2000多个未分类的PSD素材文件随机分配到20个不同主题的文件夹中,每个子文件夹存放约100个文件。手动操作不仅耗时耗力,还容易造成分配不均。这正是"随机分配文件到多级目录"工具的用武之地。
这个Python脚本的核心功能可以拆解为三个关键需求点:
- 源目录遍历:递归获取指定路径下所有文件(支持过滤特定扩展名)
- 随机分配算法:确保文件被均匀分配到目标文件夹
- 数量控制:精确控制每个子文件夹的文件数量上限
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术实现方案设计
2.1 整体架构设计
采用三层处理流程:
- 输入层:处理命令行参数(源路径/目标路径/分配数量)
- 处理层:文件收集→随机打乱→分配计算
- 输出层:执行文件移动并生成日志
python复制import argparse
import os
import random
import shutil
from pathlib import Path
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("--src", required=True)
parser.add_argument("--dst", required=True)
parser.add_argument("--num", type=int, required=True)
return parser.parse_args()
2.2 关键技术点实现
2.2.1 文件收集与过滤
使用os.walk递归遍历时需要注意:
- 跳过隐藏文件(以
.开头的文件) - 支持通过扩展名过滤文件类型
- 处理符号链接等特殊文件
python复制def collect_files(src_dir):
file_list = []
for root, _, files in os.walk(src_dir):
for file in files:
if not file.startswith('.'): # 忽略隐藏文件
file_list.append(Path(root) / file)
return file_list
2.2.2 随机分配算法
采用Fisher-Yates洗牌算法确保真随机:
- 对文件列表进行原地随机排序
- 计算每个子文件夹应分配的文件数
- 使用切片操作批量分配文件
python复制def random_distribute(files, dst_dirs, num_per_dir):
random.shuffle(files)
for i, dst in enumerate(dst_dirs):
start = i * num_per_dir
end = (i + 1) * num_per_dir
yield dst, files[start:end]
3. 完整实现代码
python复制import argparse
import os
import random
import shutil
from pathlib import Path
def main():
args = parse_arguments()
# 校验目标目录是否存在
dst_dirs = [d for d in Path(args.dst).iterdir() if d.is_dir()]
if not dst_dirs:
raise ValueError("目标目录下没有子文件夹")
files = collect_files(args.src)
if len(files) < len(dst_dirs) * args.num:
raise ValueError("文件总数不足")
# 执行分配
for dst_dir, assigned_files in random_distribute(files, dst_dirs, args.num):
for file in assigned_files:
shutil.move(str(file), str(dst_dir / file.name))
if __name__ == "__main__":
main()
4. 高级功能扩展
4.1 保留目录结构
修改移动逻辑,在目标文件夹中保持源目录的相对路径:
python复制def move_preserve_structure(src_file, dst_dir):
relative_path = src_file.relative_to(SOURCE_ROOT)
target_path = dst_dir / relative_path
target_path.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(src_file), str(target_path))
4.2 进度显示
添加tqdm进度条增强用户体验:
python复制from tqdm import tqdm
for dst_dir, assigned_files in random_distribute(files, dst_dirs, args.num):
for file in tqdm(assigned_files, desc=f"Moving to {dst_dir.name}"):
shutil.move(str(file), str(dst_dir / file.name))
5. 实际应用案例
5.1 教学资料分配
将500个教学视频随机分配到10个班级文件夹,每个班级50个视频:
bash复制python file_distributor.py --src /videos --dst /classes --num 50
5.2 数据集划分
把10万张图片均匀分配到100个训练集文件夹:
bash复制python file_distributor.py --src /images --dst /train --num 1000
6. 性能优化建议
- 批量操作:对于超大规模文件(10万+),改用rsync替代shutil
- 多进程处理:使用multiprocessing加速文件移动
- 内存优化:对于超大目录,改用生成器替代列表存储文件路径
python复制from multiprocessing import Pool
def move_file(args):
src, dst = args
shutil.move(src, dst)
with Pool(8) as p:
p.map(move_file, [(str(f), str(dst_dir/f.name)) for f in assigned_files])
7. 异常处理与日志
完善的错误处理应包括:
- 权限不足时的友好提示
- 文件名冲突时的自动重命名
- 详细的操作日志记录
python复制try:
shutil.move(src, dst)
except PermissionError:
print(f"权限不足: {src}")
except shutil.Error as e:
new_name = dst.with_stem(f"{dst.stem}_dup")
shutil.move(src, new_name)
这个脚本在我司的素材管理系统中已经稳定运行2年,累计处理超过50万份文件。最关键的实践经验是:一定要在正式操作前先用--dry-run参数测试分配方案,避免误操作导致文件混乱。
