上传数据

流式多部分数据编码器

Requests 支持多部分上传,但 API 意味着使用该功能构建所需的多部分上传可能很困难或不可能。此外,在使用 Requests 的多部分上传功能时,所有数据都必须先读入内存,然后再发送到服务器。在极端情况下,这可能导致无法将文件作为 multipart/form-data 上传的一部分发送。

该工具包包含一个类,允许你以所需的格式构建多部分请求体,并避免将文件读入内存。以下是如何使用它的示例

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder

m = MultipartEncoder(
    fields={'field0': 'value', 'field1': 'value',
            'field2': ('filename', open('file.py', 'rb'), 'text/plain')}
    )

r = requests.post('http://httpbin.org/post', data=m,
                  headers={'Content-Type': m.content_type})

MultipartEncoder 也有 .to_string() 便利方法。此方法将多部分主体呈现为字符串。在开发代码时,这很有用,因为它允许你在发送多部分主体之前确认其具有你预期的格式。

该工具包还提供了一种使用 MultipartEncoderMonitor 监视流式上传的方法。

class requests_toolbelt.multipart.encoder.MultipartEncoder(fields, boundary=None, encoding='utf-8')

MultipartEncoder 对象是将为你创建 multipart/form-data 主体的引擎的通用接口。

基本用法是

import requests
from requests_toolbelt import MultipartEncoder

encoder = MultipartEncoder({'field': 'value',
                            'other_field': 'other_value'})
r = requests.post('https://httpbin.org/post', data=encoder,
                  headers={'Content-Type': encoder.content_type})

如果你不需要利用流式传输发布主体,你还可以执行以下操作

r = requests.post('https://httpbin.org/post',
                  data=encoder.to_string(),
                  headers={'Content-Type': encoder.content_type})

如果你希望编码器使用特定顺序,你可以使用 OrderedDict 或更简单地使用元组列表

encoder = MultipartEncoder([('field', 'value'),
                            ('other_field', 'other_value')])

在版本 0.4.0 中更改。

你还可以提供元组作为部分值,就像你将它们提供给 requests 的 files 参数一样。

encoder = MultipartEncoder({
    'field': ('file_name', b'{"a": "b"}', 'application/json',
              {'X-My-Header': 'my-value'})
])

警告

此对象最终将直接进入 httplib。目前,httplib 的硬编码读取大小为 8192 字节。这意味着它将循环,直到文件被读取,并且你的上传可能需要一段时间。这不是 requests 中的错误。正在考虑为该对象添加一项功能,允许你(用户)指定读取时应返回的大小。如果你对此有意见,请在此问题中权衡。

监控流式多部分上传

如果您需要流式传输 multipart/form-data 上传,那么您可能处于上传内容可能需要一段时间的情况。在这些情况下,能够监控上传进度可能很有意义。出于此原因,工具带提供了 MultipartEncoderMonitor。该监视器将 MultipartEncoder 的实例包装起来,并且与编码器完全相同。它提供了一个类似的 API,并增加了一些内容

  • 该监视器接受一个函数作为回调。每次 requests 在监视器上调用 read 并将监视器作为参数传入时,都会调用该函数。

  • 该监视器会跟踪在上传过程中已读取的字节数。

您可以使用该监视器为上传创建进度条。这是一个 使用 clint 的示例,它显示了进度条。

要使用该监视器,您需要遵循以下模式

import requests
from requests_toolbelt.multipart import encoder

def my_callback(monitor):
    # Your callback function
    pass

e = encoder.MultipartEncoder(
    fields={'field0': 'value', 'field1': 'value',
            'field2': ('filename', open('file.py', 'rb'), 'text/plain')}
    )
m = encoder.MultipartEncoderMonitor(e, my_callback)

r = requests.post('http://httpbin.org/post', data=m,
                  headers={'Content-Type': m.content_type})

如果您有一个非常简单的用例,您还可以执行以下操作

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoderMonitor

def my_callback(monitor):
    # Your callback function
    pass

m = MultipartEncoderMonitor.from_fields(
    fields={'field0': 'value', 'field1': 'value',
            'field2': ('filename', open('file.py', 'rb'), 'text/plain')},
    callback=my_callback
    )

r = requests.post('http://httpbin.org/post', data=m,
                  headers={'Content-Type': m.content_type})
class requests_toolbelt.multipart.encoder.MultipartEncoderMonitor(encoder, callback=None)

用于监控 MultipartEncoder 进度的一个对象。

MultipartEncoder 应仅负责准备和流式传输数据。对于希望监控它的人来说,他们不应该使用该实例来管理它。使用此类,他们可以监控编码器并注册回调。该回调接收监视器的实例。

要使用此监视器,请按照正常方式构造 MultipartEncoder

from requests_toolbelt import (MultipartEncoder,
                               MultipartEncoderMonitor)
import requests

def callback(monitor):
    # Do something with this information
    pass

m = MultipartEncoder(fields={'field0': 'value0'})
monitor = MultipartEncoderMonitor(m, callback)
headers = {'Content-Type': monitor.content_type}
r = requests.post('https://httpbin.org/post', data=monitor,
                  headers=headers)

或者,如果您的用例非常简单,您可以使用以下模式。

from requests_toolbelt import MultipartEncoderMonitor
import requests

def callback(monitor):
    # Do something with this information
    pass

monitor = MultipartEncoderMonitor.from_fields(
    fields={'field0': 'value0'}, callback
    )
headers = {'Content-Type': montior.content_type}
r = requests.post('https://httpbin.org/post', data=monitor,
                  headers=headers)

从生成器流式传输数据

在某些情况下,您(用户)拥有大量数据的生成器,并且您已经知道该数据的规模。如果您通过 data 参数将生成器传递给 requests,则 requests 将假定您希望分块上传数据,并将 Transfer-Encoding 标头值设置为 chunked。通常,这会导致服务器行为不佳。如果您想避免这种情况,可以使用 StreamingIterator。您将数据大小和生成器传递给它。

import requests
from requests_toolbelt.streaming_iterator import StreamingIterator

generator = some_function()  # Create your generator
size = some_function_size()  # Get your generator's size
content_type = content_type()  # Get the content-type of the data

streamer = StreamingIterator(size, generator)
r = requests.post('https://httpbin.org/post', data=streamer,
                  headers={'Content-Type': content_type})

流媒体会为您处理生成器,并在将数据传递给 requests 之前对其进行缓冲。

在 0.4.0 版本中更改:现在可以传递类似文件的对象,而不是生成器。

例如,如果您需要上传通过管道输入到标准输入中的数据,您可能会执行以下操作

import requests
import sys

r = requests.post(url, data=sys.stdin)

这将对数据进行流式处理,但会使用分块传输编码。如果您知道要发送到 stdin 的数据的长度,并且希望防止数据分块上传,则可以使用 StreamingIterator 对文件内容进行流式处理,而不依赖于分块。

import requests
from requests_toolbelt.streaming_iterator import StreamingIterator
import sys

stream = StreamingIterator(size, sys.stdin)
r = requests.post(url, data=stream,
                  headers={'Content-Type': content_type})
class requests_toolbelt.streaming_iterator.StreamingIterator(size, iterator, encoding='utf-8')

此类提供了一种方法,允许对具有已知大小的迭代器进行流式处理,而不是分块。

在 requests 中,如果您传入一个迭代器,它会假定您希望使用分块传输编码来上传数据,而并非所有服务器都很好地支持这种编码。此外,您可能希望自己设置内容长度以避免这种情况,但这不起作用。预先阻止 requests 使用分块传输编码并强制其对上传进行流式处理的唯一方法是模仿一个非常具体的接口。您无需了解这些详细信息,只需使用此类即可。您只需提供大小和迭代器,然后通过 data 参数将 StreamingIterator 的实例传递给 requests,如下所示

from requests_toolbelt import StreamingIterator

import requests

# Let iterator be some generator that you already have and size be
# the size of the data produced by the iterator

r = requests.post(url, data=StreamingIterator(size, iterator))

您还可以将类似文件的对象传递给 StreamingIterator,以防 requests 无法自行确定文件大小。对于流式文件对象(如 stdin 或任何套接字),情况就是如此。没有必要用 StreamingIterator 包装磁盘上的文件,因为 requests 可以自行确定文件大小。

当然,您还应该适当地设置上传的 Content-Type,因为工具带不会尝试为您猜测该类型。