จะส่ง“ หลายส่วน / ฟอร์มข้อมูล” พร้อมคำขอใน python ได้อย่างไร


230

จะส่งmultipart/form-dataคำขอใน python ได้อย่างไร? วิธีการส่งไฟล์ฉันเข้าใจ แต่วิธีการส่งข้อมูลโดยวิธีนี้ไม่สามารถเข้าใจได้


คำถามของคุณยังไม่ชัดเจน คุณต้องการบรรลุอะไร คุณต้องการส่ง "หลายส่วน / ฟอร์มข้อมูล" โดยไม่ต้องอัปโหลดไฟล์ในแบบฟอร์มหรือไม่?
Hans Then

4
ความจริงที่ว่าfilesพารามิเตอร์ที่ใช้ทำทั้งสองอย่างนั้นเป็น API ที่แย่มาก ฉันยกปัญหาเรื่องการส่งข้อมูลหลายส่วน - เราต้องการ API ที่ดีกว่าเพื่อแก้ไขปัญหานี้ หากคุณยอมรับว่าการใช้filesพารามิเตอร์เพื่อส่งข้อมูลหลายส่วนนั้นทำให้เข้าใจผิดได้ดีที่สุดโปรดขอให้เปลี่ยน API ในปัญหาข้างต้น
Piotr Dobrogost

@PiotrDobrogost ปัญหานั้นถูกปิด อย่าสนับสนุนให้ผู้คนแสดงความคิดเห็นในประเด็นที่ปิดไม่เกี่ยวข้องหรืออื่น ๆ
Ian Stapleton Cordasco

1
ไม่เป็นไรฉันเพิ่งรู้ว่าความคิดเห็นของคุณถูกโพสต์ก่อนที่จะปิด ฉันเกลียดที่ StackOverflow ไม่เก็บสิ่งต่างๆตามลำดับเวลา
Ian Stapleton Cordasco

ตรวจสอบคำตอบนี้stackoverflow.com/a/64586578/8826047ขอบเขตมีความสำคัญ!
Sona Pochybova

คำตอบ:


197

โดยทั่วไปหากคุณระบุfilesพารามิเตอร์ (พจนานุกรม) ระบบrequestsจะส่งmultipart/form-dataPOST แทนapplication/x-www-form-urlencodedPOST คุณไม่ได้ จำกัด เฉพาะการใช้ไฟล์จริงในพจนานุกรมนั้นอย่างไรก็ตาม:

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

และ httpbin.org ช่วยให้คุณทราบว่าคุณโพสต์ด้วยส่วนหัวใด ในresponse.json()เรามี:

>>> 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'}

ยังดีกว่าคุณสามารถควบคุมชื่อไฟล์ประเภทเนื้อหาและส่วนหัวเพิ่มเติมสำหรับแต่ละส่วนได้โดยใช้ทูเพิลแทนสตริงเดี่ยวหรือไบต์ออบเจ็กต์ ทูเพิลคาดว่าจะมีระหว่าง 2 ถึง 4 องค์ประกอบ ชื่อไฟล์เนื้อหาเป็นทางเลือกประเภทเนื้อหาและพจนานุกรมทางเลือกของส่วนหัวเพิ่มเติม

ฉันจะใช้รูปแบบทูเพิลNoneเป็นชื่อไฟล์เพื่อให้filename="..."พารามิเตอร์หลุดจากคำขอสำหรับส่วนเหล่านั้น:

>>> 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 ยังสามารถเป็นรายการสิ่งที่สองค่าได้หากคุณต้องการสั่งซื้อและ / หรือหลายช่องที่มีชื่อเดียวกัน:

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

หากคุณระบุทั้งสองอย่างfilesและdataขึ้นอยู่กับค่าของdataสิ่งที่จะใช้ในการสร้างเนื้อหา POST ถ้าdataเป็นสตริงจะใช้เฉพาะ willl เท่านั้น มิฉะนั้นทั้งสองdataและfilesมีการใช้กับองค์ประกอบในdataรายการแรก

นอกจากนี้ยังมียอดเยี่ยมrequests-toolbeltโครงการซึ่งรวมถึงการสนับสนุน Multipart ขั้นสูง ใช้นิยามฟิลด์ในรูปแบบเดียวกับfilesพารามิเตอร์ แต่ต่างจากrequestsค่าเริ่มต้นที่จะไม่ตั้งค่าพารามิเตอร์ชื่อไฟล์ นอกจากนี้ยังสามารถสตรีมคำขอจากอ็อบเจ็กต์ไฟล์ที่เปิดซึ่งrequestsจะสร้างเนื้อหาคำขอในหน่วยความจำก่อน:

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}
)

เขตข้อมูลเป็นไปตามอนุสัญญาเดียวกัน ใช้ทูเพิลที่มีองค์ประกอบระหว่าง 2 ถึง 4 เพื่อเพิ่มชื่อไฟล์ประเภทละครใบ้หรือส่วนหัวพิเศษ ไม่เหมือนกับfilesพารามิเตอร์ไม่มีความพยายามในการค้นหาค่าเริ่มต้นfilenameหากคุณไม่ใช้ทูเปิล


3
หากใช้ files = {} จะต้องไม่ใช้ headers = {'Content-Type': 'blah blah'}!
zaki

5
@zaki: จริงๆแล้วเนื่องจากmultipart/form-dataContent-Type ต้องมีค่าขอบเขตที่ใช้ในการแบ่งส่วนต่างๆในเนื้อหาของโพสต์ การไม่ตั้งค่าContent-Typeส่วนหัวทำให้แน่ใจว่าrequestsตั้งค่าเป็นค่าที่ถูกต้อง
Martijn Pieters

หมายเหตุสำคัญ: คำขอจะถูกส่งราวกับmultipart/form-dataว่าค่าfiles=เป็นจริงเท่านั้นดังนั้นหากคุณต้องการส่งmultipart/form-dataคำขอ แต่ไม่รวมไฟล์ใด ๆ คุณสามารถตั้งค่าที่แท้จริง แต่ไม่มีความหมายเช่น{'':''}และตั้งค่าdata=ด้วยเนื้อหาคำขอของคุณ หากคุณกำลังทำสิ่งนี้อย่าระบุContent-Typeส่วนหัวด้วยตัวคุณเอง requestsจะตั้งค่าให้คุณ สามารถดูความจริงได้ที่นี่: github.com/psf/requests/blob/…
Daniel Situnayake

@DanielSitunayake ไม่จำเป็นต้องแฮ็คแบบนี้ เพียงใส่ฟิลด์ทั้งหมดในfilesdict โดยไม่จำเป็นต้องเป็นไฟล์ (เพียงตรวจสอบให้แน่ใจว่าใช้แบบฟอร์มทูเพิลและตั้งชื่อไฟล์เป็นNone) ยังดีกว่าใช้requests_toolbeltโครงการ
Martijn Pieters

ขอบคุณ @MartijnPieters เคล็ดลับในรูปแบบทูเพิลนั้นยอดเยี่ยมมาก! จะให้ลองดู
Daniel Situnayake

114

เนื่องจากคำตอบก่อนหน้านี้ถูกเขียนคำขอจึงเปลี่ยนไป ดูหัวข้อข้อบกพร่องที่ Githubสำหรับรายละเอียดเพิ่มเติมและความคิดเห็นนี้เป็นตัวอย่าง

กล่าวโดยย่อพารามิเตอร์ files ใช้dictคีย์เป็นชื่อของฟิลด์ฟอร์มและค่าจะเป็นสตริงหรือทูเพิลความยาว 2, 3 หรือ 4 ตามที่อธิบายไว้ในส่วนการโพสต์ไฟล์ที่เข้ารหัสหลายส่วนในคำร้องขอ เริ่มต้นอย่างรวดเร็ว:

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

ในข้างต้นทูเปิลประกอบด้วยดังนี้:

(filename, data, content_type, headers)

หากค่าเป็นเพียงสตริงชื่อไฟล์จะเหมือนกับคีย์ดังต่อไปนี้:

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

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

72c2b6f406cdabd578c5fd7598557c52

หากค่าเป็นทูเปิลและรายการแรกคือNoneคุณสมบัติชื่อไฟล์จะไม่รวม:

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

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

72c2b6f406cdabd578c5fd7598557c52

2
จะเกิดอะไรขึ้นถ้าคุณต้องการแยกความแตกต่างnameและfilenameแต่มีหลายเขตข้อมูลที่มีชื่อเดียวกัน?
Michael

1
ฉันมีปัญหาเกี่ยวกับซิมลาร์เป็น @Michael คุณสามารถดูคำถามและแนะนำบางสิ่งได้หรือไม่? [ลิงค์] ( stackoverflow.com/questions/30683352/… )
Shaardool

มีใครแก้ปัญหานี้ด้วยการมีหลายช่องที่มีชื่อเดียวกันหรือไม่
user3131037

1
เคล็ดลับที่จะผ่าน en สตริงที่ว่างเปล่าเป็นค่าแรกของfilestuple ไม่ทำงานอีกต่อไป: คุณจำเป็นต้องใช้requests.post dataพารามิเตอร์แทนการส่งไฟล์ที่ไม่ใช่ additionnal multipart/form-dataพารามิเตอร์
ลูคัส Cimon

1
การส่งNoneแทนสตริงว่างดูเหมือนจะใช้ได้
Alexandre Blin

81

คุณต้องใช้filesพารามิเตอร์เพื่อส่งคำขอ POST ในรูปแบบหลายส่วนแม้ว่าคุณจะไม่จำเป็นต้องอัปโหลดไฟล์ใด ๆ

จากแหล่งที่มาของคำขอดั้งเดิม:

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.

ส่วนที่เกี่ยวข้องคือ: file-tuple can be a2-tuple, .3-tupleor a4-tuple

จากข้างต้นคำขอแบบฟอร์มหลายส่วนที่ง่ายที่สุดที่มีทั้งไฟล์ที่จะอัปโหลดและฟิลด์แบบฟอร์มจะมีลักษณะดังนี้

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)

สังเกตNoneว่าเป็นอาร์กิวเมนต์แรกใน tuple สำหรับฟิลด์ข้อความธรรมดา - นี่คือตัวยึดสำหรับฟิลด์ชื่อไฟล์ซึ่งใช้สำหรับการอัปโหลดไฟล์เท่านั้น แต่สำหรับฟิลด์ข้อความที่ส่งผ่านNoneเป็นพารามิเตอร์แรกจำเป็นเพื่อให้ส่งข้อมูลได้ .

หลายช่องที่มีชื่อเดียวกัน

หากคุณต้องการโพสต์หลายช่องด้วยชื่อเดียวกันแทนที่จะใช้พจนานุกรมคุณสามารถกำหนดเพย์โหลดของคุณเป็นรายการ (หรือทูเพิล) ของสิ่งทอ:

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

API คำขอการสตรีม

หาก API ด้านบนไม่ pythonic เพียงพอสำหรับคุณให้พิจารณาใช้request toolbelt ( pip install requests_toolbelt) ซึ่งเป็นส่วนขยายของโมดูลคำร้องขอหลักที่ให้การสนับสนุนการสตรีมการอัพโหลดไฟล์รวมถึงMultipartEncoderซึ่งสามารถใช้แทนfilesได้และยังช่วยให้ คุณกำหนดเพย์โหลดเป็นพจนานุกรมทูเพิลหรือรายการ

MultipartEncoderสามารถใช้ได้ทั้งกับคำขอหลายส่วนที่มีหรือไม่มีช่องอัปโหลดจริง ต้องกำหนดให้กับdataพารามิเตอร์

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})

หากคุณต้องการส่งหลายช่องที่มีชื่อเดียวกันหรือหากลำดับของช่องฟอร์มมีความสำคัญคุณสามารถใช้ทูเพิลหรือรายการแทนพจนานุกรมได้:

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

ขอบคุณสำหรับสิ่งนี้. ลำดับของคีย์มีความสำคัญสำหรับฉันและสิ่งนี้ช่วยได้มาก
Splendor

น่าอัศจรรย์. อธิบายไม่ได้ว่า api ที่ฉันกำลังทำงานอยู่นั้นต้องการค่าที่แตกต่างกัน 2 ค่าสำหรับคีย์เดียวกัน นี่มันอัศจรรย์มาก. ขอบคุณ.
59

@ccpizza บรรทัดนี้หมายถึงอะไร? > "('file.py', เปิด ('file.py', 'rb'), 'text / plain')" มันไม่ได้ผลสำหรับฉัน :(
Denis Koreyba

@DenisKoreyba: นี่คือตัวอย่างของฟิลด์อัปโหลดไฟล์ซึ่งถือว่าไฟล์ที่มีชื่อfile.pyอยู่ในโฟลเดอร์เดียวกับสคริปต์ของคุณ
ccpizza

1
คุณสามารถใช้Noneแทนสตริงว่างได้ จากนั้นคำขอจะไม่มีชื่อไฟล์เลย ดังนั้นแทนที่จะมันจะเป็นContent-Disposition: form-data; name="action"; filename="" Content-Disposition: form-data; name="action"สิ่งนี้สำคัญสำหรับฉันสำหรับเซิร์ฟเวอร์ที่จะยอมรับฟิลด์เหล่านั้นเป็นฟิลด์แบบฟอร์มไม่ใช่เป็นไฟล์
Mitar

10

นี่คือข้อมูลโค้ดง่ายๆในการอัปโหลดไฟล์เดียวพร้อมพารามิเตอร์เพิ่มเติมโดยใช้คำขอ:

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)

โปรดทราบว่าคุณไม่จำเป็นต้องระบุประเภทเนื้อหาใด ๆ อย่างชัดเจน

หมายเหตุ: ต้องการแสดงความคิดเห็นเกี่ยวกับหนึ่งในคำตอบข้างต้น แต่ทำไม่ได้เนื่องจากชื่อเสียงต่ำจึงร่างคำตอบใหม่ที่นี่


5

คุณต้องใช้nameแอตทริบิวต์ของไฟล์อัปโหลดที่อยู่ใน HTML ของไซต์ ตัวอย่าง:

autocomplete="off" name="image">

เห็น name="image">มั้ย? คุณสามารถค้นหาได้ใน HTML ของไซต์สำหรับอัปโหลดไฟล์ คุณต้องใช้เพื่ออัปโหลดไฟล์ด้วยMultipart/form-data

สคริปต์:

import requests

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

ที่นี่แทนที่รูปภาพให้เพิ่มชื่อของไฟล์อัปโหลดใน HTML

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

หากการอัปโหลดจำเป็นต้องคลิกปุ่มเพื่ออัปโหลดคุณสามารถใช้ดังนี้:

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

จากนั้นเริ่มการร้องขอ

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

และเสร็จแล้วอัปโหลดไฟล์สำเร็จ


3

ส่งคีย์ข้อมูลและค่าหลายส่วน / แบบฟอร์ม

คำสั่ง curl:

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

คำขอหลาม- คำขอ POST ที่ซับซ้อนมากขึ้น :

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

ส่งไฟล์ข้อมูลหลายส่วน / แบบฟอร์ม

คำสั่ง 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

คำขอ python - โพสต์ไฟล์ที่เข้ารหัสหลายส่วน :

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

นั่นคือทั้งหมด


0

ฉันพยายามส่งคำขอไปยัง URL_server พร้อมโมดูลคำขอใน python 3 สิ่งนี้ใช้ได้กับฉัน:

# -*- coding: utf-8 *-*
import json, requests

URL_SERVER_TO_POST_DATA = "URL_to_send_POST_request"
HEADERS = {"Content-Type" : "multipart/form-data;"}

def getPointsCC_Function():
  file_data = {
      'var1': (None, "valueOfYourVariable_1"),
      'var2': (None, "valueOfYourVariable_2")
  }

  try:
    resElastic = requests.post(URL_GET_BALANCE, files=file_data)
    res = resElastic.json()
  except Exception as e:
    print(e)

  print (json.dumps(res, indent=4, sort_keys=True))

getPointsCC_Function()

ที่ไหน:

  • URL_SERVER_TO_POST_DATA = เซิร์ฟเวอร์ที่เราจะส่งข้อมูล
  • HEADERS = ส่งส่วนหัวแล้ว
  • file_data = พารามิเตอร์ที่ส่ง

0

เพื่อชี้แจงตัวอย่างที่ให้ไว้ข้างต้น

"คุณต้องใช้พารามิเตอร์ files เพื่อส่งคำขอ POST ในรูปแบบหลายส่วนแม้ว่าคุณจะไม่จำเป็นต้องอัปโหลดไฟล์ใด ๆ ก็ตาม"

ไฟล์ = {}

ไม่ได้ผลน่าเสียดาย

คุณจะต้องใส่ค่าดัมมี่เข้าไปเช่น

files={"foo": "bar"}

ฉันคิดขึ้นมาเมื่อพยายามอัปโหลดไฟล์ไปยัง REST API ของ Bitbucket และต้องเขียนสิ่งที่น่ารังเกียจนี้เพื่อหลีกเลี่ยงข้อผิดพลาด "Unsupported Media Type" ที่น่ากลัว:

url = "https://my-bitbucket.com/rest/api/latest/projects/FOO/repos/bar/browse/foobar.txt"
payload = {'branch': 'master', 
           'content': 'text that will appear in my file',
           'message': 'uploading directly from python'}
files = {"foo": "bar"}
response = requests.put(url, data=payload, files=files)

: O =


-1

นี่คือข้อมูลโค้ด python ที่คุณต้องใช้ในการอัปโหลดไฟล์เดียวขนาดใหญ่เป็นไฟล์ข้อมูลแบบหลายส่วน ด้วยมิดเดิลแวร์ NodeJs Multer ที่ทำงานบนฝั่งเซิร์ฟเวอร์

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

สำหรับฝั่งเซิร์ฟเวอร์โปรดตรวจสอบเอกสาร Multer ที่: https://github.com/expressjs/multer ที่นี่ฟิลด์ single ('fieldName') ใช้เพื่อยอมรับไฟล์เดียวหนึ่งไฟล์ดังใน:

var upload = multer().single('fieldName');
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.