• Python自动化办公【自动组织文件】


    1 文件属性与文件操作

    对操作系统而言,一个文件就是一个资源,每个资源都具有相应的属性。就文件而言,文件具有创建时间、修改时间、文件大小、操作权限等不同的属性,其中操作权限对应着当前用户是否可以读写该文件。

    1.1 获取文件属性

    在 Python 中,可以通过 pathlib 库轻松获取文件的不同属性,首先获取文件大小的信息:

    from pathlib import Path
    
    # 获取文件大小 文件创建时间、修改时间
    def get_filesize(filepath):
        # 获取文件大小 单位为B
        fsize = Path(filepath).stat().st_size
        # B转MB
        fsize = fsize / float(1000 * 1000)
        return round(fsize, 4)
    
    
    fsize = get_filesize("test.txt")
    print(f"文件大小为: {fsize}MB")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    除 pathlib 库外,还可以使用 os.path.getsize 方法获取文件大小信息。
    pathlib 库会通过文件所在路径将文件构建成pathlib.Path 对象,后续对文件的操作都会通过该对象完成。相比于过去文件路径只是单纯的一个字符,pathlib 库提供的方式更加符合面向对象的编程风格。

    使用 pathlib 库获取文件创建时间与修改时间的操作同样简单:

    import time
    
    def get_time(timestamp):
        # 格式化时间戳
        t = time.localtime(timestamp)
        return time.strftime("%Y-%m-%d %H-%M-%S", t)
    
    
    filepath = "test.txt"
    
    # 文件创建时间
    ctime = Path(filepath).stat().st_ctime
    ctime = get_time(ctime)
    
    # 文件修改时间
    mtime = Path(filepath).stat().st_mtime
    mtime = get_time(mtime)
    
    print(f"创建时间: {ctime}, 修改时间: {mtime}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    时间戳是指从格林尼治时间 1970 年 01月 01日 00 时 00 分 00 秒起至现在的总秒数。

    1.2 读写文件

    在Python 中,可以通过内置的 open 方法打开一个文件,以此获取该文件的句柄,该句柄是否具有读写权限依赖于启动Python程序的用户是否有权限读写该文件。当使用 open 方法打开文件并完成操作后,需要调用 close 方法将其关闭,避免不用的文件因长期不关闭而产生的系统资源消耗。

    # 打开文件
    f = open("test.txt", "r", encoding="utf-8")
    # 读取内容
    all_content = f.read()
    print(all_content)
    # 关闭文件句柄
    f.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果读取的是一个大型文件,如十几GB大小的文件,此时不推荐使用 read 方法将文件内容一次读入,因为文件太大,计算机内存空间难以在短时间内存放文件的所有内容,此时应该通过 readlines 方法逐行读取,或者在使用 read 方法时传入要读取的字符数:

    # 逐行读取内容
    f = open("test.txt", "r", encoding="utf-8")
    for line in f.readlines():
        print(line)
        print("---")
    f.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    需要注意的是,通过只读方式打开的文件是无法添加新内容的,如果只读文件的文件句柄没有关闭,此时再通过 open 方法以只写方式打开文件就会出现问题:

    # 以只读的方式打开
    fr = open("test.txt", "r", encoding="utf-8")
    # 以只写的方式打开
    fw = open("test.txt", "w", encoding="utf-8")
    
    fw.write("新的内容")
    # 没有输出任何内容,此时是有问题的
    print(f"读取 {fr.read()}")
    
    fr.close()
    fw.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    为了避免出现问题,在每次对文件操作完后,最好调用 close 方法关闭该文件句柄,但这显得有些繁杂。我们可以通过 with 关键字来简化整个过程,同样以读取文件为例,代码如下:

    # 不用close的写法 会自动关闭
    with open("test.txt", "r", encoding="utf-8") as f:
        all_content = f.read()
        print(all_content)
    
    • 1
    • 2
    • 3
    • 4

    with 关键字使用的基本形式为 “with…as…”,通过 with 关键字来管理文件的打开,在使用完成后, with 关键字会帮助用户自动关闭当前打开的文件。

    不是所有方法都可以使用 with 关键字来管理上下文的,只有上下文管理器对象才可以使用 with 关键字进行管理。任何实现了 _enter_方法与_exit_方法的对象都可以称为上下文管理器对象,open 方法返回的文件对象就是一个上下文管理器对象。

    # 任何实现了 __enter__ 和 __exit__ 方法的对象 都可以称为上下文管理器对象
    # 自定义一个
    class myopen():
        # 模拟with关键字使用open的方法
        def __init__(self, filepath, mode, encode):
            self.filepath = filepath
            self.mode = mode
            self.encode = encode
    
        def __enter__(self):
            print("打开文件")
            self.f = open(self.filepath, self.mode, encoding=self.encode)
            return self.f
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("关闭文件")
            self.f.close()
    
    
    with myopen("test.txt", "r", "utf-8") as f:
        res = f.read()
        print(res)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在很多时候,我们需要处理具有特定格式的文件,需要以二进制形式将文件内容读入,然后根据该文件的格式规则进行解析。以二进制形式读写文件的代码如下:

    # 写入二进制
    with open("test2.txt", "wb") as f:
        f.write("写入的是二进制".encode("utf-8"))
    with open("test2.txt", "rb") as f:
        res = f.read()
        print(res.decode("utf-8"))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此外,通过 wb 模式或 rb 模式打开文件时,如果文件已经存在,则旧内容会被完全覆盖。有时旧内容仍具有重要的价值,因此为了避免旧内容被无意覆盖,可以使用 x 模式,代码如下:

    # 判断是否存在
    # 存在则会抛出FileExistsError: [Errno 17] File exists的异常
    # 不存在则新建
    with open("test2.txt", "x") as f:
        f.write("写入的是二进制2")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.3 重命名文件

    对文件进行重命名是常见的需求,通过 pathlib 库的 Path。rename 方法可以轻松实现对某文件的重命名操作:

    from pathlib import Path
    
    # 重命名文件
    txtpath = Path('/Users/xxx/Desktop/t/1.txt')
    # 将 1.txt 重命名为 2.txt
    txtpath.rename(txtpath.parent.joinpath('2.txt'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.4 删除文件

    在编写程序时,为了避免不需要的文件对代码逻辑产生影响,通常可以将其删除。pathlib 库提供了 unlink 方法与 rmdir 方法来删除文件,但两个方式的使用场景有所不同,示例代码如下:

    from pathlib import Path
    txtpath = Path('/Users/xxx/Desktop/t/1.txt')
    
    # 删除文件时使用
    txtpath.unlink()
    emptydirpath = Path('/Users/xxx/Desktop/t/test')
    # 删除空目录时使用
    emptydirpath.rmdir()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Path.unlink 方法等价于 os.remove 方法,用于删除已存在的文件; Path.rmdir 方法等价与 os.rmdir 方法,用于删除空的目录,如果目录非空,该方法会抛出异常。

    在很多时候,为了避免误删文件,在删除文件时都不会将文件真正删除,而是制造出放入垃圾桶的效果,放入垃圾桶的文件经过一段时间后再由其他脚本将其真正删除,这样我们在误删文件后,就可以从垃圾桶中将文件恢复。

    我们可以创建一个新的文件夹“垃圾桶”,在删除时,使用 Path.rename 方法将要删除的文件移动到垃圾桶文件夹中:

    from pathlib import Path
    txtpath = Path('/Users/xxx/Desktop/t/1.txt')
    
    # 垃圾桶文件夹路径
    trashpath = Path('/Users/xxx/Desktop/t/trash/')
    # 将文件移动到垃圾桶,完成删除
    txtpath.rename(trashpath.joinpath(txtpath.name))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.5 监控文件变化

    有时需要对某个目录中的所有文件进行监控,当目录本身或目录中的文件发生改变时,程序需要做出相应的操作,例如,程序依赖于某些配置文件,当配置文件改变时,在不重启程序的情况下载入文件中的新内容。

    在 Python 中,通过 watchdog 第三方库可以轻松实现监控目录的功能。首先通过 pip3 安装 watchdog 库,命令如下:

    pip3 install watchdog
    
    • 1

    watchdog 库的使用可以简单分为以下4步。
    (1)定义被监控文件在变动后要执行的方法。代码如下:

    def on_created(event):
        print(f"创建了{event.src_path}")
        
    def on_deleted(event):
        print(f"注意,{event.src_path}被删除了")
        
    def on_modified(event):
        print(f"文件从{event.src_path}移动到了{event.dest_path}")
        
    def on_moved(event):
        print(f"{event.src_path}被修改")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)创建事件处理器。当被监听文件发生变动时,事件处理器会被调用,而事件处理器会根据文件变动的类型调用对应的方法,具体代码如下:

    # 事件处理器
    from watchdog.events import PatternMatchingEventHandler
    
    # 要处理的文件的匹配规则 *代表所有文件
    pattern = "*"
    # 不需要处理文件的匹配规则
    ignore_patterns = ""
    # 是否只监听常规文件 不包含文件夹 False表示文件夹也要监听
    ignore_directories = False
    # 设置为True 表示区分路径大小写
    case_sensitive = True
    # 创建事件处理器
    event_handler = PatternMatchingEventHandler(
        pattern,
        ignore_patterns,
        ignore_directories,
        case_sensitive
    )
    # 绑定对应的方法
    event_handler.on_created = on_created
    event_handler.on_deleted = on_deleted
    event_handler.on_modified = on_modified
    event_handler.on_moved = on_moved
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    实例化事件处理器后,将第一步定义的方法与事件处理器绑定,当事件处理器被触发时,这些方法就会被调用。

    (3)创建观察者对象,该对象会监听对应的目录,当目录发送变动时,会触发相应的事件处理器,代码如下:

    # 导观察者
    from watchdog.observers import Observer
    
    # 要监听的路径
    path = "test"
    # 是否要监听当前目录的子目录文件发生的变化 True表示子目录的变化也监听
    recursive = True
    # 创建观察者
    observer = Observer()
    # event_handler 事件处理器
    observer.schedule(event_handler, path, recursive=recursive)
    # 启动事件处理器
    observer.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (4)监听键盘中断指令,当按“Ctrl + C” 或 “Command + C” 组合键时,停止整个程序,代码如下:

    try:
        while True:
            time.sleep(1)
    # 按 ctrl + c 组合键退出程序
    except KeyboardInterrupt:
        # 停止观察者
        observer.stop()
        observer.join()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    watchdog 库在运行的过程中,除非认为中断,否则 程序会一直执行。为了避免watchdog 库影响程序的正常逻辑,通常会开启一个新的线程来运行 watchdog 库相关的逻辑。

    2 文件路径

    2.1 不同操作系统间路径的差异

    Windows 操作系统与 UNIX 系统间文件路径的规则是存在差异的。在 Windows 操作系统中,文件与文件的间隔用反斜杠 “\” 连接;而在 MacOS 或 Linux 这类操作系统中,文件与文件间的间隔用正斜杠 “/” 连接。

    # Windows 路径使用反斜杠
    C:\Users\xxx
    # macOS 路径使用正斜杠
    /Users/xxx
    
    • 1
    • 2
    • 3
    • 4

    因为不同操作系统路径存在差异,所以在编写程序时,路径相关的内容就不能使用硬编码的形式,而应该使用 Python 内置的 pathlib 库来实现路径的拼接,代码如下:

    from pathlib import Path
    
    # 硬编码是错误的写法,不同系统间的路径差异导致这段代码不可使用
    path = '/Users/ayuliao/Desktop/'
    print(f'硬编码的路径:{path}')
    
    path2 = Path().joinpath('Users','xxx','Desktop')
    print(f'软编码的路径:{path2}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.2 绝对路径与相对路径

    通过 pathlib 库可以判断当前路径是否为绝对路径,代码如下:

    from pathlib import Path
    
    txtpath = Path('/Users/xxx/Desktop/t/1.txt')
    # 判断是否为绝对路径
    txtpath.is_absolute()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3 创建文件夹

    Python 中有多种创建文件夹的方式,这里介绍常见的两种方式。

    首先,可以通过 os 库的 makedirs 方法创建新的文件夹,如果待创建文件夹的父文件夹不存在,那么父文件夹也会被同时创建,代码如下:

    import  os
    os.makedirs('d1/d2/d3')
    
    • 1
    • 2

    此外,还可以通过pathlib 库达到类似的效果:

    from pathlib import Path
    Path('d4/d5/d6').mkdir(parents=True)
    
    • 1
    • 2

    2.4 与文件路径相关的常用操作

    (1)通过 Path.exists 方法判断文件是否存在:

    from pathlib import Path
    
    # 判断文件是否存在
    txtpath = Path("1.txt")
    print(txtpath.exists())
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)获取当前程序运行时所在的目录,再通过相对路径的方式构建出需要的路径。通过这种方式获取的路径具有很好的平台兼容性:

    # 获取当前程序的工作目录
    nowpath = Path.cwd()
    print(nowpath)
    
    # 相对于当前工作目录
    codepath = nowpath.joinpath("..", "code")
    print(codepath)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (3)通过 Path.is_dir 方法判断文件路径对应的是否为文件夹,与之类似,可以通过 Path.is_file 方法判断文件路径对应的是否为文件:

    from pathlib import Path
    
    # 判断是否为文件夹
    txtpath = Path("1.txt")
    # 判断是否为文件夹 True表示是文件夹
    print(txtpath.is_dir())
    print("================================")
    
    # 判断是否为文件 True表示是文件
    txtpath.is_file()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (4)通过 Path.samefile 方法判断两个文件路径对应的文件是否相同,该方法要求对比的两个文件路径所对应的文件是否存在:

    from pathlib import Path
    
    # 判断路径是否相同
    txtpath = Path("1.txt")
    txt2path = Path("1.txt")
    txtpath.samefile(txt2path)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (5)通过 Path 对象对应的属性获取文件名、文件扩展名、文件所在的目录等信息:

    from pathlib import Path
    
    # 获取文件名、文件扩展名、文件所在目录等信息
    print(txtpath.name)
    print(txtpath.suffix)
    print(txtpath.parent)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (6)处理文件夹中的所有文件,包括文件夹中的子文件夹包含的文件:

    from pathlib import Path
    
    # 寻找扩展名为py的文件个数
    def findallpyfile(dir):
        # 以递归形式遍历dir文件夹 找寻满足*.py条件的文件
        for p in dir.rglob("*.py"):
            # 深度
            depth = len(p.relative_to(dir).parts)
            print(p.name, depth)
    
    print("================================")
    findallpyfile(Path.cwd())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (7)通过 iterdir 方法获取当前文件夹下的所有文件,但该方法无法以同样的方式处理当前目录下子文件夹中的文件。通过 iterdir 方法可以轻松计算出当前目录中不同类型文件的个数:

    from pathlib import Path
    from collections import Counter
    
    # 计算出不同类型的文件的个数
    gen = []
    # 遍历当前文件夹中的文件
    for i in Path.cwd().iterdir():
        # 将文件类型添加到list中
        gen.append(i.suffix)
    print(Counter(gen))
    print("================================")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3 压缩文件操作

    压缩软件压缩文件的基本原理就是查找文件中重复或符合某种规律的二进制数据,然后通过更少的二进制数据去表示它,这类似于构建了一个字典,使用一段简短的数据表示一段符合某种规律的数据,从而达到压缩文件大小的效果。

    3.1 压缩文件

    通过 zipfile 库与 tarfile 库实现对某个文件夹的压缩操作。
    首先实现 .zip 格式的压缩文件:

    import zipfile
    from pathlib import Path
    
    # 压缩文件
    path = "test"
    newzip = zipfile.ZipFile("new.zip", "w")
    
    # 压缩path目录下的所有文件
    for p in Path(path).rglob("*"):
        newzip.write(p, compress_type=zipfile.ZIP_DEFLATED)
    newzip.close()
    print("done!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    zipfile 类支持只读(r)、只写(w)、独占写入(x)、追加写入(a)这4中模式。
    .tar.gz 格式的压缩文件可以通过类似的形式构建:

    import zipfile
    from pathlib import Path
    
    path = "test"
    # tar.gz格式 w:gz表示采用gzip算法压缩
    tar = tarfile.open("new.tar.gz", "w:gz")
    for p in Path(path).rglob("*"):
        tar.add(p)
    tar.close()
    print("done!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2 解压缩文件

    通过 zipfile 库对 .zip 格式的文件进行解压:

    import zipfile
    from pathlib import Path
    
    # 解压缩文件
    target_path = Path.cwd()
    zippath = Path("new.zip")
    # 创建ZipFile对象
    zip_file = zipfile.ZipFile(zippath)
    # 解压
    zip_file.extractall(target_path)
    zip_file.close()
    print("done!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    只解压部分内容,此时可以调用 extract 方法对个别文件进行解压:

    # 解压的目标路径
    target_path = Path.cwd()
    zippath = Path('test.zip')
    # 创建 ZipFile 对象
    zip_file = zipfile.ZipFile(zippath)
    # 需要被解压的文件
    need_unzip = ['1.txt','2.txt']
    # 返回 zip 文件中包含的所有文件和文件夹列表
    names = zip_file.namelist()
    print(names)
    for fn in names:
        # 判断需要解压的文件是否包含在 fn 中
        files = [f for f in need_unzip if f in fn]
        if files:
            print(fn)
            # 解压某个文件
            zip_file.extract(fn,target_path)
    zip_file.close()
    print('done!')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    .tar.gz 格式的压缩文件也可以通过类似的形式进行解压:

    import tarfile
    
    # 解压缩tar.gz
    path = "new.tar.gz"
    target_path = "."
    
    tar = tarfile.open(path, "r:gz")
    # 获取压缩文件中所有文件的名称
    file_names = tar.getnames()
    print(f"tar zip files name: {file_names}")
    
    # 解压
    tar.extractall(target_path)
    print("done!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.3 破解加密压缩文件

    破解并不是万能的,为了提高破解成功的概率,通常需要收集相关的信息,通过这些信息整理出可能的密码组合,如网站域名、人名、手机号、生日等不同信息的组合。

    破解加密压缩文件的代码如下:

    import zipfile
    
    zippath = 'test.zip'
    pwd_path = 'passwd.txt'
    targetpath = '.'
    zip = zipfile.ZipFile(zippath)
    
    def unzip(pwd):
        try:
            # 解压,pwd 为解压时使用的密码
            zip.extractall(path=targetpath,pwd=pwd.encode('utf-8'))
            print(f'密码为:{pwd}')
            return True
        except:
            return  False
    with open(pwd_path,'r') as f:
        for pwd in f.readlines():
            # 除去字符串两端空格
            pwd = pwd.strip()
            if unzip(pwd):
                break # 找到密码退出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    Shiro学习2----spring boot整合(JdbcRealm)
    Selenium实现原理
    网络安全(黑客)自学
    buuctf web 前5题
    使用Apache Doris自动同步整个 MySQL/Oracle 数据库进行数据分析
    Java 零基础教程,看完就会,建议收藏
    VUE UI组件ui-libs.vercel.app 花了太长时间进行响应怎么解决?
    数字化转型这么火热,企业选择数字化转型都有哪些目的
    Mysql——》索引存储模型推演
    计算机图形学 - surface,layer,pipeline
  • 原文地址:https://blog.csdn.net/qq_42588990/article/details/126362549