我正在执行使用Python请求库上传文件的简单任务。我搜索了Stack Overflow,似乎没有人有同样的问题,即文件没有被服务器接收到:

import requests
url='http://nesssi.cacr.caltech.edu/cgi-bin/getmulticonedb_release2.cgi/post'
files={'files': open('file.txt','rb')}
values={'upload_file' : 'file.txt' , 'DB':'photcat' , 'OUT':'csv' , 'SHORT':'short'}
r=requests.post(url,files=files,data=values)

我用我的文件名填充'upload_file'关键字的值,因为如果我让它为空,它说

Error - You must select a file to upload!

现在我得到

File  file.txt  of size    bytes is  uploaded successfully!
Query service results:  There were 0 lines.

只有当文件为空时才会出现。所以我不知道如何成功地发送我的文件。我知道这个文件是有效的,因为如果我去这个网站,手动填写表单,它会返回一个匹配对象的列表,这就是我想要的。我很感激所有的提示。

其他一些相关的话题(但没有回答我的问题):

从Python脚本中使用POST发送文件 http://docs.python-requests.org/en/latest/user/quickstart/#response-content 使用请求上传文件并发送额外数据 http://docs.python-requests.org/en/latest/user/advanced/#body-content-workflow


如果upload_file是文件,使用:

files = {'upload_file': open('file.txt','rb')}
values = {'DB': 'photcat', 'OUT': 'csv', 'SHORT': 'short'}

r = requests.post(url, files=files, data=values)

和请求将发送一个由多个部分组成的表单POST体,其中upload_file字段设置为file.txt文件的内容。

文件名将包含在特定字段的mime头中:

>>> import requests
>>> open('file.txt', 'wb')  # create an empty demo file
<_io.BufferedWriter name='file.txt'>
>>> files = {'upload_file': open('file.txt', 'rb')}
>>> print(requests.Request('POST', 'http://example.com', files=files).prepare().body.decode('ascii'))
--c226ce13d09842658ffbd31e0563c6bd
Content-Disposition: form-data; name="upload_file"; filename="file.txt"


--c226ce13d09842658ffbd31e0563c6bd--

注意filename="file.txt"参数。

如果需要更多的控制,可以使用包含2到4个元素的元组作为文件映射值。第一个元素是文件名,后面跟着内容,一个可选的内容类型头值和一个可选的附加头的映射:

files = {'upload_file': ('foobar.txt', open('file.txt','rb'), 'text/x-spam')}

这设置了一个可选的文件名和内容类型,省略了可选的头文件。

如果您的意思是从一个文件中获取整个POST主体(没有指定其他字段),那么不要使用files参数,只需将文件直接作为数据发布。然后,您可能也想设置一个Content-Type头,否则将不会设置任何内容。参见Python请求-从文件中POST数据。


(2018)新的python请求库简化了这个过程,我们可以使用'files'变量来表示我们想要上传一个多部分编码的文件

url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}

r = requests.post(url, files=files)
r.text

客户端上传

如果你想用Python请求库上传一个文件,那么请求库支持流上传,这允许你发送大文件或流,而不读入内存。

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

服务器端

然后将文件存储在server.py端,这样就可以将流保存到文件中而不加载到内存中。下面是一个使用Flask文件上传的例子。

@app.route("/upload", methods=['POST'])
def upload_file():
    from werkzeug.datastructures import FileStorage
    FileStorage(request.stream).save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    return 'OK', 200

或者使用werkzeug表单数据解析,就像在修复“大文件上传消耗内存”的问题中提到的那样,以避免在大文件上传(s.t 22 GiB文件大约60秒)时低效地使用内存。内存使用率稳定在13 MiB左右)。

@app.route("/upload", methods=['POST'])
def upload_file():
    def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
        import tempfile
        tmpfile = tempfile.NamedTemporaryFile('wb+', prefix='flaskapp', suffix='.nc')
        app.logger.info("start receiving file ... filename => " + str(tmpfile.name))
        return tmpfile

    import werkzeug, flask
    stream, form, files = werkzeug.formparser.parse_form_data(flask.request.environ, stream_factory=custom_stream_factory)
    for fil in files.values():
        app.logger.info(" ".join(["saved form name", fil.name, "submitted as", fil.filename, "to temporary file", fil.stream.name]))
        # Do whatever with stored file at `fil.stream.name`
    return 'OK', 200

在Ubuntu中你可以这样应用,

将文件保存在某个位置(临时),然后打开并发送到API

      path = default_storage.save('static/tmp/' + f1.name, ContentFile(f1.read()))
      path12 = os.path.join(os.getcwd(), "static/tmp/" + f1.name)
      data={} #can be anything u want to pass along with File
      file1 = open(path12, 'rb')
      header = {"Content-Disposition": "attachment; filename=" + f1.name, "Authorization": "JWT " + token}
       res= requests.post(url,data,header)

@martijn-pieters的答案是正确的,但是我想给data=和另一边添加一点上下文,在Flask服务器中,在你试图上传文件和JSON的情况下。

从请求端来看,这就像Martijn描述的那样:

files = {'upload_file': open('file.txt','rb')}
values = {'DB': 'photcat', 'OUT': 'csv', 'SHORT': 'short'}

r = requests.post(url, files=files, data=values)

然而,在Flask端(在这篇文章的另一边的接收web服务器),我必须使用表单

@app.route("/sftp-upload", methods=["POST"])
def upload_file():
    if request.method == "POST":
        # the mimetype here isnt application/json
        # see here: https://stackoverflow.com/questions/20001229/how-to-get-posted-json-in-flask
        body = request.form
        print(body)  # <- immutable dict

Body = request.get_json()不会返回任何内容。Body = request.get_data()将返回一个包含许多内容的blob,如文件名等。

下面是不好的部分:在客户端,将data={}更改为json={}将导致服务器无法读取KV对!例如,这将导致上面的{}体:

r = requests.post(url, files=files, json=values). # No!

这很糟糕,因为服务器无法控制用户如何格式化请求;json=是请求用户的习惯。


上传:

with open('file.txt', 'rb') as f:
    files = {'upload_file': f.read()}
    
values = {'DB': 'photcat', 'OUT': 'csv', 'SHORT': 'short'}

r = requests.post(url, files=files, data=values)

下载(Django):

with open('file.txt', 'wb') as f:
    f.write(request.FILES['upload_file'].file.read())

当调用api时,你可以通过post api发送任何文件,只需要提到files={'any_key': fobj}

import requests
import json
    
url = "https://request-url.com"
 
headers = {"Content-Type": "application/json; charset=utf-8"}
    
with open(filepath, 'rb') as fobj:
    response = requests.post(url, headers=headers, files={'file': fobj})
 
print("Status Code", response.status_code)
print("JSON Response ", response.json())

关于到目前为止给出的答案,总有一些缺失的东西阻止它在我这边工作。让我来告诉你什么对我有用:

import json
import os
import requests

API_ENDPOINT = "http://localhost:80"
access_token = "sdfJHKsdfjJKHKJsdfJKHJKysdfJKHsdfJKHs"  # TODO: get fresh Token here


def upload_engagement_file(filepath):
 
    url = API_ENDPOINT + "/api/files"  # add any URL parameters if needed
    hdr = {"Authorization": "Bearer %s" % access_token}
    with open(filepath, "rb") as fobj:
        file_obj = fobj.read()
        file_basename = os.path.basename(filepath)
        file_to_upload = {"file": (str(file_basename), file_obj)}
        finfo = {"fullPath": filepath}
        upload_response = requests.post(url, headers=hdr, files=file_to_upload, data=finfo)
        fobj.close()
    # print("Status Code ", upload_response.status_code)
    # print("JSON Response ", upload_response.json())
    return upload_response

注意requests.post(…)需要

一个url参数,包含您调用的API端点的完整url,使用API_ENDPOINT,假设我们有一个http://localhost:8000/api/files端点来POST一个文件 头参数,至少包含授权(承载令牌) 一个files参数,包含文件名加上整个文件内容 只包含路径和文件名的数据参数

所需安装(控制台):

PIP安装请求

从函数调用中返回的是一个响应对象,其中包含状态代码和JSON格式的完整错误消息。upload_engage ement_file末尾的注释打印语句向您展示了如何访问它们。

注意:关于请求库的一些有用的附加信息可以在这里找到


有些可能需要通过put请求上传,这与发布数据略有不同。为了形成有效的请求,了解服务器如何期望数据是很重要的。一个常见的混淆来源是当数据不被接受时发送多部分形式的数据。本例使用基本身份验证,并通过put请求更新图像。

url = 'foobar.com/api/image-1'
basic = requests.auth.HTTPBasicAuth('someuser', 'password123')
# Setting the appropriate header is important and will vary based
# on what you upload
headers = {'Content-Type': 'image/png'} 
with open('image-1.png', 'rb') as img_1:
    r = requests.put(url, auth=basic, data=img_1, headers=headers)

虽然请求库使处理http请求变得容易得多,但它的一些魔力和便利性掩盖了如何创建更微妙的请求。