superaj 's Home

一辈子,总还是得让一些善念, 推着我们前行, 不管是三年,还是三十年, 专注做点东西, 至少对得起光阴。

宝塔/aapanel 定时备份数据库/web 到指定邮箱

配置定时备份数据库,加上执行此定时任务,可以实现定期备份数据库或者网站到邮箱

import smtplib
import os
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from datetime import datetime

# ===================== 请修改以下配置项 =====================
# 邮件发送相关配置
SMTP_SERVER = "smtp.qq.com"  # 邮件SMTP服务器(QQ:smtp.qq.com;163:smtp.163.com)
SMTP_PORT = 465  # SMTP SSL端口(QQ/163均为465)
SEND_EMAIL = "xxxx@qq.com"  # 发件人邮箱
SEND_EMAIL_PWD = "xxxxx"  # 邮箱授权码(非登录密码!)
RECV_EMAIL = "xxxx@qq.com"  # 收件人邮箱(多个用逗号分隔)

# 宝塔备份文件夹配置(核心修改)
BACKUP_FOLDER_PATH = "/www/backup/site/xxxxx/"  # 宝塔备份文件夹路径
FILTER_FILE_EXT = [".tar.gz", ".sql.gz", ".zip"]  # 可选:只发送指定后缀的备份文件(留空则发送所有文件)
EMAIL_SUBJECT = f"备份_xxxx_{datetime.now().strftime('%Y%m%d_%H%M%S')}"  # 邮件标题
# ==========================================================

def get_backup_files(folder_path):
    """
    递归获取指定文件夹及所有子文件夹下的备份文件
    :param folder_path: 备份主文件夹路径
    :return: 有效文件路径列表
    """
    # 检查主文件夹是否存在
    if not os.path.isdir(folder_path):
        print(f"错误:备份主文件夹不存在!路径:{folder_path}")
        return []
    
    # 递归遍历所有文件夹(包括子文件夹)
    file_list = []
    # os.walk返回:当前文件夹路径、子文件夹列表、文件列表
    for root, dirs, files in os.walk(folder_path):
        # 跳过空文件的文件夹
        if not files:
            continue
        # 遍历当前文件夹下的所有文件
        for file_name in files:
            file_path = os.path.join(root, file_name)
            # 只处理文件(os.walk已过滤文件夹,此处双重校验)
            if not os.path.isfile(file_path):
                continue
            # 过滤指定后缀的文件(如果配置了过滤规则)
            if FILTER_FILE_EXT and not any(file_path.endswith(ext) for ext in FILTER_FILE_EXT):
                # 计算相对路径,方便展示子文件夹层级
                rel_path = os.path.relpath(file_path, folder_path)
                print(f"跳过非备份文件:{rel_path}(后缀不在过滤列表中)")
                continue
            # 添加有效文件到列表
            file_list.append(file_path)
            # 打印子文件夹层级提示
            rel_path = os.path.relpath(file_path, folder_path)
            print(f"发现备份文件:{rel_path}")
    
    # 检查是否有有效文件
    if not file_list:
        print(f"警告:备份主文件夹及所有子文件夹中无有效文件!路径:{folder_path}")
        return []
    
    # 汇总打印结果
    print(f"\n✅ 递归遍历完成,共获取 {len(file_list)} 个有效备份文件")
    for idx, file in enumerate(file_list, 1):
        rel_path = os.path.relpath(file, folder_path)
        print(f"  {idx}. {rel_path}(大小:{round(os.path.getsize(file)/1024/1024, 2)}MB)")
    return file_list

def send_backup_by_email():
    """批量发送宝塔备份文件夹下的所有文件到指定邮箱"""
    # 1. 获取待发送的备份文件列表
    backup_files = get_backup_files(BACKUP_FOLDER_PATH)
    if not backup_files:
        return False
    
    # 2. 构建邮件基础信息
    msg = MIMEMultipart()
    msg["From"] = SEND_EMAIL
    msg["To"] = RECV_EMAIL
    msg["Subject"] = EMAIL_SUBJECT

    # 3. 构建邮件正文(自动列出文件信息)
    file_info = "\n".join([f"<li>{os.path.basename(f)}(大小:{round(os.path.getsize(f)/1024/1024, 2)}MB)</li>" for f in backup_files])
    email_content = f"""
<p>您好!</p>
<p>这是宝塔面板自动批量发送的备份文件,本次发送信息如下:</p>
<p>备份文件夹路径:{BACKUP_FOLDER_PATH}</p>
<p>发送时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>发送文件总数:{len(backup_files)} 个</p>
<p>文件列表:</p>
<ul>{file_info}</ul>
<p>请注意查收附件!</p>
"""
    msg.attach(MIMEText(email_content, "html", "utf-8"))

    # 4. 批量添加附件(逐个处理)
    failed_files = []
    for file_path in backup_files:
        file_name = os.path.basename(file_path)
        try:
            with open(file_path, "rb") as f:
                attachment = MIMEBase("application", "octet-stream")
                attachment.set_payload(f.read())
                encoders.encode_base64(attachment)
                # 设置附件头(解决中文文件名乱码问题)
                attachment.add_header(
                    "Content-Disposition",
                    f"attachment; filename*=utf-8''{file_name}"
                )
                msg.attach(attachment)
            print(f"已添加附件:{file_name}")
        except Exception as e:
            failed_files.append((file_name, str(e)))
            print(f"添加附件失败:{file_name} → {str(e)}")

    # 5. 发送邮件
    try:
        with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
            server.login(SEND_EMAIL, SEND_EMAIL_PWD)
            server.sendmail(SEND_EMAIL, RECV_EMAIL.split(","), msg.as_string())
        
        # 输出发送结果
        print(f"\n===== 发送结果 =====")
        print(f"✅ 邮件发送成功!已发送至 {RECV_EMAIL}")
        print(f"📤 成功添加附件:{len(backup_files) - len(failed_files)} 个")
        if failed_files:
            print(f"❌ 失败附件:{len(failed_files)} 个")
            for file_name, err in failed_files:
                print(f"  - {file_name}:{err}")
        return True
    except smtplib.SMTPException as e:
        print(f"邮件发送失败(SMTP错误):{str(e)}")
        return False
    except Exception as e:
        print(f"邮件发送失败:{str(e)}")
        return False

if __name__ == "__main__":
    # 执行批量发送
    send_backup_by_email()

 

Copyright © 2026 superaj 's Home. All Right Reserved.