Press "Enter" to skip to content

使用python备份远程FTP上的文件和Mysql

写这个脚本初衷是想借助VPS来定期备份虚拟主机上的文件和数据库。最近对美国VPS的速度有点不满了,想把博客搬到香港的虚拟主机上,但虚拟主机的备份不太方便,于是就冒出了这样一个想法:用VPS备份虚拟主机。

其实备份文件是个简单的事情,但要把数据库都导出,就麻烦了。大部分Mysql有个locahost的限制,只能在本地连接,不能远程连接数据库,想来想去,只能是在localhost弄个php的脚本,用来导出数据库

程序包含2个文件,分别是
db_backup.py:主程序,在里面设置好虚拟主机的FTP地址,用户名和密码信息,然后执行备份程序,如果要定时执行,在crontab里设置这个脚本即可
db_backup.php:导出Mysql的脚本,主程序会把此脚本发送至FTP服务器,然后自动访问http://ftp/db_backup.php把Mysql导出至FTP服务器上的的db_backup目录。因此需要在此文件里填写好Mysql的连接信息。备份完成以后主程序会删除FTP服务器上的此文件和db_backup目录,保证了数据库不被泄露

前提条件:
1,VPS安装Python,这个一般没问题,VPS的虚拟化技术不能是OpenVZ。
2,虚拟主机必须是php型的,且支持passthru()函数,国内的主机一般都支持,不过还是建议事先用探针检测一下

脚本打包下载请点击这里

db_backup.py
[code lang=”python”]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import commands
import sys
import os
import re
import zipfile
import urllib2
import shutil
from urllib2 import URLError, HTTPError
from datetime import date,datetime,timedelta

class Package:
"""
检查fuse-curlftpfs软件包的安装与否,如果没有,则安装之
"""

def __init__(self,name):
self.name=name
def install(self):
print(‘execute "yum install ‘ + self.name + ‘"…’)
#cmd=commands.getstatusoutput(‘yum install ‘ + self.name + ‘ -y’)
print os.system(‘yum install ‘ + self.name + ‘ -y’)
if self.name in os.popen(‘rpm -qa | grep ‘ + self.name).read():
print(‘Install ‘ + self.name + ‘ successful.’)
else:
print(‘Install ‘ + self.name + ‘ failed, aborting.’)
sys.exit()
def checkinstall(self):
cmd=commands.getstatusoutput(‘rpm -qa | grep ‘ + self.name)
if self.name in cmd[1]:
print(self.name + ‘ has aleady installed. Continue…’)
else:
print(self.name + ‘ isn\’t installed. Ready to install it.’)
self.install()

class Backup:
"""
使用shell命令挂载之,然后执行备份过程(压缩目录,删除旧文件),压缩完成以后再解除挂载
"""

def __init__(self,server,user,passwd,webroot,backupname):
self.server=server
self.user=user
self.passwd=passwd
self.webroot=webroot
self.backupname=backupname
def mount(self):
#判断挂载点是否存在,如果不存在则尝试新建
try:
if os.path.isdir(‘/’ + self.backupname):
print(‘found mount point /’ + self.backupname + ‘, ready to mount ftp server.’)
else:
print(‘doesn\’t found mount point /’ + self.backupname + ‘, build it.’)
os.mkdir(‘/’ + self.backupname)
except IOError, error:
print error
sys.exit()

#开始挂载
cmd=commands.getstatusoutput(r’curlftpfs ftp://’ + self.user + ‘:’ + self.passwd + ‘@’ + self.server + r’ /’ + self.backupname + ‘ -o codepage=utf8′)
print cmd[1]

#检验是否挂载成功
pat=r’fuse(.+)/’ + self.backupname
all=os.popen(‘df -hT’).read()
if re.search(pat,all):
print(‘mounted /’ + self.backupname + ‘, continue…’)
else:
print(‘mount failed. please check.’)
sys.exit()

#把php脚本发送至FTP
try:
src = file(os.getcwd() + ‘/db_backup.php’, ‘r+’)
des = file(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup.php’, ‘w+’)
des.writelines(src.read())
src.close()
des.close()
if not os.path.isdir(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup’):
os.mkdir(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup’)
#访问php文件(导出数据库)
try:
page=urllib2.urlopen(‘http://’ + self.server + ‘/db_backup.php’)
print page.read()
except (URLError, HTTPError), err:
print err
print(‘mysqldump failed! please check.’)
except (IOError, OSError), e:
print e
self.umount()
sys.exit()

def backup(self):

self.mount()

newday = date.today() #获取今天的日期
oldday = date.today()-timedelta(5) #获得5天前的日期
newfile = self.backupname + ‘_backup_’ + str(newday.year) + ‘.’ + str(newday.month) + ‘.’ + str(newday.day) + ‘.zip’ #本次备份的文件名
oldfile = self.backupname + ‘_backup_’ + str(oldday.year) + ‘.’ + str(oldday.month) + ‘.’ + str(oldday.day) + ‘.zip’ #5天前备份的文件名

print(‘delete old file…’)
try:
if os.path.isfile(oldfile):
os.remove(oldfile)
else:
print(‘don\’t found old file, jumped.’)
except IOError, err:
print err

#开始压缩目录
print(‘compress directory. it will take a while.’)
try:
try:
f = zipfile.ZipFile(newfile,’w’,zipfile.ZIP_DEFLATED)
for dirpath, dirnames, filenames in os.walk(‘/’ + self.backupname + ‘/’ + self.webroot):
for filename in filenames:
f.write(os.path.join(dirpath,filename))
print("backup completely! file name is " + newfile)
except (IOError, OSError), err:
print err
finally:
f.close()
self.umount()
sys.exit()

def umount(self):

#删除FTP里的数据库目录和文件
if os.path.isdir(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup’):
shutil.rmtree(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup’)
if os.path.isfile(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup.php’):
os.remove(‘/’ + self.backupname + ‘/’ + self.webroot + ‘/db_backup.php’)

#判断是否解除挂载成功
print os.system(‘umount /’ + self.backupname)
pat=r’fuse(.+)/’ + self.backupname
all=os.popen(‘df -hT’).read()
if not re.search(pat,all):
print(‘umounted /’ + self.backupname + ‘. Done!’)
else:
print(‘umount failed. please check.’)

if __name__==’__main__’:
ps=Package(‘fuse-curlftpfs’)
ps.checkinstall()

#如果不是独立IP的话,FTP地址请填写服务商分配的三级域名,请确保能通过"http://FTP地址"访问网站首页
#FTP地址开头不要加http://,末尾不要加/
#网站目录是指FTP上存放网站的目录,有些国内服务商喜欢把网站目录设置成FTP根目录下的wwwroot目录
#如果网站目录就是FTP根目录,把此项留空即可(填写成”)
#标示符是挂载到本地的文件名,及压缩以后的文件名,只能用英文且不能有空格,可以用下划线
#如果要备份多个FTP,把下面2行复制一遍即可,注意每个的标示符不能一样
bu=Backup(‘FTP地址’,’FTP用户名’,’FTP密码’,’网站目录’,’标示符’)
bu.backup()

db_backup.php
[code lang=”php”]
<?php
$db_server = ‘localhost’; //一般不需要改
$db_user = ‘Mysql用户名’;
$db_pass = ‘Mysql密码’;
$save_path = ‘./db_backup’; //导出的数据库备份目录,此目录必须已存在
$suf=’.sql’;

function actionExport($db_server, $db_user, $db_pass, $save_path, $suf){
echo "Now start exporting databases…\n ";
$conn = mysql_connect($db_server, $db_user, $db_pass) or die("couldn’t connect to mysql.");
mysql_query("SET NAMES ‘utf8’");
$result_db = mysql_list_dbs($conn);
for ($i=0;$i<mysql_num_rows($result_db);$i++){
$db_name = mysql_tablename($result_db,$i);
echo "dump database: $db_name";
$cmd = "mysqldump -h $db_server -u $db_user -p$db_pass -q –skip-lock-tables $db_name > $save_path/$db_name$suf";
dbdump($cmd);
}
mysql_close($conn);
}

function dbdump($cmd){
passthru($cmd, $ret);
if ($ret != 0) {
echo " : Failed\n ";
}else{
echo " : OK \n ";
}
}

if (mysql_connect($db_server, $db_user, $db_pass)) {
actionExport($db_server, $db_user, $db_pass, $save_path, $suf);
}else{
echo "Could not connect to mysql.";
}
?>

2 Comments

  1. 伪劣熊 2013/05/22

    用的win,写了个计划任务….

神搜索进行回复 取消回复

您的电子邮箱地址不会被公开。