Python: 通过流上传一个大文件

19 投票
6 回答
36417 浏览
提问于 2025-04-15 20:46

我正在把可能很大的文件上传到一个网络服务器。目前我这样做:

import urllib2

f = open('somelargefile.zip','rb')
request = urllib2.Request(url,f.read())
request.add_header("Content-Type", "application/zip")
response = urllib2.urlopen(request)

但是,这种方法会在上传之前把整个文件的内容都读到内存里。有没有办法让我可以把文件分段上传到服务器呢?

6 个回答

2

你试过用 Mechanize 吗?

from mechanize import Browser
br = Browser()
br.open(url)
br.form.add_file(open('largefile.zip'), 'application/zip', 'largefile.zip')
br.submit()

或者,如果你不想使用 multipart/form-data,看看 这个 老帖子。

它提供了两个选择:

  1. Use mmap, Memory Mapped file object
  2. Patch httplib.HTTPConnection.send
5

文档里没有说明你可以这样做,但在urllib2(和httplib)里的代码可以接受任何有read()方法的对象作为数据。所以,打开一个文件就可以解决这个问题。

你需要自己设置Content-Length这个头信息。如果不设置,urllib2会对数据调用len()函数,而文件对象是不支持这个的。

import os.path
import urllib2

data = open(filename, 'r')
headers = { 'Content-Length' : os.path.getsize(filename) }
response = urllib2.urlopen(url, data, headers)

这是处理你提供的数据的相关代码。它来自Python 2.7中的httplib.py里的HTTPConnection类:

def send(self, data):
    """Send `data' to the server."""
    if self.sock is None:
        if self.auto_open:
            self.connect()
        else:
            raise NotConnected()

    if self.debuglevel > 0:
        print "send:", repr(data)
    blocksize = 8192
    if hasattr(data,'read') and not isinstance(data, array):
        if self.debuglevel > 0: print "sendIng a read()able"
        datablock = data.read(blocksize)
        while datablock:
            self.sock.sendall(datablock)
            datablock = data.read(blocksize)
    else:
        self.sock.sendall(data)
30

在systempuntoout提供的邮件列表讨论中,我发现了一个解决问题的线索。

mmap模块可以让你打开一个文件,这个文件就像一个字符串一样。文件的部分内容会根据需要加载到内存中。

这是我现在使用的代码:

import urllib2
import mmap

# Open the file as a memory mapped string. Looks like a string, but 
# actually accesses the file behind the scenes. 
f = open('somelargefile.zip','rb')
mmapped_file_as_string = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

# Do the request
request = urllib2.Request(url, mmapped_file_as_string)
request.add_header("Content-Type", "application/zip")
response = urllib2.urlopen(request)

#close everything
mmapped_file_as_string.close()
f.close()

撰写回答