【问题标题】:Python: HTTP Post a large file with streamingPython:HTTP 发布带有流式传输的大文件
【发布时间】:2010-03-23 18:31:57
【问题描述】:

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

import urllib2

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

但是,这会在发布之前将整个文件的内容读入内存。如何让它将文件流式传输到服务器?

【问题讨论】:

标签: python http urllib2


【解决方案1】:

通读 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()

【讨论】:

  • 您能否确认以下行是正确的:request = urllib2.Request(url, mmapped_file_as_string)
【解决方案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,请查看 this 旧帖子。

它提出了两个选择:

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

【讨论】:

  • 我不想发送编码为“multipart/form-data”的文件。这似乎可以做到这一点。我只是在寻找原始帖子。
  • 在 python 2.7 选项 #2 上已经添加了补丁,块大小是 8192,我想知道为什么.. 嗯。这方面的规范/标准是什么?
【解决方案3】:

文档没有说你可以这样做,但是 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)

【讨论】:

  • urllib2.urlopen(url, data, headers) 不将标头作为参数,因此 response = urllib2.urlopen(url, data, headers) 行将不起作用。我在下面的answer 中提供了工作代码
  • 请求模块可以做到这一点吗?我必须分块发送文件(10 MB)但是不想读取内存中的所有 10MB 但想读取一些字节(8192)并发送到请求..直到我完成 10MB
【解决方案4】:

试试 pycurl。我没有任何设置可以接受在 multipart/form-data POST 中不是的大文件,但这里有一个简单的示例,可以根据需要读取文件。

import os
import pycurl

class FileReader:
    def __init__(self, fp):
        self.fp = fp
    def read_callback(self, size):
        return self.fp.read(size)

c = pycurl.Curl()
c.setopt(pycurl.URL, url)
c.setopt(pycurl.UPLOAD, 1)
c.setopt(pycurl.READFUNCTION, FileReader(open(filename, 'rb')).read_callback)
filesize = os.path.getsize(filename)
c.setopt(pycurl.INFILESIZE, filesize)
c.perform()
c.close()

【讨论】:

  • 感谢 JimB。我会用这个,除了我有几个 Windows 人在用这个,我不希望他们安装任何其他东西。
【解决方案5】:

使用requests 库你可以做到

with open('massive-body', 'rb') as f:
    requests.post('http://some.url/streamed', data=f)

如上所述here in their docs

【讨论】:

  • 8K 块大小仍然适用,因为 httplib.py, send() L#869 被调用。
【解决方案6】:

以下是 Python 2 / Python 3 的工作示例:

try:
    from urllib2 import urlopen, Request
except:
    from urllib.request import urlopen, Request

headers = { 'Content-length': str(os.path.getsize(filepath)) }
with open(filepath, 'rb') as f:
    req = Request(url, data=f, headers=headers)
    result = urlopen(req).read().decode()

请求模块很棒,但有时您无法安装任何额外的模块...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-18
    • 1970-01-01
    • 2012-12-08
    • 2016-10-20
    • 2010-09-08
    相关资源
    最近更新 更多