前言

在一个网站上申请了一个无限容量的FTP空间,每月2T流量,对我而言已经很多了,
官方给的使用说明就是,绑定自己的域名(需要已备案),然后通过直链分享文件和下载文件。

我的问题

我想用来当作图床,或者个人网盘,但是每次都需要依靠官方提供的客户端上传,并且获取远程链接,这就很麻烦了。我直接在网站后台上传,然后取得远程链接不行吗?
对于正常服务器是百分百可行的,但是对于ftp空间而言,只有上传和下载功能,根本没办法执行程序和脚本

有条件的小伙伴,可以使用七牛云或者又拍云等等的,这些服务商专做cdn加速和资源存储,博主学生党没钱,所以白嫖这个ftp空间,还要动手写脚本,实现这一功能 话说,我搞的这个ftp空间用的就是又拍云的cdn加速,下载贼快,嘿嘿嘿~~

解决思路

拿到这么好的东西(无限存储和2T流量),emmm,一定不能浪费了
虽然ftp空间本身不具有执行程序的条件,但我可以用另一台服务器,来辅助完成上传功能。
况且我的博客搭建在Linux服务器上的,写一个脚本,专门负责上传指定文件到ftp空间,不就可以了吗
对于人而言,可能是会觉得有点麻烦,不过机器就是机器,你不用它,它还不是闲置着

解决过程

1. 明确程序执行过程

Snipaste_2019-12-10_18-01-16.png

大致流程是这样的,emmm,软件工程没学好,随便拿几个方框画一画

我们需要做的事情是2.22.3

前端上传数据并保存,这个功能,是由网站本身做好了的。

2. 程序上传哪些文件?

一个网站,后台上传文件后,会保存在特定目录,那么我们只需要让程序循环检测这个目录有没有新文件产生,一旦新文件产生,则立即上传这个新文件至ftp空间

3. 为什么要删除已上传数据?

其实还是为了方便,程序判断,是否有新文件产生

最初我的想法就是,上传到ftp服务器后,就删除本地服务器的源文件,然后一直保持着当前文件夹内是空的(这样好判断是否有新文件产生

但是做的时候,想了想,最好还是在本地保存一份,万一ftp空间那边出现什么意外,跑路什么的,那我就只能哭吧

所以我改成了在源文件后面加后缀.bak 例如:源文件是1.png 上传成功后,将其重命名为1.png.bak

当然上传的时候,需要去检测文件后缀是否是.bak 是的话不上传了,不是,那这文件必定是网站新上传上来的文件

我没学过php,php这边可以搞个上传成功的信号,上传成功后信号发送给Python脚本,Python程序接收到这个信号,就知道新文件是哪个?

这应该是可行的,话说我会php了,就不会拿Python完成这个功能了。

开始撸代码

需求模块:

os
ftplib
time

1. 用Python完成ftp上传和下载

使用ftplib模块
这里描述下基本方法:


新建ftp:f = ftplib.FTP()
连接ftp:f.connect(host='你的ftp地址',port='端口号,默认22')
登录ftp:f.login(user='', passwd='')
获取登录欢迎信息:f.f.welcome
改变当前路径:f.cwd('ftp路径')
上传文件:f.storbinary('STOR ' + 'ftp上待保存的文件名', 本地文件读出的内容)
下载文件:f.retrbinary('RETR ' + '远程文件名', '本地保存的文件名')
获取目录下所有信息:f.dir() -->返回数据为列表
获取目录下所有文件:f.nlst() -->返回数据为列表
新建远程目录:ftp.mkd(pathname)
返回当前所在位置:ftp.pwd()
删除远程目录:ftp.rmd(dirname)
删除远程文件:ftp.delete(filename)
将fromname修改名称为toname:ftp.rename(fromname, toname)

详细的可以参考下方文件,或者百度了解
https://blog.csdn.net/polaris_119/article/details/90736915

我所需要的功能,只有上传,主要做上传单个文件和整个文件夹,顺便做了下载单个文件和下载整个文件夹

代码

import os
import ftplib


class Myftp(object):
    f = ftplib.FTP()
    bIsDir = False
    path = ''

    def __init__(self, host, port=21):
        self.f.connect(host, port)

    def login(self, user, passwd):
        # 登录ftp
        self.f.login(user=user, passwd=passwd)
        print(self.f.welcome)

    def cwd(self, remote_dir):
        # 改变路径
        self.f.cwd(remote_dir)

    def download(self, local_file, remote_file):
        # 下载文件
        file = open(local_file, 'wb')
        self.f.retrbinary('RETR ' + remote_file, file.write)
        file.close()
        return True

    def upload(self, remote_dir, local_file, remote_file):
        # try:
        #     # ftp没有这个文件夹,则自动创建一个
        #     self.f.cwd(remote_dir)
        # except:
        #     self.f.mkd(remote_dir)
        #     self.f.cwd(remote_dir)
        # 上传文件
        if not os.path.exists(local_file):
            print('当前目录不存在此文件: ', local_file)
            return False
        file = open(local_file, 'rb')
        self.f.storbinary('STOR '+remote_file, file)
        file.close()
        return True

    def upload_folder(self, local_dir, remote_dir):
        local_names = os.listdir(local_dir)
        try:
            self.f.cwd(remote_dir)
        except ftplib.error_perm:
            self.f.mkd(remote_dir)
            self.f.cwd(remote_dir)
        for local in local_names:
            src = os.path.join(local_dir, local)
            if os.path.isdir(src):
                self.upload_folder(local_dir=src, remote_dir=local)
            else:
                self.upload(local_dir, src, local)
        self.f.cwd('..')
        return

    def download_folder(self, local_dir, remote_dir):
        self.f.cwd(remote_dir)
        remote_names = self.f.nlst()
        if not os.path.isdir(local_dir):
            os.mkdir(local_dir)
        for file in remote_names:
            local = os.path.join(local_dir, file)
            if self.isDir(file):
                self.download_folder(local_dir, remote_dir)
            else:
                self.download(local, file)
        self.f.cwd('..')

    def show(self, list):
        result = list.lower().split(" ")
        if self.path in result and "<dir>" in result:
            self.bIsDir = True

    def isDir(self, path):
        self.bIsDir = False
        self.path = path
        self.f.retrlines('LIST', self.show)
        return self.bIsDir

    def close(self):
        self.f.quit()

2. 检测文件夹内产生的新文件并上传

一共三个小功能:

  • 检测是否有新文件产生
  • 上传新文件
  • 将已上传文件重命名

导入模块:

import ftp
# 上面的写的ftp上传下载模块,模块我给命名的`ftp`

检测、上传、重命名功能实现

检测新文件并返回新文件名和新文件所在路径

def exits_file():
    # 列出所有文件目录
    file_list = os.listdir('111')
    # 只需要最后一个文件目录
    target_dir = file_list[len(file_list)-1]
    dir = '111/' + target_dir
    file_list = os.listdir(dir)
    for i in file_list:
        if i.endswith('.bak'):
            file_list.remove(i)
    return dir, file_list

我的文件目录是这样的

2019
  |——11
     |——1.png
     |——2.png
   ——12
     |——1.png
     |——2.png

我每次取最后一个文件夹,是因为我知道,这个文件夹一定是在最后面(文件命名是按照数字从小到大排列,并且os.listdir() 返回的列表,也是从小到大排列的)
最后一个文件夹也必定是新文件出现的文件夹,因为网站上传的文件,是按照年月进行创建文件夹,现在是2019/12,那么上传的文件必定是在2019/12/下面

上传目标文件函数

# 上传目标文件函数
def upload_file():
    f = Myftp('主机地址')
    f.login('账号', '密码')
    dir, file_list = exits_file() # 从上一个函数取得本地路径和待上传的文件名
    for i in file_list:
        remote_dir = '/usr/uploads/'+'/'+dir #这里拼接下ftp空间的保存的路径
        f.cwd(remote_dir) # 切换ftp路径
        f.upload(remote_dir, dir+'/'+i, i) # 开始上传文件
        change_file_suffix(dir, i) #上传文件后,修改本地文件名
    f.close()

改本地源文件后缀,作为备份

# 改本地源文件后缀,作为备份
def change_file_suffix(dir, file):
    src = dir+'/'+file # 拼接现在的文件路径
    dst = dir+'/'+file+'.bak' # 需要修改成的文件名
    if not file.endswith('.bak'): # 做一个判断,如果后缀是.bat,说明已经是备份文件了,无需再修改
        os.rename(src, dst)
    return

最后一直循环运行

if __name__ == '__main__':
    while True:
        upload_file()
        time.sleep(5) # 还是给个休眠时间,机器也会累嘛

3. 前台获取文件远程链接

我的博客正常上传文件返回来的,文件链接是这样的:

http://www.z2blog.com/usr/uploads/2019/12/2261909415.png

为了取巧,我将ftp空间的文件上传路径也设置成/usr/uploads/
所以上方文件上传ftp空间后,远程连接应该是:

http://assets.z2blog.com/usr/uploads/2019/12/2261909415.png

改变的只有域名前缀而已,所以在前台,通过js 将原本的www替换成assets 搞定~

实现的效果

Animation.gif

网站前台显示成功上传,图片依然不加载的情况,是因为ftp空间的cdn需要时间缓存新内容,去ftp空间里看,已经上传成功了

Last modification:December 15th, 2019 at 04:56 pm