如何批量发送包含多个网址的多部分html帖子?

use*_*419 4 python multipartform-data python-requests gmail-api

我正在与gmail api交谈,并希望批量处理请求.他们有一个友好的指南,https://developers.google.com/gmail/api/guides/batch,这表明我应该能够使用multipart/mixed并包含不同的网址.

我正在使用Python和Requests库,但我不确定如何发布不同的URL.像这样的答案如何在python中发送带有请求的"multipart/form-data"?不要提及更改该部分的选项.

我该怎么做呢?

Tim*_*o D 10

不幸的是,请求不支持在其API中使用multipart/mixed.这已在几个GitHub问题(#935#1081)中提出,但目前尚无此更新.如果您在请求源中搜索"混合" 并获得零结果,这也变得非常清楚:(

现在,您有多种选择,具体取决于您希望使用多少Python和第三方库.

Google API客户端

现在,这个问题最明显的答案是使用谷歌在这里提供的官方Python API .它附带了一个HttpBatchRequest类,可以处理您需要的批处理请求.本指南中详细介绍了这一点.

基本上,您创建一个HttpBatchRequest对象并将所有请求添加到它.然后图书馆将所有内容放在一起(取自上面的指南):

batch = BatchHttpRequest()
batch.add(service.animals().list(), callback=list_animals)
batch.add(service.farmers().list(), callback=list_farmers)
batch.execute(http=http)
Run Code Online (Sandbox Code Playgroud)

现在,如果由于某种原因你不能或不会使用官方谷歌库,你将不得不自己构建请求体的一部分.

要求+ email.mime

正如我已经提到的,请求没有正式支持multipart/mixed.但这并不意味着我们不能"强迫"它.创建Request对象时,我们可以使用该files参数来提供多部分数据.

files是一个接受此格式的4元组值的字典:(filename,file_object,content_type,headers).文件名可以为空.现在我们需要将Request对象转换为文件(类似)对象.我写了一个小方法,涵盖了Google示例中的基本示例.它部分受到Google在其Python库中使用的内部方法的启发:

import requests
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart

BASE_URL = 'http://www.googleapis.com/batch'

def serialize_request(request):
    '''Returns the string representation of the request'''
    mime_body = ''

    prepared = request.prepare()

    # write first line (method + uri)
    if request.url.startswith(BASE_URL):
        mime_body = '%s %s\r\n' % (request.method, request.url[len(BASE_URL):])
    else:
        mime_body = '%s %s\r\n' % (request.method, request.url)

    part = MIMENonMultipart('application', 'http')

    # write headers (if possible)
    for key, value in prepared.headers.iteritems():
        mime_body += '%s: %s\r\n' % (key, value)

    if getattr(prepared, 'body', None) is not None:
        mime_body += '\r\n' + prepared.body + '\r\n'

    return mime_body.encode('utf-8').lstrip()
Run Code Online (Sandbox Code Playgroud)

此方法将requests.Request对象转换为UTF-8编码的字符串,以后可以将其用作MIMENonMultipart对象的有效负载,即不同的多部分.

现在,为了生成实际的批处理请求,我们首先需要将(Google API)请求列表压缩到请求 lib 的files字典中.以下方法将获取对象列表,将每个对象转换为MIMENonMultipart,然后返回符合字典结构的字典:requests.Requestfiles

import uuid

def prepare_requests(request_list):
    message = MIMEMultipart('mixed')
    output = {}

    # thanks, Google. (Prevents the writing of MIME headers we dont need)
    setattr(message, '_write_headers', lambda self: None)

    for request in request_list:
        message_id = new_id()
        sub_message = MIMENonMultipart('application', 'http')
        sub_message['Content-ID'] = message_id
        del sub_message['MIME-Version']

        sub_message.set_payload(serialize_request(request))

        # remove first line (from ...)
        sub_message = str(sub_message)
        sub_message = sub_message[sub_message.find('\n'):]

        output[message_id] = ('', str(sub_message), 'application/http', {})

    return output

def new_id():
    # I am not sure how these work exactly, so you will have to adapt this code
    return '<item%s:12930812@barnyard.example.com>' % str(uuid.uuid4())[-4:]
Run Code Online (Sandbox Code Playgroud)

最后,我们需要将Content-Type从multipart/form-data更改为multipart/mixed,并从每个请求部分中删除Content-Disposition和Content-Type标头.这些是我们通过请求生成的,不能被files字典覆盖.

import re

def finalize_request(prepared):
    # change to multipart/mixed
    old = prepared.headers['Content-Type']
    prepared.headers['Content-Type'] = old.replace('multipart/form-data', 'multipart/mixed')

    # remove headers at the start of each boundary
    prepared.body = re.sub(r'\r\nContent-Disposition: form-data; name=.+\r\nContent-Type: application/http\r\n', '', prepared.body)
Run Code Online (Sandbox Code Playgroud)

我已尽力使用批处理指南中的Google示例对此进行测试:

sheep = {
  "animalName": "sheep",
  "animalAge": "5",
  "peltColor": "green"
}

commands = []
commands.append(requests.Request('GET', 'http://www.googleapis.com/batch/farm/v1/animals/pony'))
commands.append(requests.Request('PUT', 'http://www.googleapis.com/batch/farm/v1/animals/sheep', json=sheep, headers={'If-Match': '"etag/sheep"'}))
commands.append(requests.Request('GET', 'http://www.googleapis.com/batch/farm/v1/animals', headers={'If-None-Match': '"etag/animals"'}))

files = prepare_requests(commands)

r = requests.Request('POST', 'http://www.googleapis.com/batch', files=files)
prepared = r.prepare()

finalize_request(prepared)

s = requests.Session()
s.send(prepared)
Run Code Online (Sandbox Code Playgroud)

最终的请求应该与Google在其"配料"指南中提供的内容足够接近:

POST http://www.googleapis.com/batch
Content-Length: 1006
Content-Type: multipart/mixed; boundary=a21beebd15b74be89539b137bbbc7293

--a21beebd15b74be89539b137bbbc7293

Content-Type: application/http
Content-ID: <item8065:12930812@barnyard.example.com>

GET /farm/v1/animals
If-None-Match: "etag/animals"

--a21beebd15b74be89539b137bbbc7293

Content-Type: application/http
Content-ID: <item5158:12930812@barnyard.example.com>

GET /farm/v1/animals/pony

--a21beebd15b74be89539b137bbbc7293

Content-Type: application/http
Content-ID: <item0ec9:12930812@barnyard.example.com>

PUT /farm/v1/animals/sheep
Content-Length: 63
Content-Type: application/json
If-Match: "etag/sheep"

{"animalAge": "5", "animalName": "sheep", "peltColor": "green"}

--a21beebd15b74be89539b137bbbc7293--
Run Code Online (Sandbox Code Playgroud)

最后,我强烈推荐官方谷歌库,但如果你不能使用它,你将不得不即兴发挥:)

免责声明:我实际上并没有尝试将此请求发送到Google API端点,因为身份验证过程太麻烦了.我只是想尽可能接近批处理指南中描述的HTTP请求.\ r和\n行结尾可能存在一些问题,具体取决于Google终端的严格程度.

资料来源: