Как автоматизировать удаление ненужных файлов с помощью Python
Определение задач для автоматизации
Сначала требовалось определить задачи для автоматизации. По итогам вдумчивого анализа получился следующий перечень.
- Удаление повторяющихся файлов, например файлов с похожими именами (
img.png,img(1).png,img(2).png), и сохранение самой последней версии с одновременным удалением всех остальных. - Удаление временных файлов, в которых уже нет необходимости. К ним относятся: логи, скриншоты, кеш и установочные файлы.
- Систематизация файлов в папке загрузок
Downloadsпутем упорядочивания их по типу и перемещение файлов в специально обозначенные папки. - Удаление старых/неиспользуемых приложений, занимающих необходимое дисковое пространство.
Сформулированные задачи помогли понять, какие скрипты создавать и как оптимизировать их под свои конкретные потребности. С учетом всей подготовленной информации можно было приступать к процессу автоматизации.
СКРИПТ РАЗБИТ НА ОТДЕЛЬНЫЕ ФРАГМЕНТЫ КОДА ДЛЯ КАЖДОЙ ЗАДАЧИ, ЧТОБЫ ОБЛЕГЧИТЬ НАСТРОЙКУ ПРОЦЕССА АВТОМАТИЗАЦИИ ПОД СВОИ НУЖДЫ.
Удаление повторяющихся файлов

Довольно часто я обнаруживаю скопления нескольких версий одного и того же файла (разных размеров), поскольку скачиваю/сохраняю файлы из интернета или приложения с одинаковым именем!
Предлагаемый скрипт удаляет повторяющиеся файлы, сохраняет только самую последнюю версию и называет ее исходным именем, избавляя от всяких забот по отслеживанию разных имен:
import os
import re
def remove_duplicates(directory):
for root, dirs, files in os.walk(directory):
filedict = {}
for filename in files:
filepath = os.path.join(root, filename)
# Проверяет, повторяется ли файл
match = re.search(r'^(.*?)\((\d+)\)(\.[^.]*)?$', filename)
if match:
name = match.group(1)
number = int(match.group(2))
ext = match.group(3) or ''
# Добавление файла в словарь с именем в качестве ключа и числом в качестве значения
if name not in filedict:
filedict[name] = [(number, filepath, ext)]
else:
filedict[name].append((number, filepath, ext))
# Переименование файлов в каждой группе
for name in filedict:
files = filedict[name]
files.sort(key=lambda x: x[0])
print(files)
latest = files[-1][1]
ext = files[-1][2]
for number, filepath, _ in files[:-1]:
print(f"Deleted duplicate file: {filepath}")
os.remove(filepath)
os.rename(latest, os.path.join(root, f"{name}{ext}"))
print(f"Renamed {latest} to {name}{ext}")
remove_duplicates(os.path.expanduser("~/Downloads"))
ЕСЛИ ВЫ ИЩИТЕ СПОСОБ УДАЛЕНИЯ ФАЙЛОВ ОДИНАКОВОГО РАЗМЕРА ИЛИ СОДЕРЖАНИЯ, ТО ПЕРЕХОДИТЕ ЗА ИНТЕРЕСУЮЩЕЙ ИНФОРМАЦИЕЙ ПО УКАЗАННОЙ ССЫЛКЕ.
Удаление временных файлов
Этот скрипт ищет временные файлы в таких директориях компьютера, как /private/var/folders, ~/Library/Caches, ~/Library/Logs, ~/Downloads и ~/Desktop, и удаляет те из них, что изрядно там подзадержались.
Скрипт знает, что делать: он содержит список расширений файлов extensions, которые находит и удаляет. Точно также он поступает и со скриншотами, накапливающимися в директории ~/Desktop:
import os
import shutil
# Список директорий для поиска временных файлов
temp_directories = [
'/private/var/folders',
'~/Library/Caches',
'~/Library/Logs',
'~/Downloads',
'~/Desktop'
]
# Список расширений файлов, подлежащих удалению
extensions = [
'.log',
'.cache',
'.tmp',
'.dmg',
'.pkg'
]
for directory in temp_directories:
for dirpath, dirnames, filenames in os.walk(os.path.expanduser(directory)):
for filename in filenames:
# Проверяет, значится ли расширение файла в списке тех, что подлежат удалению
if os.path.splitext(filename)[1].lower() in extensions:
filepath = os.path.join(dirpath, filename)
try:
if os.path.isfile(filepath):
os.remove(filepath)
elif os.path.isdir(filepath):
shutil.rmtree(filepath)
print(f"Removed {filepath}")
except Exception as e:
print(f"Error deleting {filepath}: {e}")
# Удаление скриншотов из директории Desktop
if directory == '~/Desktop':
desktop_path = os.path.expanduser(directory) # expand ~ to home directory
screenshot_files = [f for f in os.listdir(desktop_path) if f.startswith('Screenshot')]
for file in screenshot_files:
filepath = os.path.join(desktop_path, file)
try:
os.remove(filepath)
print(f"Removed {filepath}")
except Exception as e:
print(f"Error deleting {filepath}: {e}")
Систематизация и удаление файлов в папке Downloads
Содержимое моей папки Downloads напоминает ситуацию с пропавшими носками в сушилке — статус “утеряны безвозвратно”. Для поддержания порядка я систематизировала файлы по отдельным папкам в зависимости от их типа: изображения, видео, документы, аудио, код и даже файлы Photoshop:
Downloads/ ├─ Image/ ├─ Video/ │ ├─ Subtitle/ ├─ Code/ ├─ Document/ ├─ Audio/ ├─ Photoshop/
Созданный код также учитывает мою тягу к порядку и делает за меня всю грязную работу. Он распаковывает файлы .zip, удаляет файлы .torrent и даже перемещает файлы .srt в соответствующую папку. А вот и сам скрипт:
import os
import shutil
import zipfile
downloads_dir = os.path.expanduser("~/Downloads")
# Создание словаря типов файлов и соответствующих им папок
file_types = {
"Image": [".jpg", ".jpeg", ".png", ".gif", ".tiff", ".bmp", ".eps"],
"Code": [".ipynb",".py", ".js", ".html", ".css", ".php", ".cpp", ".h", ".java"],
"Document": [".pdf", ".doc", ".docx", ".txt", ".rtf", ".xls", ".xlsx", ".ppt", ".pptx"],
"Audio": [".mp3", ".wav", ".aac", ".ogg"],
"Video": [".mp4", ".avi", ".mov", ".flv", ".wmv", ".mpeg"],
"Photoshop": [".psd"],
}
# Создание папок для каждого типа файла
for folder_name in file_types.keys():
folder_path = os.path.join(downloads_dir, folder_name)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
# Итерация по файлам в папке Downloads
for filename in os.listdir(downloads_dir):
filepath = os.path.join(downloads_dir, filename)
# Распаковка и удаление файлов .zip
if filename.endswith(".zip"):
# Проверяет, распакован ли файл
unzip_dir = os.path.join(downloads_dir, filename[:-4])
if not os.path.exists(unzip_dir):
# Распаковка файла .zip
with zipfile.ZipFile(filepath, 'r') as zip_ref:
zip_ref.extractall(downloads_dir)
# Удаление файла .zip
os.remove(filepath)
print(f"Unzipped and removed {filename}")
# Удаление файлов .torrent
elif filename.endswith(".torrent"):
os.remove(filepath)
print(f"Removed {filename}")
# Перемещение файлов .srt в папку .subtitle внутри папки Video
elif filename.endswith(".srt"):
dest_folder = os.path.join(downloads_dir, "Video", ".subtitle")
if not os.path.exists(dest_folder):
os.makedirs(dest_folder)
shutil.move(filepath, os.path.join(dest_folder, filename))
print(f"Moved {filename} to {dest_folder}")
# Перемещение файла в соответствующую папку в зависимости от его расширения
else:
for folder_name, extensions in file_types.items():
if any(filename.endswith(ext) for ext in extensions):
dest_folder = os.path.join(downloads_dir, folder_name)
shutil.move(filepath, os.path.join(dest_folder, filename))
print(f"Moved {filename} to {dest_folder}")
break
Удаление старых/неиспользуемых приложений
У меня есть плохая привычка устанавливать несколько приложений, которыми практически не пользуюсь. Поэтому нужно было разработать механизм для обнаружения неиспользуемых приложений и периодического оповещения о них. С помощью следующего скрипта я легко могу определить, какие приложения не работают долгое время, и удалить их одним щелчком мыши:
import os
import subprocess
import time
# Определение количества дней бездействия приложения перед запросом на его удаление
inactivity_period = 90 # 90 дней
# Получение списка установленных приложений
apps_dir = "/Applications"
app_names = os.listdir(apps_dir)
# Проверка времени последнего доступа для каждого бинарного файла приложения и запрос на его удаление при условии 90-дневного неактивного периода
for app_name in app_names:
app_path = os.path.join(apps_dir, app_name)
if os.path.isdir(app_path):
app_binary = os.path.join(app_path, "Contents/MacOS", app_name[:-4])
if os.path.exists(app_binary):
last_access_time = os.stat(app_binary).st_atime
days_since_access = (time.time() - last_access_time) // (24 * 60 * 60)
if int(days_since_access) > inactivity_period:
alert_message = f"Are you still using {app_name}? Do you want to remove it?"
button_pressed = subprocess.run(['osascript', '-e', f'tell app "System Events" to display alert "{alert_message}" buttons {{"Keep", "Remove"}} default button "Remove"', '-e', 'button returned of result'], stdout=subprocess.PIPE, encoding='utf-8').stdout.strip()
if button_pressed == "Remove":
print(f"Removed {app_name}")
subprocess.run(['sudo', 'rm', '-rf', app_path])
else:
print(f"Kept {app_name}")
with open('app_decisions.txt', 'a') as file:
file.write(f"{app_name},Keep,{time.time()}\n")
ПРИМЕЧАНИЕ. ДАННЫЙ СКРИПТ СЛЕДУЕТ ЗАДЕЙСТВОВАТЬ ТОЛЬКО ПРИ УСЛОВИИ ПОЛНОГО ПОНИМАНИЯ ЕГО ФУНКЦИОНАЛЬНЫХ ВОЗМОЖНОСТЕЙ И ПОТЕНЦИАЛЬНЫХ РИСКОВ. ОН РАЗРАБОТАН ДЛЯ УДАЛЕНИЯ СТАРЫХ И НЕИСПОЛЬЗУЕМЫХ ПРИЛОЖЕНИЙ, НО МОЖЕТ ПРИВЕСТИ К СЛУЧАЙНОМУ УДАЛЕНИЮ ВАЖНЫХ ПРИЛОЖЕНИЙ И ДАННЫХ.
Планирование периодического выполнения скриптов
Следующий этап после подготовки скриптов заключался в планировании их периодического выполнения. Это можно было сделать разными способами. Я же воспользовалась планировщиком cron для планирования запусков скриптов в системе Mac. Эта полезная утилита позволяет планировать выполнение задач через определенные промежутки времени.
Я открыла файл crontab и добавила следующие записи:
0 0 * * * /path/to/remove_dupes.py 0 0 * * * /path/to/remove_temps.py 0 5,17 * * * /path/to/download_cleanup.py 0 0 1 * * /path/to/remove_apps.py
Данные записи выполняют скрипты remove_dupes.py и remove_temps.py один раз в день. Скрипт download_cleanup.py — дважды в день в 5:00 и 17:00, а скрипт remove_apps.py — один раз в месяц в первый его день.
Воспользовавшись сайтом crontab guru, вы сможете планировать периодичность выполнения задач в соответствии со своими потребностями.
Перед планированием автоматического выполнения скриптов еще раз проверьте их и убедитесь, что они отвечают вашим целям. Каждый раз при автоматизации процессов будьте предельно внимательны, чтобы случайно не удалить что-то важное!
Перевод статьи Gargee Suresh: How I Use Python To Clear Junk on My Laptop