【问题标题】:Python Requests: Post JSON and file in single requestPython 请求:在单个请求中发布 JSON 和文件
【发布时间】:2013-10-26 17:23:29
【问题描述】:

我需要执行 API 调用来上传文件以及包含文件详细信息的 JSON 字符串。

我正在尝试使用 python 请求库来执行此操作:

import requests

info = {
    'var1' : 'this',
    'var2'  : 'that',
}

data = json.dumps({
    'token' : auth_token,
    'info'  : info,
})

headers = {'Content-type': 'multipart/form-data'}

files = {'document': open('file_name.pdf', 'rb')}

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

这会引发以下错误:

    raise ValueError("Data must not be a string.")
 ValueError: Data must not be a string

如果我从请求中删除“文件”,它会起作用。
如果我从请求中删除“数据”,它会起作用。
如果我不将数据编码为 JSON,它就可以工作。

因此,我认为错误与在同一请求中发送 JSON 数据和文件有关。

关于如何让它发挥作用的任何想法?

【问题讨论】:

    标签: python json urllib2


    【解决方案1】:

    不要使用 json 编码。

    import requests
    
    info = {
        'var1' : 'this',
        'var2'  : 'that',
    }
    
    data = {
        'token' : auth_token,
        'info'  : info,
    }
    
    headers = {'Content-type': 'multipart/form-data'}
    
    files = {'document': open('file_name.pdf', 'rb')}
    
    r = requests.post(url, files=files, data=data, headers=headers)
    

    请注意,这不一定是您想要的,因为它将成为另一个表单数据部分。

    【讨论】:

    • 如果我按照你的建议做,我会得到另一个例外:“需要多于 1 个值才能解压”并想知道如何处理它:-(
    • 这仅在data 是简单的键值对(类似表单参数)时才有效,但所有嵌套的内容都将被截断,因为 HTTP 表单编码无法表示嵌套的数据结构。
    • 谢谢,@hoefling。你救了我的命。我花了 1 个小时试图理解为什么图书馆会截断它(或者发生了什么)。
    • @proteneer 这似乎对我不起作用,正在打一个电话。 d = requests.post('http://localhost:18090/upload',files={ 'face_image': ('mama_justkilled_aman.jpg', open('mama_justkilled_aman.jpg', 'rb'), 'image/jpeg' ) }, data={ 'gffp': 42 }) 但服务器没有接收到数据
    • @BalaKrishna 也不适合我,服务器看不到数据:(
    【解决方案2】:

    看到这个帖子How to send JSON as part of multipart POST-request

    不要自己设置 Content-type 标头,留给 pyrequests 生成

    def send_request():
        payload = {"param_1": "value_1", "param_2": "value_2"}
        files = {
            'json': (None, json.dumps(payload), 'application/json'),
            'file': (os.path.basename(file), open(file, 'rb'), 'application/octet-stream')
        }
    
        r = requests.post(url, files=files)
        print(r.content)
    

    【讨论】:

    • 假设 Flask 是接收者,那么为此编写 Flask 代码的方法是什么?
    • @Mouldri 试试response.data
    • @MouIdri 应该是request.form['json']
    • @DanSalo 谢谢,我忘了回答,我确实找到了遗嘱。感谢您的确认(支持您的 cmets)
    • 这个对我有用,但是我们如何在这个中发送多个文件?
    【解决方案3】:

    我不认为您可以在多部分编码文件中同时发送数据和文件,因此您也需要将数据设为“文件”:

    files = {
        'data' : data,
        'document': open('file_name.pdf', 'rb')
    }
    
    r = requests.post(url, files=files, headers=headers)
    

    【讨论】:

    • 你将如何解码?客户会得到一个 python 字典而不是 JSON 对吗?这是一个问题!
    • @sabik:请求将字典编码为表单数据。
    • 注意:在接收端:request.files['data'] 是一个文件存储元组。需要做的是执行request.files['data'].read() 来获取实际数据(这是一个json 编码的字符串),因此您需要执行json.loads(request.files['data'].read()) 之类的操作
    【解决方案4】:

    为了发送 Facebook Messenger API,我将所有有效负载字典值更改为字符串。然后,我可以将有效负载作为data 参数传递。

    import requests
    
    ACCESS_TOKEN = ''
    
    url = 'https://graph.facebook.com/v2.6/me/messages'
    payload = {
            'access_token' : ACCESS_TOKEN,
            'messaging_type' : "UPDATE",
            'recipient' : '{"id":"1111111111111"}',
            'message' : '{"attachment":{"type":"image", "payload":{"is_reusable":true}}}',
    }
    files = {'filedata': (file, open(file, 'rb'), 'image/png')}
    r = requests.post(url, files=files, data=payload)
    

    【讨论】:

      【解决方案5】:

      我一直在用requests==2.22.0

      对我来说,下面的代码有效。

      import requests
      
      
      data = {
          'var1': 'this',
          'var2': 'that'
      }
      
      r = requests.post("http://api.example.com/v1/api/some/",
          files={'document': open('doocument.pdf', 'rb')},
          data=data,
          headers={"Authorization": "Token jfhgfgsdadhfghfgvgjhN"}. #since I had to authenticate for the same
      )
      
      print (r.json())
      

      【讨论】:

        【解决方案6】:

        还有什么:

        files = {
            'document': open('file_name.pdf', 'rb')
        }
        

        仅当您的文件与脚本所在的目录位于同一目录时才有效。

        如果你想从不同的目录追加文件,你应该这样做:

        files = {
            'document': open(os.path.join(dir_path, 'file_name.pdf'), 'rb')
        }
        

        其中 dir_path 是您的 'file_name.pdf' 文件所在的目录。

        但是,如果您想发送多个 PDF 文件怎么办?

        您可以简单地创建一个自定义函数来返回您需要的文件列表(在您的情况下,只能是具有 .pdf 扩展名的文件)。这还包括子目录中的文件(递归搜索文件):

        def prepare_pdfs():
            return sorted([os.path.join(root, filename) for root, dirnames, filenames in os.walk(dir_path) for filename in filenames if filename.endswith('.pdf')])
        

        那么你就可以调用它了:

        my_data = prepare_pdfs()
        

        还有简单的循环:

        for file in my_data:
        
            pdf = open(file, 'rb')
        
            files = {
                'document': pdf
            }
        
            r = requests.post(url, files=files, ...)
        

        【讨论】:

        • 最后一个代码块很糟糕。那是DDoS攻击!不要那样做。
        猜你喜欢
        • 2014-08-29
        • 1970-01-01
        • 2020-12-11
        • 2017-08-31
        • 1970-01-01
        • 2016-07-21
        • 1970-01-01
        • 1970-01-01
        • 2017-02-11
        相关资源
        最近更新 更多