【问题标题】:Make an http POST request to upload a file using Python urllib/urllib2使用 Python urllib/urllib2 发出 http POST 请求以上传文件
【发布时间】:2015-01-18 22:50:43
【问题描述】:

我想使用 Python 发出 POST 请求以将文件上传到 Web 服务(并获得响应)。例如,我可以使用curl 执行以下 POST 请求:

curl -F "file=@style.css" -F output=json http://jigsaw.w3.org/css-validator/validator

如何使用 python urllib/urllib2 发出相同的请求?到目前为止,我得到的最接近的是:

with open("style.css", 'r') as f:
    content = f.read()
post_data = {"file": content, "output": "json"}
request = urllib2.Request("http://jigsaw.w3.org/css-validator/validator", \
                          data=urllib.urlencode(post_data))
response = urllib2.urlopen(request)

我从上面的代码中得到了 HTTP 错误 500。但是既然我的curl命令成功了,那一定是我的python请求有问题吧?

我对这个话题很陌生,我的问题可能有非常简单的答案或错误。

【问题讨论】:

    标签: python http post urllib2 urllib


    【解决方案1】:

    我个人认为您应该考虑使用requests 库来发布文件。

    url = 'http://jigsaw.w3.org/css-validator/validator'
    files = {'file': open('style.css')}
    response = requests.post(url, files=files)
    

    使用urllib2 上传文件并非不可能,而是一项相当复杂的任务:http://pymotw.com/2/urllib2/#uploading-files

    【讨论】:

    • 谢谢,@Wolph。我刚刚尝试了 requests 库,但仍然收到 HTTP 500 错误。所以我的问题可能应该改写为,我们在 python 中发出的请求与 curl 发出的请求有什么区别?谢谢。
    • 好吧,你的 curl 请求中有 output=json,这不在 Python 请求中,所以这可能是不同之处。很高兴你现在可以使用它了 :)
    • 我有.jpg 变量形式的原始.jpg 文件。我怎样才能以类似的方式POST这个?
    • @Santhosh 使用ndarray.tobytes() 可能是最简单的,但您也可以使用fh = StringIO(); ndarray.tofile(fh) 并将fh 用作文件对象
    • @real4x:作者不是唯一阅读这个问题的人,它的答案。对于大多数使用requests 的人来说,这是更好的选择,这就是为什么我给出了这个答案以及如何使用它的示例。此外,我链接到复杂的代码以使其工作
    【解决方案2】:

    经过一番挖掘,this post 似乎解决了我的问题。事实证明我需要正确设置多部分编码器。

    from poster.encode import multipart_encode
    from poster.streaminghttp import register_openers
    import urllib2
    
    register_openers()
    
    with open("style.css", 'r') as f:
        datagen, headers = multipart_encode({"file": f})
        request = urllib2.Request("http://jigsaw.w3.org/css-validator/validator", \
                                  datagen, headers)
        response = urllib2.urlopen(request)
    

    【讨论】:

    • 别忘了关闭style.css文件?
    • @Vladius 该文件将自动关闭,因为它被用作上下文管理器。请参阅the with statement 上的文档。
    • 我对 python 真的很陌生。我跑了上面貌似成功了。我现在应该期待什么?我在哪里可以验证它的工作原理。
    【解决方案3】:

    嗯,有多种方法可以做到这一点。如上所述,您可以在“multipart/form-data”中发送文件。但是,目标服务可能不期望这种类型,在这种情况下,您可以尝试更多方法。

    传递文件对象

    urllib2 可以接受文件对象为data。当您传递此类型时,库将文件作为二进制流读取并将其发送出去。但是,它不会设置正确的Content-Type 标头。此外,如果缺少Content-Length 标头,那么它将尝试访问该文件不存在的对象的len 属性。也就是说,您必须同时提供 Content-TypeContent-Length 标头才能使该方法正常工作:

    import os
    import urllib2
    
    filename = '/var/tmp/myfile.zip'
    headers = {
        'Content-Type': 'application/zip',
        'Content-Length': os.stat(filename).st_size,
    }
    request = urllib2.Request('http://localhost', open(filename, 'rb'),
                              headers=headers)
    response = urllib2.urlopen(request)
    

    包装文件对象

    不处理长度,你可以创建一个简单的包装对象。如果您将文件加载到内存中,只需稍作更改,您就可以对其进行调整以从字符串中获取内容。

    class BinaryFileObject:
      """Simple wrapper for a binary file for urllib2."""
    
      def __init__(self, filename):
        self.__size = int(os.stat(filename).st_size)
        self.__f = open(filename, 'rb')
    
      def read(self, blocksize):
        return self.__f.read(blocksize)
    
      def __len__(self):
        return self.__size
    

    将内容编码为 base64

    另一种方法是通过base64.b64encode 编码data 并提供Content-Transfer-Type: base64 标头。但是,这种方法需要服务器端的支持。根据实现,服务可以接受文件并错误地存储它,或者返回HTTP 400。例如。 GitHub API 不会报错,但是上传的文件会损坏。

    【讨论】:

    • 另外需要注意的是base64 表示+33.3% 的流量。特别是如果您使用一些云托管,它会花费不少。
    猜你喜欢
    • 2016-07-28
    • 1970-01-01
    • 2016-03-11
    • 1970-01-01
    • 2018-01-31
    • 2011-05-24
    • 1970-01-01
    • 2017-11-28
    相关资源
    最近更新 更多