【Python】自动备份脚本

一、前言

之前因为疫情常常不知道会不会被封在家里,又不想把电脑带过来带过去,就做了这个自动备份的脚本。

功能如下:

  1. 自动从指定根目录里将找到的所有指定后缀名的文件备份到一个备份文件夹里;备份文件架确保和根目录完全一致,即同步更新也同步删除
  2. 将备份文件夹中的文件自动和阿里云盘同步,同样完全一致,同步更新/删除;
  3. 原文件目录结构不会改变;
  4. 可以屏蔽根目录下一级中不想要的文件夹/文件,下多级的不行;
  5. 定时运行;
  6. 初版很傻,每天都会带着所有文件去访问云盘,导致调用api次数太多频繁被墙,大大降低脚本运行效率,现在已改成若文件哈希值没变则直接跳过

二、代码

创建了一个AutoTransfer类,这个类在初始化的时候会自动读取配置文件里的参数,如果没有配置文件也可以后续调用函数时来传参。

调用类里的move_scripts函数可以将指定后缀名的文件备份到备份文件夹里。

调用ali_login函数登录阿里云。

调用find_id函数可以根据阿里云盘里的文件夹名查找文件夹id,这个id后面要用。

调用sync_folder函数进行同步,file_id就是上面的文件夹id,flag表示同步方式,True代表以本地为主。

#!/usr/bin/env python
# coding: utf-8


import os
import sys
import json
import fnmatch
import filecmp
from typing import List, Dict, Union, Callable, Optional
from shutil import copyfile, rmtree
from functools import reduce
from pathlib import Path

from tqdm import tqdm
from aligo import Aligo


class AutoTransfer:
    def __init__(self, config_file='transfer_config.json'):
        self.config_file = config_file
        # 自动从配置json里获取参数
        if os.path.exists(config_file):
            with open(config_file, mode='r', encoding='utf-8') as c:
                cfg = json.load(c)
            for k, v in cfg.items():
                setattr(self, k, v)

    def __setattr__(self, k, v):
        if '_' != k[0]:
            print(f"Current attribute '{k}' is '{v}'")
        self.__dict__[k] = v

    @staticmethod
    def list_dir(
            cur_path: str,
            ext_filter: Optional[List] = None
    ) -> List:
        """
        列举文件根目录下各文件的路径.
        Parameters
        ----------
        cur_path: str
            根目录.
        ext_filter: Optional[List], default None
            用作筛选的后缀名.

        Returns
        -------
        List
            文件路径列表.
        """
        file_paths = []
        for root, dirs, files in os.walk(cur_path):
            for file in files:
                file_paths.append(os.path.join(root, file))
        if ext_filter:
            file_paths = list(filter(lambda x: os.path.splitext(x)[-1] in ext_filter, file_paths))

        return file_paths

    @staticmethod
    def copy_file(
            src: str,
            dst: str,
            mode: str = 'copy'
    ) -> str:
        """
        复制目标文件到指定位置, 如果没有文件夹会自动创建.

        Parameters
        ----------
        src: str
            文件原路径.
        dst: str
            文件新路径.
        mode: str, default `copy`
            模式, 移动或复制.
        Returns
        -------
        str
            文件新路径.
        """
        output_folder, f_name = os.path.split(dst)
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)

        if mode == 'copy':
            return copyfile(src, dst)
        elif mode == 'move':
            return move(src, dst)

    def _get_org_files(
            self
    ):
        folders = filter(
            lambda x: x not in self.outer_blocks,
            os.listdir(self.org_root)
        )
        total_files = reduce(
            lambda x, y: x + y,
            [self.list_dir(os.path.join(self.org_root, f), self.exts) for f in folders]
        )
        for inn_block in self.inner_blocks:
            total_files = list(filter(lambda x: not fnmatch.fnmatch(x, f"*{inn_block}*"), total_files))

        self._total_org_files = total_files

    def _get_backup_files(
            self
    ):
        self._total_backup_files = self.list_dir(self.new_root)

    def _check_update(
            self
    ):
        need_backup = []
        for org_file in self._total_org_files:
            backup_file = org_file.replace(self.org_root, self.new_root)
            if not os.path.exists(backup_file):
                need_backup.append(org_file)
            else:
                if not filecmp.cmp(org_file, backup_file):
                    need_backup.append(org_file)
        self._need_backup = need_backup

        need_delete = []
        for backup_file in self._total_backup_files:
            org_file = backup_file.replace(self.new_root, self.org_root)
            if not os.path.exists(org_file):
                need_delete.append(backup_file)
        self._need_delete = need_delete

        return True if self._need_backup or self._need_delete else False

    def move_scripts(
            self,
            org_root='',
            new_root='',
            outer_blocks=[],
            inner_blocks=[],
            exts=[]
    ):
        # 更新参数
        if org_root:
            self.org_root = org_root
        if new_root:
            self.new_root = new_root
        if outer_blocks:
            self.outer_blocks = outer_blocks
        if inner_blocks:
            self.inner_blocks = inner_blocks
        if exts:
            self.exts = exts

        # 检查参数有无缺少
        missing_attr = [attr for attr in ['org_root', 'new_root', 'exts'] if attr not in self.__dict__.keys()]
        if missing_attr:
            raise AttributeError

        # 获取待备份文件和已备份文件
        self._get_org_files()
        self._get_backup_files()
        is_update = self._check_update()

        for backup_file in self._need_backup:
            self.copy_file(
                backup_file,
                backup_file.replace(self.org_root, 'temp_storage')
            )

        self.remember_configs()

        return is_update

    def ali_login(self):
        self.aligo = Aligo()

    def find_id(self, target_name):
        for f in self.aligo.get_file_list():
            if f.name == target_name:
                return f.file_id

    @staticmethod
    def ali_remove_file(
            aligo,
            ali_path,
            parent_file_id
    ):
        f = aligo.get_file_by_path(ali_path, parent_file_id=parent_file_id)
        if f:
            aligo.move_file_to_trash(f.file_id)
            ali_folder = aligo.get_folder_by_path(
                os.path.dirname(ali_path),
                parent_file_id=parent_file_id
            )
            file_list = aligo.get_file_list(
                parent_file_id=ali_folder.file_id
            )
            if not file_list:
                aligo.move_file_to_trash(ali_folder.file_id)

    def _to_trash(
            self
    ):
        for delete_file in self._need_delete:
            ali_path = os.path.join(*Path(self._need_delete[0]).parts[len(Path(self.new_root).parts):])
            f = self.aligo.get_file_by_path(ali_path, parent_file_id=self.file_id)
            self.ali_remove_file(
                aligo=self.aligo,
                ali_path=ali_path,
                parent_file_id=self.file_id
            )

    def _delete_file(
            self
    ):
        for delete_file in self._need_delete:
            os.remove(delete_file)
            try:
                os.removedirs(os.path.dirname(delete_file))
            except OSError:
                continue

    def _temp_to_backup(
            self
    ):
        files = self.list_dir('temp_storage')
        for file in files:
            self.copy_file(
                file,
                file.replace('temp_storage', self.new_root),
            )
        rmtree('temp_storage')

    def sync_folder(self, file_id='', flag=None):
        if file_id:
            self.file_id = file_id
        if flag:
            self.flag = flag
        self.remember_configs()
        self.aligo.sync_folder('temp_storage', self.file_id, self.flag)
        self._temp_to_backup()
        self._to_trash()
        self._delete_file()

    def remember_configs(self):
        config_dict = {
            k: v
            for k, v in self.__dict__.items()
            if k in ['org_root', 'new_root', 'outer_blocks', 'inner_blocks', 'exts', 'file_id', 'flag']
        }
        with open(self.config_file, mode='w', encoding='utf-8') as f:
            json.dump(config_dict, f)


if __name__ == '__main__':
    at = AutoTransfer()
    is_update = at.move_scripts()
    if not is_update:
        sys.exit()
    at.ali_login()
    at.sync_folder()
    print('ok')

接下来创建一个.bat文件:

@echo off

D:

cd D:\sprite\PythonProject\script_transfer

call D:\sprite\PythonProject\main_venv\work_venv\Scripts\activate.bat

python script_transfer.py

pause

最后创建定时任务即可:

在这里插入图片描述

如果想每天看着它运行,记得勾选“只在用户登录时运行”。

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sprite.Nym

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值