用Python实现FTP文件上传

前情

最近在做活动编辑器的时候,考虑到运营人员很多都没有图片上传的权限,所以想在活动编辑器里面内置一个这样的功能,让运营可以使用上传控件或者托拽的方式上传图片到服务器,并在上传成功之后,自动把地址放到文本框里。

第一步要做的就是尝试着实现用FTP来上传一个文件到服务器的指定目录,同时基于文件的sha1值来命名上传之后的文件。

手上现有的两种实现路径是NodeJsPython,不过想到最近刚学Python,正需要这样一个机会,就先尝试python的方式。

开发

先是Google了一下,知道了一个ftplib的内置模块可以用来做FTP相关的功能。然后是之前在即刻上有看到余弦发的一个文章,说到用Python一句话查看文件的哈希值,上去找了找,也很快找到了那篇文章里面的相关代码。

折腾一下之后,实现代码如下:

import ftplib
import hashlib
import os

host = 'YOUR HOST'
username = 'YOUR NAME'
pwd = 'YOUR PWD'
port = 21
filePath = 'some/path/to/img.jpg'
serverPath = '/where/to/store/your/file/on/the/server/'

session = ftplib.FTP()

session.connect(host, port)

session.login(username, pwd)

session.cwd(serverPath)

file = open(filePath, 'rb')

filename, file_extension = os.path.splitext(filePath)

str_hash = hashlib.sha1(file.read()).hexdigest()

target_file_name = str_hash[:16]+file_extension

session.storbinary('STOR '+target_file_name, file)

file.close()

session.quit()

代码写完,就顺手执行看了下效果:文件顺利按的传到了预设的目录,也重命名为了指定的文件名。但是,文件的体积是0

调试

在Python的IDEL环境里面使用help命令查看了file相关的各种文档,最后把问题的方向指向了file.read。因为当时在IDEL环境里面,使用file.read读取了要上传的文件内容之后,再次调用file.read时,返回的内容为空。

于是在Google上面用python file read twice作为搜索词搜索了一下。果然找到了问题,文件里面有一个指针指向上一次读取的位置,默认是在最开始,也就是0。file.read会读取文件的全部内容,完成之后,指针就指向文件的最末尾。

所以,在调用过一次file.read之后,再次调用file.read,会从文件最末尾开始获取内容,这获取的内容自然就是空了。而ftp.storbinary在上传文件的时候,内部应该也是会使用类似file.read类似的方法来获取要上传的文件内容并传递给服务器。这个时候也自然就没法正常获取正确的文件内容

解决方式是在file.read之后,使用file.seek(0)来把文件的指针指回最开始。

最终代码:

import ftplib
import hashlib
import os

host = 'YOUR HOST'
username = 'YOUR NAME'
pwd = 'YOUR PWD'
port = 21
filePath = 'some/path/to/img.jpg'
serverPath = '/where/to/store/your/file/on/the/server/'

session = ftplib.FTP()

session.connect(host, port)

session.login(username, pwd)

session.cwd(serverPath)

file = open(filePath, 'rb')

filename, file_extension = os.path.splitext(filePath)

str_hash = hashlib.sha1(file.read()).hexdigest()

target_file_name = str_hash[:16]+file_extension

# 前面的file.read使得file的read cursor指向了文件的尾部
# 如果不手动调整指针的话,后面的文件上传,就会是一个空文件
file.seek(0)

session.storbinary('STOR '+target_file_name, file)

file.close()

session.quit()

参考文档

  1. Python Script Uploading files via FTP
  2. Extracting extension from filename in Python
  3. Why can’t I call read() twice on an open file?
  4. 我是如何 Python 一句话校验软件哈希值的