序言:文件自动化的必要性

在使用计算机时,我们会花费大量时间处理文件和文件夹。重命名数百个文件、查找并整理符合特定条件的文件、为备份复制文件等工作,如果手动操作既耗时又容易出错。

Python提供了强大的文件系统处理工具。在这一篇中,我们将学习如何使用Python的os、pathlib、shutil、glob模块高效地自动化文件和文件夹操作。

1. os模块基础

os模块是Python与操作系统交互的基本模块。它提供了文件系统操作必需的功能。

1.1 基本路径操作

import os

# 检查当前工作目录
current_dir = os.getcwd()
print(f"当前目录:{current_dir}")

# 更改工作目录
os.chdir("/path/to/directory")

# 访问环境变量
home_dir = os.environ.get('HOME')  # Linux/Mac
user_profile = os.environ.get('USERPROFILE')  # Windows

# 用户主目录(跨平台)
home = os.path.expanduser("~")
print(f"主目录:{home}")

1.2 路径操作

import os

# 路径连接(使用适合操作系统的分隔符)
full_path = os.path.join("folder", "subfolder", "file.txt")
print(full_path)  # Windows: folder\subfolder\file.txt

# 路径分离
directory = os.path.dirname("/path/to/file.txt")  # /path/to
filename = os.path.basename("/path/to/file.txt")  # file.txt

# 分离文件名和扩展名
name, extension = os.path.splitext("document.pdf")
print(f"文件名:{name},扩展名:{extension}")  # document, .pdf

# 获取绝对路径
abs_path = os.path.abspath("relative/path/file.txt")

# 检查路径是否存在
if os.path.exists("/path/to/check"):
    print("路径存在。")

# 检查是文件还是文件夹
if os.path.isfile("/path/to/file.txt"):
    print("是文件。")

if os.path.isdir("/path/to/folder"):
    print("是文件夹。")

1.3 目录列表查询

import os

# 目录内项目列表
items = os.listdir("/path/to/directory")
print(items)  # ['file1.txt', 'folder1', 'file2.py']

# 仅筛选文件
files = [f for f in os.listdir(".") if os.path.isfile(f)]

# 仅筛选文件夹
folders = [f for f in os.listdir(".") if os.path.isdir(f)]

# 仅筛选特定扩展名的文件
python_files = [f for f in os.listdir(".") if f.endswith(".py")]

1.4 目录遍历(os.walk)

import os

# 遍历所有子目录
for root, dirs, files in os.walk("/path/to/start"):
    print(f"当前目录:{root}")
    print(f"子文件夹:{dirs}")
    print(f"文件:{files}")
    print("-" * 50)

# 查找所有特定扩展名的文件
def find_files_by_extension(start_path, extension):
    """在指定路径中查找所有特定扩展名的文件。"""
    found_files = []
    for root, dirs, files in os.walk(start_path):
        for file in files:
            if file.endswith(extension):
                full_path = os.path.join(root, file)
                found_files.append(full_path)
    return found_files

# 查找所有.txt文件
txt_files = find_files_by_extension(".", ".txt")
for f in txt_files:
    print(f)

2. pathlib模块使用

pathlib是Python 3.4引入的面向对象的文件系统路径库。比os.path更直观、更现代化地处理路径。

2.1 Path对象基础

from pathlib import Path

# 创建Path对象
p = Path("/path/to/file.txt")
current = Path(".")
home = Path.home()

# 路径连接(使用/运算符)
full_path = Path.home() / "Documents" / "project" / "file.txt"
print(full_path)

# 路径属性
print(p.name)       # file.txt(文件名)
print(p.stem)       # file(不含扩展名的文件名)
print(p.suffix)     # .txt(扩展名)
print(p.parent)     # /path/to(父目录)
print(p.parts)      # ('/', 'path', 'to', 'file.txt')

# 绝对路径
abs_path = Path("relative/path").resolve()

# 检查是否存在
if p.exists():
    print("存在。")

if p.is_file():
    print("是文件。")

if p.is_dir():
    print("是目录。")

2.2 目录探索

from pathlib import Path

# 当前目录的所有项目
for item in Path(".").iterdir():
    print(item)

# 特定模式匹配(仅当前目录)
for py_file in Path(".").glob("*.py"):
    print(py_file)

# 递归模式匹配(所有子目录)
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# 搜索多个扩展名
extensions = ["*.jpg", "*.png", "*.gif"]
images = []
for ext in extensions:
    images.extend(Path(".").rglob(ext))

# 仅筛选文件
files_only = [p for p in Path(".").iterdir() if p.is_file()]

# 仅筛选文件夹
dirs_only = [p for p in Path(".").iterdir() if p.is_dir()]

2.3 文件/文件夹创建和删除

from pathlib import Path

# 创建目录
new_dir = Path("new_folder")
new_dir.mkdir(exist_ok=True)  # 即使已存在也不报错

# 创建嵌套目录
nested_dir = Path("parent/child/grandchild")
nested_dir.mkdir(parents=True, exist_ok=True)

# 创建文件(空文件)
new_file = Path("new_file.txt")
new_file.touch()

# 删除文件
if new_file.exists():
    new_file.unlink()

# 删除空目录
if new_dir.exists() and new_dir.is_dir():
    new_dir.rmdir()  # 目录必须为空

# 重命名文件
old_path = Path("old_name.txt")
new_path = Path("new_name.txt")
if old_path.exists():
    old_path.rename(new_path)

3. 文件读写

了解Python中读写文件的方法。使用with语句可以自动关闭文件,因此推荐使用。

3.1 文本文件处理

# 写入文件
with open("example.txt", "w", encoding="utf-8") as f:
    f.write("第一行\n")
    f.write("第二行\n")

# 追加内容到文件
with open("example.txt", "a", encoding="utf-8") as f:
    f.write("追加的行\n")

# 读取文件(全部)
with open("example.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# 读取文件(逐行)
with open("example.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())  # 去除换行符

# 读取文件(转为列表)
with open("example.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()
    print(lines)  # ['第一行\n', '第二行\n', ...]

# 一次性写入多行
lines_to_write = ["行1\n", "行2\n", "行3\n"]
with open("output.txt", "w", encoding="utf-8") as f:
    f.writelines(lines_to_write)

3.2 使用pathlib读写文件

from pathlib import Path

# 简便的文件写入
Path("simple.txt").write_text("Hello, World!", encoding="utf-8")

# 简便的文件读取
content = Path("simple.txt").read_text(encoding="utf-8")
print(content)

# 读取二进制文件
binary_content = Path("image.png").read_bytes()

# 写入二进制文件
Path("copy.png").write_bytes(binary_content)

3.3 CSV文件处理

import csv

# 写入CSV
data = [
    ["姓名", "年龄", "城市"],
    ["张三", 30, "北京"],
    ["李四", 25, "上海"],
    ["王五", 28, "广州"]
]

with open("data.csv", "w", newline="", encoding="utf-8-sig") as f:
    writer = csv.writer(f)
    writer.writerows(data)

# 读取CSV
with open("data.csv", "r", encoding="utf-8-sig") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

# 使用字典处理CSV
with open("data.csv", "r", encoding="utf-8-sig") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(f"{row['姓名']}今年{row['年龄']}岁。")

3.4 JSON文件处理

import json

# 写入JSON
data = {
    "name": "张三",
    "age": 30,
    "skills": ["Python", "JavaScript", "SQL"],
    "address": {
        "city": "北京",
        "district": "朝阳区"
    }
}

with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# 读取JSON
with open("data.json", "r", encoding="utf-8") as f:
    loaded_data = json.load(f)
    print(loaded_data["name"])
    print(loaded_data["skills"])

4. 文件夹创建/删除/移动

4.1 创建文件夹

import os
from pathlib import Path

# 使用os创建文件夹
os.makedirs("parent/child/grandchild", exist_ok=True)

# 使用pathlib创建文件夹
Path("another/nested/folder").mkdir(parents=True, exist_ok=True)

# 创建按日期命名的文件夹
from datetime import datetime

today = datetime.now().strftime("%Y-%m-%d")
daily_folder = Path("backups") / today
daily_folder.mkdir(parents=True, exist_ok=True)

4.2 删除文件夹

import os
import shutil
from pathlib import Path

# 删除空文件夹
os.rmdir("empty_folder")
# 或者
Path("empty_folder").rmdir()

# 删除有内容的文件夹(注意:无法恢复!)
shutil.rmtree("folder_with_contents")

# 安全删除(确认后删除)
def safe_delete_folder(folder_path):
    """安全地删除文件夹。"""
    path = Path(folder_path)
    if not path.exists():
        print(f"'{folder_path}'不存在。")
        return False

    # 检查文件夹内容
    items = list(path.rglob("*"))
    file_count = sum(1 for item in items if item.is_file())
    folder_count = sum(1 for item in items if item.is_dir())

    print(f"要删除的文件夹:{folder_path}")
    print(f"包含的文件:{file_count}个")
    print(f"包含的文件夹:{folder_count}个")

    confirm = input("确定要删除吗?(yes/no):")
    if confirm.lower() == "yes":
        shutil.rmtree(path)
        print("删除完成!")
        return True
    else:
        print("已取消。")
        return False

5. 文件搜索(glob)

glob模块使用Unix shell风格的模式匹配来搜索文件。

5.1 glob基本用法

import glob

# 当前目录的所有.txt文件
txt_files = glob.glob("*.txt")

# 特定文件夹的所有.py文件
py_files = glob.glob("src/*.py")

# 递归搜索(所有子文件夹)
all_py_files = glob.glob("**/*.py", recursive=True)

# 搜索多个扩展名
import itertools
extensions = ["*.jpg", "*.png", "*.gif"]
images = list(itertools.chain.from_iterable(
    glob.glob(ext, recursive=True) for ext in ["**/" + e for e in extensions]
))

# 模式匹配示例
# ? : 单个字符
# * : 任意字符(0个或多个)
# [abc] : a、b、c中的一个
# [0-9] : 数字

files = glob.glob("file?.txt")      # file1.txt, fileA.txt
files = glob.glob("data[0-9].csv")  # data0.csv ~ data9.csv
files = glob.glob("[!_]*.py")       # 不以下划线开头的.py文件

5.2 pathlib的glob

from pathlib import Path

# 在当前目录搜索
for txt_file in Path(".").glob("*.txt"):
    print(txt_file)

# 递归搜索
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# 复杂模式
for file in Path("data").glob("**/report_*.xlsx"):
    print(file)

6. 文件复制/移动(shutil)

shutil模块提供文件和文件夹的高级操作(复制、移动、删除)功能。

6.1 文件复制

import shutil

# 复制文件(不含元数据)
shutil.copy("source.txt", "destination.txt")

# 复制文件(含元数据)
shutil.copy2("source.txt", "destination.txt")

# 复制到文件夹(保留原文件名)
shutil.copy("source.txt", "backup_folder/")

# 复制整个文件夹
shutil.copytree("source_folder", "destination_folder")

# 排除特定文件复制
def ignore_patterns(directory, files):
    """忽略特定模式的文件。"""
    return [f for f in files if f.endswith('.pyc') or f.startswith('.')]

shutil.copytree("source", "dest", ignore=ignore_patterns)

# 或使用内置函数
shutil.copytree("source", "dest",
                ignore=shutil.ignore_patterns('*.pyc', '*.tmp', '__pycache__'))

6.2 文件移动

import shutil

# 移动文件
shutil.move("source.txt", "new_location/source.txt")

# 重命名文件(在同一文件夹内移动)
shutil.move("old_name.txt", "new_name.txt")

# 移动文件夹
shutil.move("source_folder", "new_location/")

# 安全移动函数
from pathlib import Path

def safe_move(source, destination):
    """安全地移动文件。"""
    src = Path(source)
    dst = Path(destination)

    if not src.exists():
        print(f"源文件'{source}'不存在。")
        return False

    # 如果目标是文件夹,保留源文件名
    if dst.is_dir():
        dst = dst / src.name

    # 如果目标已存在则确认
    if dst.exists():
        confirm = input(f"'{dst}'已存在。是否覆盖?(y/n):")
        if confirm.lower() != 'y':
            print("已取消。")
            return False

    shutil.move(str(src), str(dst))
    print(f"'{source}' -> '{dst}' 移动完成")
    return True

7. 批量重命名文件

批量重命名文件是自动化的典型应用场景。

7.1 基本文件名更改

import os
from pathlib import Path

# 添加前缀
def add_prefix(folder, prefix):
    """为文件夹内所有文件添加前缀。"""
    folder_path = Path(folder)
    for file in folder_path.iterdir():
        if file.is_file():
            new_name = folder_path / f"{prefix}{file.name}"
            file.rename(new_name)
            print(f"'{file.name}' -> '{new_name.name}'")

# 添加后缀
def add_suffix(folder, suffix):
    """为文件名添加后缀(扩展名前)。"""
    folder_path = Path(folder)
    for file in folder_path.iterdir():
        if file.is_file():
            new_name = folder_path / f"{file.stem}{suffix}{file.suffix}"
            file.rename(new_name)
            print(f"'{file.name}' -> '{new_name.name}'")

7.2 按序号重命名文件

from pathlib import Path

def rename_with_sequence(folder, base_name, start=1, padding=3):
    """将文件名更改为序号。

    例:image_001.jpg, image_002.jpg, ...
    """
    folder_path = Path(folder)
    files = sorted([f for f in folder_path.iterdir() if f.is_file()])

    for i, file in enumerate(files, start=start):
        # 格式化序号(001, 002, ...)
        sequence = str(i).zfill(padding)
        new_name = folder_path / f"{base_name}_{sequence}{file.suffix}"

        file.rename(new_name)
        print(f"'{file.name}' -> '{new_name.name}'")

7.3 基于日期重命名文件

from pathlib import Path
from datetime import datetime
import os

def rename_with_date(folder, include_time=False):
    """根据文件的修改日期重命名。"""
    folder_path = Path(folder)

    for file in folder_path.iterdir():
        if file.is_file():
            # 获取文件修改时间
            mtime = os.path.getmtime(file)
            date_obj = datetime.fromtimestamp(mtime)

            if include_time:
                date_str = date_obj.strftime("%Y%m%d_%H%M%S")
            else:
                date_str = date_obj.strftime("%Y%m%d")

            new_name = folder_path / f"{date_str}_{file.name}"

            # 防止重复
            counter = 1
            while new_name.exists():
                new_name = folder_path / f"{date_str}_{counter}_{file.name}"
                counter += 1

            file.rename(new_name)
            print(f"'{file.name}' -> '{new_name.name}'")

7.4 使用正则表达式重命名文件

import re
from pathlib import Path

def rename_with_regex(folder, pattern, replacement):
    """使用正则表达式更改文件名。"""
    folder_path = Path(folder)

    for file in folder_path.iterdir():
        if file.is_file():
            new_name = re.sub(pattern, replacement, file.stem)
            if new_name != file.stem:
                new_path = folder_path / f"{new_name}{file.suffix}"
                file.rename(new_path)
                print(f"'{file.name}' -> '{new_path.name}'")

# 使用示例
# 将空格替换为下划线
rename_with_regex(".", r"\s+", "_")

# 删除特殊字符
rename_with_regex(".", r"[^\w\-_.]", "")

8. 查找重复文件

为了节省硬盘空间,我们来编写一个查找和整理重复文件的脚本。

import hashlib
from pathlib import Path
from collections import defaultdict

def get_file_hash(filepath, chunk_size=8192):
    """计算文件的MD5哈希值。"""
    hasher = hashlib.md5()
    with open(filepath, 'rb') as f:
        while chunk := f.read(chunk_size):
            hasher.update(chunk)
    return hasher.hexdigest()

def find_duplicates(folder):
    """查找文件夹内的重复文件。"""
    folder_path = Path(folder)

    # 第1步:按文件大小分组
    size_dict = defaultdict(list)
    for file in folder_path.rglob("*"):
        if file.is_file():
            size_dict[file.stat().st_size].append(file)

    # 第2步:只比较相同大小文件的哈希
    hash_dict = defaultdict(list)
    for size, files in size_dict.items():
        if len(files) > 1:  # 只有相同大小的文件有2个以上时
            for file in files:
                file_hash = get_file_hash(file)
                hash_dict[file_hash].append(file)

    # 第3步:筛选重复文件
    duplicates = {h: files for h, files in hash_dict.items() if len(files) > 1}

    return duplicates

def report_duplicates(folder):
    """输出重复文件报告。"""
    duplicates = find_duplicates(folder)

    if not duplicates:
        print("没有重复文件。")
        return

    total_wasted = 0
    print("=" * 60)
    print("重复文件报告")
    print("=" * 60)

    for hash_value, files in duplicates.items():
        file_size = files[0].stat().st_size
        wasted = file_size * (len(files) - 1)
        total_wasted += wasted

        print(f"\n哈希:{hash_value[:16]}...")
        print(f"文件大小:{file_size:,} bytes")
        print(f"重复数量:{len(files)}个")
        print("文件列表:")
        for f in files:
            print(f"  - {f}")

    print("\n" + "=" * 60)
    print(f"总浪费空间:{total_wasted:,} bytes ({total_wasted / 1024 / 1024:.2f} MB)")
    print("=" * 60)

def delete_duplicates(folder, keep='first'):
    """删除重复文件。

    Args:
        folder: 目标文件夹
        keep: 'first'(保留第一个)或 'newest'(保留最新文件)
    """
    duplicates = find_duplicates(folder)

    deleted_count = 0
    freed_space = 0

    for hash_value, files in duplicates.items():
        if keep == 'newest':
            # 按修改时间排序(最新的在前)
            files = sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)

        # 保留第一个文件,删除其余的
        for file in files[1:]:
            file_size = file.stat().st_size
            print(f"删除:{file}")
            file.unlink()
            deleted_count += 1
            freed_space += file_size

    print(f"\n删除的文件:{deleted_count}个")
    print(f"释放的空间:{freed_space:,} bytes ({freed_space / 1024 / 1024:.2f} MB)")

9. 文件整理自动化项目

让我们综合到目前为止学到的内容,创建一个实用的文件整理自动化项目。

"""
高级文件整理自动化脚本
- 按扩展名分类
- 按日期分类
- 按大小分类
- 处理重复文件
- 支持日志
"""

import os
import shutil
import hashlib
import logging
from pathlib import Path
from datetime import datetime
from collections import defaultdict

# 日志设置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('file_organizer.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 扩展名到类别的映射
CATEGORIES = {
    'Images': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp', '.ico'],
    'Documents': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.rtf', '.odt'],
    'Videos': ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm'],
    'Music': ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.wma', '.m4a'],
    'Archives': ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'],
    'Programs': ['.exe', '.msi', '.dmg', '.deb', '.rpm', '.apk'],
    'Code': ['.py', '.js', '.html', '.css', '.java', '.cpp', '.c', '.h', '.json', '.xml'],
    'Data': ['.csv', '.sql', '.db', '.sqlite']
}

def get_category(extension):
    """返回扩展名对应的类别。"""
    ext_lower = extension.lower()
    for category, extensions in CATEGORIES.items():
        if ext_lower in extensions:
            return category
    return 'Others'

def get_file_hash(filepath):
    """计算文件的MD5哈希。"""
    hasher = hashlib.md5()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(8192), b''):
            hasher.update(chunk)
    return hasher.hexdigest()

class FileOrganizer:
    def __init__(self, source_folder, dest_folder=None):
        self.source = Path(source_folder)
        self.dest = Path(dest_folder) if dest_folder else self.source
        self.stats = {
            'moved': 0,
            'skipped': 0,
            'duplicates': 0,
            'errors': 0
        }
        self.hash_dict = defaultdict(list)

    def organize_by_extension(self):
        """按扩展名整理文件。"""
        logger.info(f"开始按扩展名整理:{self.source}")

        for file in self.source.iterdir():
            if file.is_file():
                category = get_category(file.suffix)
                target_folder = self.dest / category
                target_folder.mkdir(exist_ok=True)

                target_path = target_folder / file.name

                # 处理重复文件名
                if target_path.exists():
                    target_path = self._get_unique_path(target_path)

                try:
                    shutil.move(str(file), str(target_path))
                    logger.info(f"移动:{file.name} -> {category}/")
                    self.stats['moved'] += 1
                except Exception as e:
                    logger.error(f"错误:{file.name} - {e}")
                    self.stats['errors'] += 1

        self._print_stats()

    def organize_by_date(self, date_format="%Y-%m"):
        """按修改日期整理文件。"""
        logger.info(f"开始按日期整理:{self.source}")

        for file in self.source.iterdir():
            if file.is_file():
                mtime = datetime.fromtimestamp(file.stat().st_mtime)
                date_folder = mtime.strftime(date_format)

                target_folder = self.dest / date_folder
                target_folder.mkdir(exist_ok=True)

                target_path = target_folder / file.name

                if target_path.exists():
                    target_path = self._get_unique_path(target_path)

                try:
                    shutil.move(str(file), str(target_path))
                    logger.info(f"移动:{file.name} -> {date_folder}/")
                    self.stats['moved'] += 1
                except Exception as e:
                    logger.error(f"错误:{file.name} - {e}")
                    self.stats['errors'] += 1

        self._print_stats()

    def organize_by_size(self, thresholds=None):
        """按大小整理文件。"""
        if thresholds is None:
            thresholds = {
                'Tiny (< 100KB)': 100 * 1024,
                'Small (100KB - 1MB)': 1024 * 1024,
                'Medium (1MB - 100MB)': 100 * 1024 * 1024,
                'Large (100MB - 1GB)': 1024 * 1024 * 1024,
                'Huge (> 1GB)': float('inf')
            }

        logger.info(f"开始按大小整理:{self.source}")

        for file in self.source.iterdir():
            if file.is_file():
                size = file.stat().st_size

                for category, threshold in thresholds.items():
                    if size < threshold:
                        target_folder = self.dest / category
                        break

                target_folder.mkdir(exist_ok=True)
                target_path = target_folder / file.name

                if target_path.exists():
                    target_path = self._get_unique_path(target_path)

                try:
                    shutil.move(str(file), str(target_path))
                    logger.info(f"移动:{file.name} ({size:,} bytes) -> {category}/")
                    self.stats['moved'] += 1
                except Exception as e:
                    logger.error(f"错误:{file.name} - {e}")
                    self.stats['errors'] += 1

        self._print_stats()

    def find_and_handle_duplicates(self, action='report'):
        """查找并处理重复文件。

        Args:
            action: 'report'(仅报告), 'move'(移动到单独文件夹), 'delete'(删除)
        """
        logger.info(f"开始搜索重复文件:{self.source}")

        # 计算文件哈希
        for file in self.source.rglob("*"):
            if file.is_file():
                try:
                    file_hash = get_file_hash(file)
                    self.hash_dict[file_hash].append(file)
                except Exception as e:
                    logger.error(f"哈希计算错误:{file} - {e}")

        # 处理重复文件
        duplicates_folder = self.dest / "Duplicates"

        for hash_value, files in self.hash_dict.items():
            if len(files) > 1:
                logger.info(f"发现重复:{len(files)}个文件")

                # 保留第一个文件
                original = files[0]
                for dup in files[1:]:
                    self.stats['duplicates'] += 1

                    if action == 'report':
                        logger.info(f"  重复:{dup}(原始:{original})")
                    elif action == 'move':
                        duplicates_folder.mkdir(exist_ok=True)
                        target = duplicates_folder / dup.name
                        if target.exists():
                            target = self._get_unique_path(target)
                        shutil.move(str(dup), str(target))
                        logger.info(f"  移动:{dup} -> Duplicates/")
                    elif action == 'delete':
                        dup.unlink()
                        logger.info(f"  删除:{dup}")

        self._print_stats()

    def _get_unique_path(self, path):
        """返回不重复的文件路径。"""
        counter = 1
        new_path = path
        while new_path.exists():
            new_path = path.parent / f"{path.stem}_{counter}{path.suffix}"
            counter += 1
        return new_path

    def _print_stats(self):
        """输出统计信息。"""
        logger.info("=" * 50)
        logger.info("处理结果:")
        logger.info(f"  移动的文件:{self.stats['moved']}个")
        logger.info(f"  跳过的文件:{self.stats['skipped']}个")
        logger.info(f"  重复文件:{self.stats['duplicates']}个")
        logger.info(f"  错误:{self.stats['errors']}个")
        logger.info("=" * 50)


def main():
    """主执行函数"""
    print("=" * 60)
    print("高级文件整理自动化")
    print("=" * 60)

    source = input("要整理的文件夹路径:").strip()
    if not source:
        source = str(Path.home() / "Downloads")
        print(f"使用默认路径:{source}")

    print("\n请选择整理方式:")
    print("1. 按扩展名整理")
    print("2. 按日期整理")
    print("3. 按大小整理")
    print("4. 查找重复文件")
    print("5. 执行所有整理")

    choice = input("\n选择(1-5):").strip()

    organizer = FileOrganizer(source)

    if choice == '1':
        organizer.organize_by_extension()
    elif choice == '2':
        organizer.organize_by_date()
    elif choice == '3':
        organizer.organize_by_size()
    elif choice == '4':
        action = input("重复文件处理方式(report/move/delete):").strip()
        organizer.find_and_handle_duplicates(action or 'report')
    elif choice == '5':
        organizer.find_and_handle_duplicates('move')
        organizer.organize_by_extension()
    else:
        print("无效的选择。")


if __name__ == "__main__":
    main()

10. 总结与下一篇预告

在这一篇中,我们学习了使用Python进行文件和文件夹自动化的核心技术:

  • 使用os模块进行基本文件系统操作
  • 使用pathlib进行面向对象的路径处理
  • 文件读写(文本、CSV、JSON)
  • 文件夹创建、删除、移动
  • 使用glob进行文件搜索
  • 使用shutil进行文件复制/移动
  • 批量重命名文件技术
  • 查找和处理重复文件
  • 综合文件整理自动化项目

这些知识将成为今后各种自动化项目的基础。如果能够自如地处理文件系统,就可以实现备份自动化、日志分析、数据预处理等无限可能的应用。

下一篇预告:在Python自动化大师第3篇中,我们将学习网页爬虫自动化。使用requests、BeautifulSoup、Selenium自动从网页收集数据的方法!