宝塔/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()
