如何使用python requests上传文件?

255 投票
10 回答
580644 浏览
提问于 2025-04-17 23:20

我正在用Python的requests库做一个简单的文件上传任务。我在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.

这个提示只有在文件为空的时候才会出现。所以我不知道怎么才能成功上传我的文件。我知道我的文件是有效的,因为如果我去这个网站手动填写表单,它会返回一份我想要的匹配对象的漂亮列表。我非常感谢任何提示。

一些相关的讨论(但没有解决我的问题):

10 个回答

3

@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 这边(接收这个 POST 请求的服务器),我必须使用 form

@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={},服务器就无法读取键值对了!也就是说,这样会导致上面的 body 变成一个空的 {}:

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

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

8

你可以通过POST接口发送任何文件,只需要在调用这个接口的时候提到 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())
57

客户端上传

如果你想用Python的requests库上传一个文件,这个库支持流式上传,这意味着你可以发送大文件或者数据流,而不需要先把它们读入内存。

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表单数据解析,这是为了修复一个关于“大文件上传占用内存”的问题,目的是避免在上传大文件时浪费内存(比如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
64

在2018年,新的Python请求库让这个过程变得简单多了。我们可以使用'files'这个变量来表示我们想要上传一个多部分编码的文件。

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

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

如果你想上传的文件是 upload_file,可以这样做:

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

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

这样 requests 就会发送一个多部分的表单,里面包含了 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 的值。第一个元素是文件名,接下来是文件内容,还有可选的内容类型头和可选的额外头的映射:

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

这样可以设置一个替代的文件名和内容类型,同时省略可选的头。

如果你想让整个 POST 的内容来自一个文件(而不指定其他字段),那么就不要使用 files 参数,直接把文件作为 data 上传。你可能还需要设置一个 Content-Type 头,因为如果不设置的话,默认是没有的。可以参考这个链接了解更多:Python requests - 从文件中 POST 数据

撰写回答