Программирование

Как написать Python-скрипт для резервного копирования

Как написать Python-скрипт для резервного копирования

Резервное копирование — это не «на всякий случай», а базовая защита от потери данных. Когда я только начинал администрировать тестовый сервер в Бугульме, первым делом после настройки SSH-доступа написал простой скрипт для копирования конфигов. Потому что потерять неделю правок из-за случайного rm -rf — это больно. На практике удобнее всего начинать с Python-скрипта, который копирует нужные папки в отдельный каталог, добавляет метку времени и, при необходимости, упаковывает копию в архив. Такой подход хорошо подходит для домашнего сервера, учебного Linux-хоста, рабочего проекта или небольшого сайта.

Что должен уметь хороший backup-скрипт

Перед написанием кода важно понять задачу. Скрипт резервного копирования не обязан быть сложным, но он должен делать несколько вещей стабильно:

  • копировать исходные файлы и папки;
  • не затирать старые копии;
  • сохранять структуру каталогов;
  • создавать понятное имя бэкапа;
  • писать лог выполнения;
  • корректно реагировать на ошибки;
  • быть удобным для запуска по расписанию.

Если нужен минимальный рабочий вариант, достаточно копирования папки в новый каталог. Если задача ближе к реальной эксплуатации, лучше сразу добавить архивирование, timestamp в имени и исключения для временных файлов. В своей практике я всегда закладываю логирование с первого же прототипа — потом это экономит часы отладки, когда что-то идёт не так в три часа ночи.

Какие библиотеки Python использовать

Для резервного копирования обычно хватает стандартной библиотеки Python. Это плюс: не нужно ставить лишние зависимости, и скрипт можно запустить на любой машине, где есть интерпретатор.

Модуль Для чего нужен Где полезен
os Работа с путями, файлами и каталогами Проверка существования папок, обход директорий
shutil Копирование, архивирование, удаление copy2, copytree, make_archive
datetime Получение текущей даты и времени Метка времени в имени бэкапа
pathlib Более удобная работа с путями Читабельный современный код
logging Запись журнала работы Отладка и контроль выполнения
zipfile Создание ZIP-архивов Когда нужен именно архив, а не папка-копия

Для большинства задач достаточно shutil и datetime. Если нужен аккуратный и расширяемый код, лучше использовать pathlib — он делает работу с путями более явной и меньше шансов ошибиться при склеивании строк. В своих скриптах я почти всегда начинаю с from pathlib import Path.

Простой вариант: копируем папку целиком

Ниже базовый пример. Он копирует содержимое папки source в папку backup, добавляя в имя текущую дату и время.

import shutil
from datetime import datetime
from pathlib import Path

SOURCE_DIR = Path("/home/user/myproject")
BACKUP_ROOT = Path("/home/user/backups")

timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
backup_name = f"backup_{timestamp}"
backup_path = BACKUP_ROOT / backup_name

shutil.copytree(SOURCE_DIR, backup_path)
print(f"Резервная копия создана: {backup_path}")

Что здесь происходит

  • SOURCE_DIR — папка, которую нужно сохранить.
  • BACKUP_ROOT — каталог, куда складываются копии.
  • datetime.now().strftime(...) — создает метку времени.
  • shutil.copytree(...) — копирует папку целиком.

Это хороший старт, но у варианта есть ограничения. Он не умеет обновлять уже существующую копию и не подходит, если вы хотите хранить бэкап в виде одного файла-архива. Кроме того, copytree требует, чтобы целевая папка не существовала — иначе получите исключение. В реальном скрипте я бы сначала проверил, нет ли уже такого каталога, и при необходимости добавил суффикс или удалил старый.

Когда лучше делать архив, а не папку

Архив удобнее в нескольких случаях:

  • нужно отправить копию на другой сервер;
  • важно экономить место;
  • копию надо хранить как единый файл;
  • планируется выгрузка в облако или на внешний диск;
  • бэкап нужно легко переносить.

Для этого подойдет shutil.make_archive() или работа через zipfile.

Пример создания ZIP-архива

import shutil
from datetime import datetime
from pathlib import Path

SOURCE_DIR = Path("/home/user/myproject")
BACKUP_ROOT = Path("/home/user/backups")
BACKUP_ROOT.mkdir(parents=True, exist_ok=True)

timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
archive_name = f"backup_{timestamp}"
archive_path = BACKUP_ROOT / archive_name

shutil.make_archive(str(archive_path), 'zip', SOURCE_DIR)
print(f"Архив создан: {archive_path}.zip")

Плюсы этого варианта

  • один файл вместо дерева каталогов;
  • проще передавать и хранить;
  • удобно делать ежедневные копии;
  • меньше шанс случайно изменить бэкап вручную.

На практике я часто использую именно архив, особенно когда нужно перебросить бэкап по scp на другой хост. Один файл передаётся быстрее, чем тысячи мелких, да и контрольная сумма считается проще.

Как добавить исключения

На практике нельзя копировать вообще всё подряд. Обычно стоит исключать:

  • временные файлы;
  • кэш;
  • логи;
  • виртуальные окружения;
  • node_modules;
  • крупные каталоги, которые можно восстановить отдельно.

В стандартной библиотеке нет универсального «умного» фильтра для copytree, но исключения можно реализовать вручную. Например, через обход дерева файлов и фильтрацию по маскам.

Идея для исключений

import shutil
from pathlib import Path

EXCLUDE_PATTERNS = {'.git', '__pycache__', 'node_modules', '*.log', '*.tmp'}

def ignore_func(directory, contents):
    ignored = []
    for item in contents:
        full_path = Path(directory) / item
        if full_path.name in EXCLUDE_PATTERNS or full_path.suffix in {'.log', '.tmp'}:
            ignored.append(item)
    return ignored

shutil.copytree(SOURCE_DIR, backup_path, ignore=ignore_func)

Такой подход даёт гибкость: можно добавлять любые условия, например, исключать файлы больше определённого размера или старше N дней. В боевых скриптах я часто выношу список исключений в отдельный конфигурационный файл, чтобы не лезть в код каждый раз.

Почему copy2 лучше, чем copy

shutil.copy() копирует только содержимое и базовые права доступа.
shutil.copy2() дополнительно старается сохранить метаданные: время модификации и другие атрибуты, если это возможно.

Для резервного копирования copy2 обычно предпочтительнее, потому что:

  • копия ближе к оригиналу;
  • проще сравнивать изменения;
  • меньше сюрпризов при восстановлении.

Когда я восстанавливаю файлы из бэкапа, мне важно видеть оригинальные даты правок — это помогает понять, что это за версия. Поэтому в своих скриптах я всегда использую copy2.

Как сделать скрипт полезным в реальной жизни

Если скрипт нужен не только для тестов, добавьте несколько обязательных вещей.

1. Проверка путей

Перед запуском убедитесь, что исходная папка существует, а каталог для бэкапов доступен на запись. Простейшая проверка:

if not SOURCE_DIR.exists():
    raise FileNotFoundError(f"Исходная папка не найдена: {SOURCE_DIR}")
BACKUP_ROOT.mkdir(parents=True, exist_ok=True)
if not os.access(BACKUP_ROOT, os.W_OK):
    raise PermissionError(f"Нет прав на запись в {BACKUP_ROOT}")

2. Логирование

Без логов сложно понять, что именно сломалось. Даже простой журнал в файл уже полезен.

import logging

logging.basicConfig(
    filename='/var/log/backup.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

logging.info("Запуск резервного копирования")
try:
    shutil.copytree(SOURCE_DIR, backup_path)
    logging.info(f"Успешно: {backup_path}")
except Exception as e:
    logging.error(f"Ошибка: {e}")

3. Обработка ошибок

Если место на диске закончилось или файл заблокирован, скрипт не должен падать молча.

try:
    shutil.make_archive(str(archive_path), 'zip', SOURCE_DIR)
except FileNotFoundError as e:
    logging.error(f"Файл не найден: {e}")
except PermissionError as e:
    logging.error(f"Нет доступа: {e}")
except Exception as e:
    logging.error(f"Неожиданная ошибка: {e}")

4. Проверка свободного места

Это особенно важно для больших каталогов. Иначе скрипт начнет копирование и оборвется на середине. Можно использовать shutil.disk_usage и сравнить с ожидаемым размером исходной папки.

5. Понятная структура папок

Хорошая практика — хранить бэкапы в отдельной иерархии:

  • backups/project-name/2026-10-05_02-00-00.zip
  • backups/server-config/2026-10-05_03-00-00/

Так проще искать нужную копию и удалять старые версии. Я обычно добавляю ещё подпапку с именем хоста, если скрипт бегает на нескольких машинах и складывает бэкапы в общее хранилище.

Пошаговый алгоритм написания backup-скрипта

  1. Определите, что именно нужно копировать.
  2. Выберите формат хранения: папка или архив.
  3. Добавьте метку времени в имя бэкапа.
  4. Проверьте наличие исходной папки.
  5. Создайте каталог назначения.
  6. Скопируйте данные с сохранением структуры.
  7. Запишите результат в лог.
  8. Добавьте обработку ошибок.
  9. Протестируйте восстановление.
  10. Настройте запуск по расписанию.

Этот порядок я рекомендую новичкам на своих занятиях. Он помогает не упустить критически важные моменты и сразу получить надёжный инструмент, а не «поделку на коленке».

Типовые ошибки новичков

Ошибка Почему это плохо Как сделать правильно
Бэкап в ту же папку, где лежат исходные файлы Можно случайно копировать копию и раздувать каталог Выносить резервные копии отдельно
Перезапись одного и того же файла Старые копии теряются Добавлять timestamp
Копирование всего подряд Лишний мусор и большой размер Исключать временные и служебные каталоги
Отсутствие логов Непонятно, что произошло при сбое Писать журнал выполнения
Нет проверки восстановления Бэкап есть, но пользоваться им нельзя Периодически делать тестовый restore

Отдельно отмечу: многие новички думают, что если скрипт не выдал ошибок, то всё хорошо. Но молчание — не признак успеха. Всегда проверяйте целостность копии, хотя бы сравнивая количество файлов и общий размер.

Как проверить, что бэкап действительно работает

Самая частая ошибка — считать успешным любой скрипт, который «ничего не выдал в консоль». Проверять нужно иначе.

Минимальный тест

  • создайте тестовую папку с несколькими файлами;
  • запустите скрипт;
  • сравните размер и структуру исходной и резервной копии;
  • откройте один-два файла из бэкапа;
  • удалите тестовый файл и попробуйте восстановить его из копии.

Если скрипт делает архив, проверьте еще и распаковку. Я обычно автоматизирую такой тест: отдельный скрипт создаёт тестовые данные, запускает бэкап, удаляет оригинал и восстанавливает, а потом сравнивает контрольные суммы. Это даёт уверенность, что в ночь перед дедлайном бэкап не подведёт.

Как запускать резервное копирование автоматически

Для Linux самый распространенный вариант — cron. Это особенно удобно на Ubuntu-серверах и домашних машинах.

Пример записи в crontab:

0 2 * * * /usr/bin/python3 /home/user/backup.py >> /var/log/backup_cron.log 2>&1

Что это значит:

  • запуск каждый день в 02:00;
  • вывод пишется в лог;
  • ошибки тоже попадают в файл.

Для Windows обычно используют «Планировщик заданий». В обоих случаях важно убедиться, что скрипт запускается от имени пользователя с достаточными правами и что переменные окружения (например, PATH) настроены корректно. Частая проблема — скрипт работает из терминала, но не работает из cron, потому что не видит нужных библиотек или путей. Я всегда прописываю полные пути к интерпретатору и файлам, а в самом скрипте использую абсолютные пути.

Практический совет: начните с малого

Если задача учебная или вам нужен первый рабочий вариант, не пытайтесь сразу сделать «комбайн» на все случаи жизни. Для старта достаточно такого набора:

  • pathlib для путей;
  • shutil.copy2() или shutil.make_archive() для копирования;
  • datetime для имени;
  • logging для журнала;
  • cron для запуска по расписанию.

А уже потом можно добавлять:

  • инкрементальные копии;
  • ротацию старых бэкапов;
  • шифрование;
  • выгрузку на удаленный сервер;
  • резервирование баз данных и конфигов отдельно.

Мой первый production-скрипт был именно таким — простым, но надёжным. Он до сих пор работает на одном из серверов, только оброс дополнительными проверками и уведомлениями в Telegram.

Когда одного Python-скрипта мало

Python-скрипт отлично подходит для локальных файлов, конфигов, учебных проектов и небольших серверов. Но если речь идет о важных данных, одной копии недостаточно. В нормальной схеме должны быть:

  • минимум две копии;
  • разные носители;
  • понятный срок хранения;
  • периодическая проверка восстановления;
  • отдельное хранение критичных данных.

Для серверов, где важна надежность, Python-скрипт часто становится не заменой, а надстройкой над общей политикой резервного копирования. Например, он может собирать конфиги и дампы баз, а затем rsync или rclone синхронизирует их в облако. Главное — не полагаться на единственный метод.

Вывод

Написать Python-скрипт для резервного копирования несложно, если сразу решить, что именно вы хотите получить: папку-копию или ZIP-архив. Для большинства задач хватает стандартной библиотеки, метки времени, логирования и проверки ошибок. Если сделать скрипт аккуратно с самого начала, он станет не одноразовым примером, а реальным рабочим инструментом. И помните: бэкап, который ни разу не проверяли восстановлением, — это не бэкап, а надежда.

FAQ

Какой модуль Python лучше всего подходит для бэкапа?
Для простого копирования обычно хватает shutil. Для работы с путями удобен pathlib, для именования копий — datetime.

Что лучше: папка или ZIP-архив?
Если нужен быстрый перенос и экономия места, лучше ZIP. Если важна простота восстановления и прозрачная структура, подойдет папка.

Как не копировать лишние файлы?
Добавьте список исключений: временные файлы, логи, кэш, __pycache__, .git, node_modules.

Как часто делать резервную копию?
Зависит от ценности данных. Для рабочих файлов — ежедневно или чаще. Для учебных проектов может хватать еженедельного графика.

Можно ли использовать такой скрипт для сервера?
Да, если объем данных небольшой и понятны границы ответственности. Для критичных систем нужен более строгий подход: ротация, проверка, несколько уровней хранения и тестовое восстановление.