文章目录
- 一、前言
- 二、代码
一、前言
之前因为疫情常常不知道会不会被封在家里,又不想把电脑带过来带过去,就做了这个自动备份的脚本。
功能如下:
- 自动从指定根目录里将找到的所有指定后缀名的文件备份到一个备份文件夹里;备份文件架确保和根目录完全一致,即同步更新也同步删除
- 将备份文件夹中的文件自动和阿里云盘同步,同样完全一致,同步更新/删除;
- 原文件目录结构不会改变;
- 可以屏蔽根目录下一级中不想要的文件夹/文件,下多级的不行;
- 定时运行;
- 初版很傻,每天都会带着所有文件去访问云盘,导致调用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
最后创建定时任务即可:
如果想每天看着它运行,记得勾选“只在用户登录时运行”。