Làm cách nào để gửi một dữ liệu đa dữ liệu / biểu mẫu dữ liệu với các yêu cầu trong python?


211

Làm thế nào để gửi một multipart/form-datayêu cầu trong python? Làm thế nào để gửi một tập tin, tôi hiểu, nhưng làm thế nào để gửi dữ liệu biểu mẫu bằng phương pháp này không thể hiểu.


Câu hỏi của bạn không thực sự rõ ràng. Bạn muốn đạt được những gì? Bạn có muốn gửi "nhiều dữ liệu / biểu mẫu dữ liệu" mà không cần tải lên tệp trong biểu mẫu không?
Hans Sau đó,

4
Thực tế là filestham số được sử dụng để làm cả hai là một API rất xấu. Tôi đã nêu vấn đề có tiêu đề Gửi dữ liệu nhiều phần - chúng tôi cần API tốt hơn để khắc phục điều này. Nếu bạn đồng ý rằng việc sử dụng filestham số để gửi dữ liệu mulitpart là sai lệch nhất, vui lòng yêu cầu thay đổi API trong vấn đề trên.
Piotr Dobrogost

@PiotrDobrogost vấn đề đó đã bị đóng. Không khuyến khích mọi người bình luận về các vấn đề đóng, có liên quan hoặc khác.
Ian Stapleton Cordasco

1
Nevermind, tôi chỉ nhận ra bình luận của bạn đã được đăng trước khi nó được đóng lại. Tôi ghét cách StackOverflow không giữ mọi thứ theo thứ tự thời gian.
Ian Stapleton Cordasco

Câu trả lời:


166

Về cơ bản, nếu bạn chỉ định một filestham số (từ điển), thì requestssẽ gửi multipart/form-dataPOST thay vì application/x-www-form-urlencodedPOST. Tuy nhiên, bạn không bị giới hạn trong việc sử dụng các tệp thực tế trong từ điển đó:

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

và httpbin.org cho bạn biết những tiêu đề bạn đã đăng; trong response.json()chúng ta có:

>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
 'Accept-Encoding': 'gzip, deflate',
 'Connection': 'close',
 'Content-Length': '141',
 'Content-Type': 'multipart/form-data; '
                 'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
 'Host': 'httpbin.org',
 'User-Agent': 'python-requests/2.21.0'}

Tốt hơn hết, bạn có thể kiểm soát thêm tên tệp, loại nội dung và các tiêu đề bổ sung cho từng phần bằng cách sử dụng bộ dữ liệu thay vì một đối tượng chuỗi hoặc byte đơn. Bộ dữ liệu dự kiến ​​sẽ chứa từ 2 đến 4 yếu tố; tên tệp, nội dung, tùy chọn một loại nội dung và từ điển tùy chọn của các tiêu đề khác.

Tôi sẽ sử dụng biểu mẫu tuple với Nonetên tệp, để filename="..."tham số được loại bỏ khỏi yêu cầu cho các phần đó:

>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"

bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"

bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--

files cũng có thể là danh sách các bộ dữ liệu hai giá trị, nếu bạn cần đặt hàng và / hoặc nhiều trường có cùng tên:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

Nếu bạn chỉ định cả hai filesdata, thì nó phụ thuộc vào giá trị của datanhững gì sẽ được sử dụng để tạo phần thân POST. Nếu datalà một chuỗi, chỉ có nó sẽ được sử dụng; mặt khác cả hai datafilesđược sử dụng, với các yếu tố datađược liệt kê đầu tiên.

Ngoài ra còn có requests-toolbeltdự án tuyệt vời , bao gồm hỗ trợ Multipart tiên tiến . Nó nhận các định nghĩa trường có cùng định dạng với filestham số, nhưng không giống như requests, nó mặc định không đặt tham số tên tệp. Ngoài ra, nó có thể truyền yêu cầu từ các đối tượng tệp đang mở, nơi requestsđầu tiên sẽ xây dựng phần thân yêu cầu trong bộ nhớ:

from requests_toolbelt.multipart.encoder import MultipartEncoder

mp_encoder = MultipartEncoder(
    fields={
        'foo': 'bar',
        # plain file object, no filename or mime type produces a
        # Content-Disposition header with just the part name
        'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
    }
)
r = requests.post(
    'http://httpbin.org/post',
    data=mp_encoder,  # The MultipartEncoder is posted as data, don't use files=...!
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={'Content-Type': mp_encoder.content_type}
)

Các lĩnh vực tuân theo các quy ước tương tự; sử dụng một tuple có từ 2 đến 4 phần tử để thêm tên tệp, phần mime-type hoặc các tiêu đề phụ. Không giống như filestham số, không có nỗ lực nào được thực hiện để tìm filenamegiá trị mặc định nếu bạn không sử dụng bộ dữ liệu.


3
Nếu tệp = {} được sử dụng thì tiêu đề = {'Loại nội dung': 'blah blah'} không được sử dụng!
zaki

5
@zaki: thực sự, vì multipart/form-datanội dung Kiểu nội dung phải bao gồm giá trị biên được sử dụng để phân định các phần trong phần thân bài. Không đặt Content-Typetiêu đề đảm bảo requestsđặt nó thành giá trị chính xác.
Martijn Pieters

Lưu ý quan trọng: yêu cầu sẽ chỉ được gửi như multipart/form-datathể giá trị files=là trung thực, vì vậy nếu bạn cần gửi multipart/form-datayêu cầu nhưng không bao gồm bất kỳ tệp nào, bạn có thể đặt giá trị trung thực nhưng vô nghĩa như {'':''}, và đặt data=với thân yêu cầu của bạn. Nếu bạn đang làm điều này, đừng tự cung cấp Content-Typetiêu đề; requestssẽ thiết lập nó cho bạn. Bạn có thể xem kiểm tra sự thật tại đây: github.com/psf/requests/blob/
Kẻ

@DanielSitunayake không cần hack như vậy. Chỉ cần đặt tất cả các trường trong filesdict, chúng không phải là tệp (chỉ cần đảm bảo sử dụng biểu mẫu tuple và đặt tên tệp thành None). Vẫn tốt hơn, sử dụng requests_toolbeltdự án.
Martijn Pieters

Cảm ơn @MartijnPieters, thủ thuật với hình thức tuple là tuyệt vời! Sẽ cho nó một thử.
Daniel Situnayake

107

Vì các câu trả lời trước được viết, các yêu cầu đã thay đổi. Hãy xem chủ đề lỗi tại Github để biết thêm chi tiết và nhận xét này cho một ví dụ.

Nói tóm lại, tham số tệp lấy một dictkhóa với tên của trường biểu mẫu và giá trị là một chuỗi hoặc một tuple 2, 3 hoặc 4 chiều dài, như được mô tả trong phần POST một tệp được mã hóa đa phần trong các yêu cầu bắt đầu nhanh:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

Trong phần trên, bộ dữ liệu được cấu tạo như sau:

(filename, data, content_type, headers)

Nếu giá trị chỉ là một chuỗi, tên tệp sẽ giống với khóa, như sau:

>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}

Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52

Nếu giá trị là một tuple và mục đầu tiên là Nonethuộc tính tên tệp sẽ không được bao gồm:

>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}

Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52

2
Điều gì xảy ra nếu bạn cần phân biệt namefilenamecũng có nhiều trường có cùng tên?
Michael

1
Tôi có một vấn đề về simillar là @Michael. Bạn có thể có một cái nhìn vào câu hỏi và đề nghị một cái gì đó? [link] ( stackoverflow.com/questions/30683352/
Khăn

ai đó đã giải quyết vấn đề này với việc có nhiều lĩnh vực có cùng tên?
dùng31 31037

1
Thủ thuật để vượt qua chuỗi rỗng vì giá trị đầu tiên của filesbộ dữ liệu không còn hoạt động nữa: requests.post datathay vào đó, bạn cần sử dụng tham số để gửi multipart/form-datatham số không phải tệp bổ sung
Lucas Cimon

1
Vượt qua Nonethay vì một chuỗi trống dường như hoạt động
Alexandre Blin

73

Bạn cần sử dụng filestham số để gửi yêu cầu POST dạng nhiều phần ngay cả khi bạn không cần tải lên bất kỳ tệp nào.

Từ nguồn yêu cầu ban đầu :

def request(method, url, **kwargs):
    """Constructs and sends a :class:`Request <Request>`.

    ...
    :param files: (optional) Dictionary of ``'name': file-like-objects``
        (or ``{'name': file-tuple}``) for multipart encoding upload.
        ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``,
        3-tuple ``('filename', fileobj, 'content_type')``
        or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``,
        where ``'content-type'`` is a string
        defining the content type of the given file
        and ``custom_headers`` a dict-like object 
        containing additional headers to add for the file.

Phần có liên quan là: file-tuple can be a2-tuple, .3-tupleor a4-tuple

Dựa trên những điều trên, yêu cầu biểu mẫu nhiều đơn giản nhất bao gồm cả hai tệp để tải lên và các trường biểu mẫu sẽ trông như thế này:

multipart_form_data = {
    'file2': ('custom_file_name.zip', open('myfile.zip', 'rb')),
    'action': (None, 'store'),
    'path': (None, '/path1')
}

response = requests.post('https://httpbin.org/post', files=multipart_form_data)

print(response.content)

Lưu ý Nonenhư là đối số đầu tiên trong tuple cho các lĩnh vực văn bản đơn giản - đây là một giữ chỗ cho lĩnh vực tên tập tin đó chỉ được sử dụng cho những lần tải tập tin, nhưng đối với các lĩnh vực văn bản đi qua Nonenhư là tham số đầu tiên là cần thiết để cho các dữ liệu được gửi .

Nhiều trường có cùng tên

Nếu bạn cần đăng nhiều trường có cùng tên thì thay vì từ điển, bạn có thể xác định tải trọng của mình dưới dạng danh sách (hoặc một tuple) của các bộ dữ liệu:

multipart_form_data = (
    ('file2', ('custom_file_name.zip', open('myfile.zip', 'rb'))),
    ('action', (None, 'store')),
    ('path', (None, '/path1')),
    ('path', (None, '/path2')),
    ('path', (None, '/path3')),
)

Truyền yêu cầu API

Nếu API ở trên không đủ pythonic cho bạn, thì hãy xem xét sử dụng request requestbelt ( pip install requests_toolbelt) là phần mở rộng của mô-đun yêu cầu cốt lõi cung cấp hỗ trợ cho phát trực tuyến tệp cũng như MultipartEncoder có thể được sử dụng thay thế filesvà cũng cho phép bạn xác định tải trọng là một từ điển, tuple hoặc danh sách.

MultipartEncodercó thể được sử dụng cả cho các yêu cầu nhiều phần có hoặc không có các trường tải lên thực tế. Nó phải được gán cho datatham số.

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder

multipart_data = MultipartEncoder(
    fields={
            # a file upload field
            'file': ('file.zip', open('file.zip', 'rb'), 'text/plain')
            # plain text fields
            'field0': 'value0', 
            'field1': 'value1',
           }
    )

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

Nếu bạn cần gửi nhiều trường có cùng tên hoặc nếu thứ tự của các trường biểu mẫu là quan trọng, thì có thể sử dụng một tuple hoặc danh sách thay vì từ điển:

multipart_data = MultipartEncoder(
    fields=(
            ('action', 'ingest'), 
            ('item', 'spam'),
            ('item', 'sausage'),
            ('item', 'eggs'),
           )
    )

Cảm ơn vì điều này. Thứ tự các phím là quan trọng đối với tôi và điều này đã giúp rất nhiều.
Lộng lẫy

Kinh ngạc. Không thể giải thích, một api tôi đang làm việc yêu cầu 2 giá trị khác nhau cho cùng một khóa. Thật đáng kinh ngạc. Cảm ơn bạn.
ajon

@ccpizza, dòng này thực sự có nghĩa là gì? > "('File.py', open ('file.py', 'rb'), 'text / plain')". Nó không hoạt động đối với tôi :(
Denis Koreyba

@DenisKoreyba: đây là một ví dụ về trường tải lên tệp giả định rằng tệp có tên file.pynằm trong cùng thư mục với tập lệnh của bạn.
ccpizza

1
Bạn có thể sử dụng Nonethay vì chuỗi rỗng. Sau đó, các yêu cầu sẽ không bao gồm tên tệp. Vì vậy, thay vì Content-Disposition: form-data; name="action"; filename=""nó sẽ được Content-Disposition: form-data; name="action". Điều này rất quan trọng đối với tôi khi máy chủ chấp nhận các trường đó dưới dạng trường biểu mẫu và không phải là tệp.
Mitar

8

Đây là đoạn mã đơn giản để tải lên một tệp với các tham số bổ sung bằng các yêu cầu:

url = 'https://<file_upload_url>'
fp = '/Users/jainik/Desktop/data.csv'

files = {'file': open(fp, 'rb')}
payload = {'file_id': '1234'}

response = requests.put(url, files=files, data=payload, verify=False)

Xin lưu ý rằng bạn không cần chỉ định rõ ràng bất kỳ loại nội dung nào.

LƯU Ý: Muốn bình luận về một trong những câu trả lời trên nhưng không thể vì danh tiếng thấp nên đã phác thảo một phản hồi mới ở đây.


4

Bạn cần sử dụng namethuộc tính của tệp tải lên trong HTML của trang web. Thí dụ:

autocomplete="off" name="image">

Bạn thấy name="image">sao? Bạn có thể tìm thấy nó trong HTML của một trang web để tải tệp lên. Bạn cần sử dụng nó để tải lên tập tin vớiMultipart/form-data

kịch bản:

import requests

site = 'https://prnt.sc/upload.php' # the site where you upload the file
filename = 'image.jpg'  # name example

Ở đây, ở vị trí của hình ảnh, thêm tên của tệp tải lên trong HTML

up = {'image':(filename, open(filename, 'rb'), "multipart/form-data")}

Nếu tải lên yêu cầu nhấp vào nút để tải lên, bạn có thể sử dụng như thế:

data = {
     "Button" : "Submit",
}

Sau đó bắt đầu yêu cầu

request = requests.post(site, files=up, data=data)

Và xong, tập tin tải lên thành công


3

Gửi khóa và giá trị nhiều dữ liệu / biểu mẫu

lệnh curl:

curl -X PUT http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F taskStatus=1

yêu cầu python - Yêu cầu POST phức tạp hơn :

    updateTaskUrl = "http://127.0.0.1:8080/api/xxx"
    updateInfoDict = {
        "taskStatus": 1,
    }
    resp = requests.put(updateTaskUrl, data=updateInfoDict)

Gửi tệp nhiều dữ liệu / biểu mẫu

lệnh curl:

curl -X POST http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F file=@/Users/xxx.txt

yêu cầu python - POST một tệp được mã hóa nhiều phần :

    filePath = "/Users/xxx.txt"
    fileFp = open(filePath, 'rb')
    fileInfoDict = {
        "file": fileFp,
    }
    resp = requests.post(uploadResultUrl, files=fileInfoDict)

đó là tất cả.


-1

Đây là đoạn mã trăn bạn cần để tải lên một tệp lớn dưới dạng nhiều biểu mẫu. Với phần mềm trung gian NodeJs Multer chạy ở phía máy chủ.

import requests
latest_file = 'path/to/file'
url = "http://httpbin.org/apiToUpload"
files = {'fieldName': open(latest_file, 'rb')}
r = requests.put(url, files=files)

Đối với phía máy chủ, vui lòng kiểm tra tài liệu multer tại: https://github.com/expressjs/multer ở đây, trường đơn ('fieldName') được sử dụng để chấp nhận một tệp duy nhất, như trong:

var upload = multer().single('fieldName');
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.