【问题标题】:Python - Transfer a file from HTTP(S) URL to FTP/Dropbox without disk writing (chunked upload)Python - 将文件从 HTTP(S) URL 传输到 FTP/Dropbox,无需磁盘写入(分块上传)
【发布时间】:2018-11-29 17:56:25
【问题描述】:

我有一个大文件 (500 Mb-1Gb) 存储在 HTTP(S) 位置
(比如https://example.com/largefile.zip)。

我对 FTP 服务器有读/写权限

我有普通用户权限(没有 sudo)。

在这些限制下,我想通过请求从 HTTP URL 读取文件并将其发送到 FTP 服务器,而无需先写入磁盘。

通常情况下,我会这样做。

response=requests.get('https://example.com/largefile.zip', stream=True)
with open("largefile_local.zip", "wb") as handle:                                                                                                     
 for data in response.iter_content(chunk_size=4096):
  handle.write(data)     

然后将本地文件上传到 FTP。但我想避免磁盘 I/O。我无法将 FTP 挂载为 fuse 文件系统,因为我没有超级用户权限。

理想情况下,我会使用ftp_file.write() 而不是handle.write()。那可能吗? ftplib 文档似乎假设只上传本地文件,而不是response.content。所以理想情况下我想这样做

response=requests.get('https://example.com/largefile.zip', stream=True)
for data in response.iter_content(chunk_size=4096):
 ftp_send_chunk(data)   

我不知道怎么写ftp_send_chunk()

这里有一个类似的问题 (Python - Upload a in-memory file (generated by API calls) in FTP by chunks)。我的用例需要从 HTTP URL 中检索一个块并将其写入 FTP。

P.S.:答案中提供的解决方案(围绕 urllib.urlopen 的包装)也适用于 Dropbox 上传。我在使用我的 ftp 提供商时遇到了问题,所以最后使用了 Dropbox,它工作可靠。

请注意,Dropbox 在 api 中有一个“添加网络上传”功能,它做同样的事情(远程上传)。这只适用于“直接”链接。在我的用例中,http_url 来自 i.p. 的流媒体服务。受限制的。因此,这种解决方法变得很有必要。 这是代码

import dropbox;
d = dropbox.Dropbox(<ACTION-TOKEN>);
f=FileWithProgress(filehandle);
filesize=filehandle.length;
targetfile='/'+fname;
CHUNK_SIZE=4*1024*1024
upload_session_start_result = d.files_upload_session_start(f.read(CHUNK_SIZE));
num_chunks=1
cursor = dropbox.files.UploadSessionCursor(session_id=upload_session_start_result.session_id,
                                           offset=CHUNK_SIZE*num_chunks)
commit = dropbox.files.CommitInfo(path=targetfile)
while CHUNK_SIZE*num_chunks < filesize:
 if ((filesize - (CHUNK_SIZE*num_chunks)) <= CHUNK_SIZE):
  print d.files_upload_session_finish(f.read(CHUNK_SIZE),cursor,commit)
 else:
  d.files_upload_session_append(f.read(CHUNK_SIZE),cursor.session_id,cursor.offset)
 num_chunks+=1
cursor.offset = CHUNK_SIZE*num_chunks
link = d.sharing_create_shared_link(targetfile)  
url = link.url
dl_url = re.sub(r"\?dl\=0", "?dl=1", url)
dl_url = dl_url.strip()
print 'dropbox_url: ',dl_url;

我认为甚至应该可以通过他们的 python api 使用 google-drive 来做到这一点,但是使用他们的 python 包装器的凭据对我来说太难了。检查this1和this2

【问题讨论】:

  • 您有对https://example.com/largefile.zip 的shell 访问权限吗?如果是这样,您为什么不使用lftp或类似的应用程序将largefile.zip直接上传到ftp服务器?
  • 不。我只能从 url 读取数据(它是一个云流媒体服务)

标签: python ftp python-requests dropbox dropbox-api


【解决方案1】:

使用urllib.request.urlopen 应该很容易,因为它返回一个类似文件的对象,您可以直接使用FTP.storbinary

ftp = FTP(host, user, passwd)

filehandle = urllib.request.urlopen(http_url)

ftp.storbinary("STOR /ftp/path/file.dat", filehandle)

如果您想监控进度,请实现一个类似文件的包装对象,它将调用委托给filehandle 对象,但也会显示进度:

class FileWithProgress:

    def __init__(self, filehandle):
        self.filehandle = filehandle
        self.p = 0

    def read(self, blocksize):
        r = self.filehandle.read(blocksize)
        self.p += len(r)
        print(str(self.p) + " of " + str(self.p + self.filehandle.length)) 
        return r

filehandle = urllib.request.urlopen(http_url)

ftp.storbinary("STOR /ftp/path/file.dat", FileWithProgress(filehandle))

对于 Python 2 使用:

  • urllib.urlopen,而不是 urllib.request.urlopen
  • filehandle.info().getheader('Content-Length') 而不是 str(self.p + filehandle.length)

【讨论】:

  • urllib.request.urlopen 在 Python 3 中,urllib.urlopen 在 Python 2 中,正如我的回答所说。
  • 谢谢。很好的答案。我对此进行了测试,当 url 为“直接”时,它运行良好。在重定向的情况下,这会给出一个虚拟文件(1 Kb),我认为这是重定向的第一级。在这种情况下,可以执行 urllib.urlopen(requests.open(http_url,stream=True).url)。
  • 顺便说一句,我对重定向没有任何问题。 urlopen 自动为我重定向。
猜你喜欢
  • 2015-03-13
  • 2016-02-20
  • 1970-01-01
  • 1970-01-01
  • 2012-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
相关资源
最近更新 更多