宝塔面板实现按年月自动备份目录至阿里云 OSS(python脚本、大文件分片上传版)

一、需求概述

基于宝塔面板完成以下核心需求:

  1. 定时执行Python脚本,无需人工干预;
  2. 针对按「年月」命名的目录(如2026-03),自动备份上个月的目录(当前2026-04则备份2026-03);
  3. 支持5G级大目录备份,通过分片上传解决大文件传输超时、内存溢出问题;
  4. 备份目标为阿里云OSS,支持断点续传、上传进度监控,保障备份可靠性。

二、实现步骤

步骤1:前置环境准备

1.1 安装Python依赖

登录宝塔面板→进入「SSH终端」,执行以下命令安装依赖:

bash 复制代码
pip3 install oss2 python-dotenv
1.2 阿里云OSS配置
  1. 登录阿里云OSS控制台,创建Bucket(建议私有权限,地域选择与服务器就近区域);
  2. 创建RAM子账号,授予以下OSS权限(最小权限原则):
    • oss:PutObject
    • oss:InitiateMultipartUpload
    • oss:UploadPart
    • oss:CompleteMultipartUpload
    • oss:AbortMultipartUpload
  3. 记录关键信息:AccessKey IDAccessKey SecretBucket名称Endpoint(如oss-cn-beijing.aliyuncs.com)。

步骤2:编写备份脚本(支持大文件分片)

2.1 创建OSS配置文件(安全存储密钥)

在服务器创建/www/oss_config.env文件,写入以下内容(替换为实际信息):

ini 复制代码
# 阿里云OSS配置
OSS_ACCESS_KEY_ID=你的RAM账号AccessKey ID
OSS_ACCESS_KEY_SECRET=你的RAM账号AccessKey Secret
OSS_BUCKET_NAME=你的Bucket名称
OSS_ENDPOINT=你的OSS地域节点(如oss-cn-hangzhou.aliyuncs.com)
2.2 编写核心备份脚本

创建/www/backup_to_oss.py文件,完整代码如下:

python 复制代码
import os
import time
import tarfile
import oss2
import math
from datetime import datetime, timedelta
from dotenv import load_dotenv
from oss2 import determine_part_size, MultipartUploader, ObjectIterator

# 加载环境变量(避免密钥硬编码)
load_dotenv('/www/oss_config.env')

# ====================== 核心配置项(按需修改)======================
# 阿里云OSS基础配置
OSS_ACCESS_KEY_ID = os.getenv('OSS_ACCESS_KEY_ID')
OSS_ACCESS_KEY_SECRET = os.getenv('OSS_ACCESS_KEY_SECRET')
OSS_BUCKET_NAME = os.getenv('OSS_BUCKET_NAME')
OSS_ENDPOINT = os.getenv('OSS_ENDPOINT')

# 待备份目录映射(本地目录前缀: OSS存储前缀)
BACKUP_DIRS = {
    "/data/logs": "/backup/logs",       # 示例1:本地日志目录
    "/data/business": "/backup/business" # 示例2:业务数据目录(可新增)
}

# 临时压缩包存储目录(确保磁盘空间≥待备份目录大小)
TMP_DIR = "/www/tmp"

# 大文件分片上传配置
PART_SIZE = 100 * 1024 * 1024  # 分片大小:100MB/片(阿里云要求100KB~5GB)
UPLOAD_THREADS = 5             # 并发上传线程数(带宽充足可设8-10)
OSS_TIMEOUT = (30, 300)        # 连接超时30s,读取超时300s(大文件需加长)
# ==========================================================================

def get_last_month():
    """计算上个月的年月,返回格式:YYYY-MM(如2026-03)"""
    today = datetime.today()
    first_day_of_this_month = datetime(today.year, today.month, 1)
    last_day_of_last_month = first_day_of_this_month - timedelta(days=1)
    return last_day_of_last_month.strftime("%Y-%m")

def compress_large_dir(source_dir, output_file, chunk_size=10*1024*1024):
    """
    流式压缩大目录(避免内存溢出)
    :param source_dir: 待压缩目录路径
    :param output_file: 压缩包输出路径
    :param chunk_size: 流式写入块大小(默认10MB)
    """
    if not os.path.exists(source_dir):
        raise FileNotFoundError(f"待备份目录不存在:{source_dir}")
    
    # 确保临时目录存在
    os.makedirs(os.path.dirname(output_file), exist_ok=True)
    
    # 流式压缩(逐文件添加,不一次性加载)
    with tarfile.open(output_file, "w:gz", bufsize=chunk_size) as tar:
        for root, dirs, files in os.walk(source_dir):
            for file in files:
                file_path = os.path.join(root, file)
                # 保留相对路径,便于OSS解压后还原目录结构
                tar.add(file_path, arcname=os.path.relpath(file_path, source_dir))
    
    # 返回压缩包大小,用于后续分片计算
    file_size = os.path.getsize(output_file)
    print(f"✅ 压缩完成:{output_file} | 大小:{file_size/1024/1024:.2f} MB")
    return file_size

def upload_large_file_to_oss(local_file, oss_path):
    """
    分片上传大文件至OSS(支持断点续传、进度监控)
    :param local_file: 本地压缩包路径
    :param oss_path: OSS存储路径(如/backup/logs/2026-03.tar.gz)
    """
    # 初始化OSS客户端(配置超时)
    auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
    bucket = oss2.Bucket(
        auth, OSS_ENDPOINT, OSS_BUCKET_NAME,
        connect_timeout=OSS_TIMEOUT[0],
        read_timeout=OSS_TIMEOUT[1]
    )
    
    # 跳过已存在的文件(避免重复上传)
    if bucket.object_exists(oss_path):
        print(f"ℹ️ OSS已存在文件:{oss_path},跳过上传")
        return
    
    # 获取文件大小,自动确定分片大小(兼容阿里云限制)
    file_size = os.path.getsize(local_file)
    part_size = determine_part_size(file_size, preferred_size=PART_SIZE)
    total_parts = math.ceil(file_size / part_size)
    
    print(f"📤 开始分片上传:总大小 {file_size/1024/1024:.2f} MB | 分片大小 {part_size/1024/1024:.2f} MB | 总片数 {total_parts}")
    
    # 初始化分片上传任务
    upload_id = bucket.init_multipart_upload(oss_path).upload_id
    try:
        # 创建分片上传器(支持多线程、断点续传)
        uploader = MultipartUploader(
            bucket, oss_path, upload_id,
            part_size=part_size,
            num_threads=UPLOAD_THREADS
        )
        
        # 上传进度回调函数
        def progress_callback(consumed, total):
            progress = (consumed / total) * 100
            print(f"\r🔄 上传进度:{progress:.2f}% ({consumed/1024/1024:.2f}MB/{total/1024/1024:.2f}MB)", end="")
        
        # 执行上传
        uploader.upload_file(local_file, progress_callback=progress_callback)
        print("\n🔗 合并分片...")
        
        # 完成分片上传(合并所有分片)
        bucket.complete_multipart_upload(oss_path, upload_id, uploader.uploaded_parts)
        print(f"✅ 上传成功:OSS路径 -> {oss_path}")
    
    except Exception as e:
        # 上传失败,清理OSS临时分片
        bucket.abort_multipart_upload(oss_path, upload_id)
        raise Exception(f"❌ 分片上传失败:{str(e)}")

def clean_expired_oss_parts(days=7):
    """清理OSS中过期的未完成分片(避免占用存储空间)"""
    auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
    bucket = oss2.Bucket(auth, OSS_ENDPOINT, OSS_BUCKET_NAME)
    
    expired_time = datetime.now() - timedelta(days=days)
    for upload in ObjectIterator(oss2.ListMultipartUploads(bucket)):
        if upload.initiated_time < expired_time:
            bucket.abort_multipart_upload(upload.key, upload.upload_id)
            print(f"🧹 清理过期分片任务:{upload.key}")

def main():
    """主执行逻辑"""
    try:
        # 1. 计算上个月年月
        last_month = get_last_month()
        print(f"========== 开始备份 {last_month} 目录 ==========")
        
        # 2. 遍历所有待备份目录
        for local_prefix, oss_prefix in BACKUP_DIRS.items():
            # 拼接完整路径
            source_dir = os.path.join(local_prefix, last_month)
            compress_filename = f"{last_month}_{os.path.basename(local_prefix)}.tar.gz"
            tmp_compress_file = os.path.join(TMP_DIR, compress_filename)
            oss_file_path = os.path.join(oss_prefix, compress_filename)
            
            try:
                # 3. 流式压缩目录
                compress_large_dir(source_dir, tmp_compress_file)
                
                # 4. 分片上传至OSS
                upload_large_file_to_oss(tmp_compress_file, oss_file_path)
                
                # 5. 删除本地临时压缩包(可选,节省磁盘空间)
                os.remove(tmp_compress_file)
                print(f"🗑️ 删除本地临时文件:{tmp_compress_file}\n")
            
            except Exception as e:
                print(f"❌ 备份 {source_dir} 失败:{str(e)}\n")
                continue
        
        # 可选:清理OSS过期分片(建议每月执行一次)
        # clean_expired_oss_parts(days=7)
        
        print("========== 所有目录备份完成 ==========")
    
    except Exception as e:
        print(f"❌ 脚本执行失败:{str(e)}")
        raise  # 抛出异常,便于宝塔日志捕获

if __name__ == "__main__":
    main()

步骤3:宝塔面板配置定时任务

  1. 登录宝塔面板,点击左侧「计划任务」;
  2. 点击「添加任务」,按以下参数配置:
    • 任务类型:脚本任务;
    • 任务名称:自定义(如「每月备份上月目录至OSS」);
    • 执行周期:按月执行,选择「每月1日」,执行时间建议凌晨(如00:30);
    • 脚本内容:python3 /www/backup_to_oss.py
    • 可选:开启「发送邮件提醒」,接收执行结果(成功/失败);
  3. 点击「添加」,完成定时任务配置。

步骤4:测试验证

  1. 手动测试 :SSH终端执行python3 /www/backup_to_oss.py,检查是否正常压缩、上传;
  2. 日志验证:宝塔计划任务列表→点击任务右侧「日志」,查看执行记录;
  3. OSS验证:登录阿里云OSS控制台,检查目标Bucket是否存在备份文件。

三、关键说明

  1. 权限保障 :确保/www/tmp目录有读写权限,RAM账号权限完整;
  2. 资源要求:服务器磁盘空间需≥待备份目录大小(5G+),带宽建议≥10Mbps;
  3. 参数调优
    • 大带宽服务器可将UPLOAD_THREADS调至8-10,提升上传速度;
    • 低内存服务器可将PART_SIZE调至50MB,降低内存占用;
  4. 异常处理:脚本内置错误捕获,失败时会输出详细日志,便于定位问题。

总结

  1. 核心能力:通过Python脚本实现「自动计算上月目录+流式压缩+OSS分片上传」,宝塔定时任务保障自动化执行;
  2. 大文件适配:分片上传解决5G级文件传输超时/内存溢出问题,支持断点续传和进度监控;
  3. 可靠性保障:内置文件存在性检查、过期分片清理、异常捕获,确保备份过程稳定。

@漏刻有时

相关推荐
用户0332126663672 小时前
使用 Python 查找并替换 PDF 中的文本
python
2201_756206342 小时前
1111111
开发语言·python
与虾牵手2 小时前
Python asyncio 踩坑实录:5 个让我加班到凌晨的坑 🕳️
python
前端付豪2 小时前
AI Math Tutor v3:题目知识点自动分类
前端·python·llm
魔道不误砍柴功2 小时前
Java Function 高级使用技巧:从工程实战中来
java·开发语言·python
xixihaha13242 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
超越自我肖2 小时前
python--while循环的基础案例
python
2501_921649492 小时前
免费港股实时行情 API:功能、性能与接入指南
开发语言·后端·python·金融·restful
智星云算力2 小时前
实验室无GPU如何深度学习
人工智能·深度学习·阿里云·智星云·gpu算力租用