【问题标题】:Upload a large XML file with Python Requests library使用 Python 请求库上传大型 XML 文件
【发布时间】:2012-11-11 13:42:03
【问题描述】:

我正在尝试用 Python 和 requests 库替换 curl。使用 curl,我可以使用 curl -T 选项将单个 XML 文件上传到 REST 服务器。我一直无法对 requests 库做同样的事情。

一个基本的场景有效:

payload = '<person test="10"><first>Carl</first><last>Sagan</last></person>'
headers = {'content-type': 'application/xml'}
r = requests.put(url, data=payload, headers=headers, auth=HTTPDigestAuth("*", "*"))

当我通过打开 XML 文件将有效负载更改为更大的字符串时,.put 方法会挂起(我使用编解码器库来获取正确的 unicode 字符串)。例如,对于 66KB 的文件:

xmlfile = codecs.open('trb-1996-219.xml', 'r', 'utf-8')
headers = {'content-type': 'application/xml'}
content = xmlfile.read()
r = requests.put(url, data=content, headers=headers, auth=HTTPDigestAuth("*", "*"))

我一直在研究使用多部分选项(文件),但服务器似乎不喜欢这样。

所以我想知道是否有办法在 Python 请求库中模拟 curl -T 行为。

更新 1: 该程序在 textmate 中挂起,但在命令行上引发 UnicodeEncodeError 错误。看来这一定是问题所在。所以问题是:有没有办法使用请求库将 unicode 字符串发送到服务器?

更新 2: 感谢 Martijn Pieters 的评论,UnicodeEncodeError 消失了,但出现了一个新问题。 对于文字 (ASCII) XML 字符串,日志记录显示以下几行:

2012-11-11 15:55:05,154 INFO Starting new HTTP connection (1): my.ip.address
2012-11-11 15:55:05,294 DEBUG "PUT /v1/documents?uri=/example/test.xml HTTP/1.1" 401 211
2012-11-11 15:55:05,430 DEBUG "PUT /v1/documents?uri=/example/test.xml HTTP/1.1" 201 0

似乎服务器总是退回第一次身份验证尝试 (?) 但随后接受第二次。

将文件对象 (open('trb-1996-219.xml', 'rb')) 传递给数据,日志文件显示:

2012-11-11 15:50:54,309 INFO Starting new HTTP connection (1): my.ip.address
2012-11-11 15:50:55,105 DEBUG "PUT /v1/documents?uri=/example/test.xml HTTP/1.1" 401 211
2012-11-11 15:51:25,603 WARNING Retrying (0 attempts remain) after connection broken by 'BadStatusLine("''",)': /v1/documents?uri=/example/test.xml

因此,第一次尝试像以前一样被阻止,但没有进行第二次尝试。

根据 Martijn Pieters(下)的说法,第二个问题可以用有故障的服务器(空行)来解释。 我会调查这个,但如果有人有解决方法(除了使用 curl)我不介意听到它。

对于小字符串和文件对象的请求库的行为如此不同,我仍然感到惊讶。文件对象在到达服务器之前不是已经序列化了吗?

【问题讨论】:

    标签: python xml python-requests


    【解决方案1】:

    要 PUT 大文件,不要将它们读入内存。只需将文件作为 data 关键字传递:

    xmlfile = open('trb-1996-219.xml', 'rb')
    headers = {'content-type': 'application/xml'}
    r = requests.put(url, data=xmlfile, headers=headers, auth=HTTPDigestAuth("*", "*"))
    

    此外,您以 unicode 格式打开文件(从 UTF-8 解码)。由于您要将其发送到远程服务器,因此您需要原始字节,而不是 unicode 值,并且您应该将文件作为二进制文件打开。

    【讨论】:

    • 感谢您的快速回复。这样做解决了 UnicodeEncodeError 但引入了 ConnectionError: MaxRetryError。并且服务器没有宕机,因为我可以用 curl 上传文件。
    • @M_breeb:您需要使用logging 模块来找出原因;由于某种原因,与服务器的连接尝试失败,能够PUT数据之前。 urllib3连接池重试连接,每次连接logging模块都会记录失败。
    • import logging,然后logging.basicConfig() 是快速获取该输出的最基本方法。
    • 我已经开始记录,成功的 put(短文字字符串)和不成功的 put(rb 文件对象)之间的区别是警告“2012-11-11 15:37:27,347警告在连接被 'BadStatusLine("''",)' 中断后重试(剩余 0 次尝试):/v1/documents?uri=/example/test.xml
    • @M_breeb:您的服务器响应为空状态行,违反了 HTTP RFC。我担心这是一个服务器端问题,而不是 Python 问题。它可能需要一个内容长度标头,但服务器仍然有问题。
    【解决方案2】:

    摘要式身份验证始终要求您至少向服务器发出两次请求。第一个请求不包含任何身份验证数据。第一个请求将失败,并返回 401“需要授权”响应代码和用于散列密码等的摘要质询(称为 nounce)(确切的细节在这里无关紧要)。这用于向服务器发出第二个请求,其中包含您的凭据与质询进行哈希处理。

    问题在于这两个步骤的身份验证:您的大文件已经与第一个未经授权的请求一起发送(发送无效),但在第二个请求中,文件对象已经在 EOF 位置。由于文件大小也在第二个请求的 Content-length 标头中发送,这会导致服务器等待永远不会发送的文件。

    您可以使用 requests 会话解决它,并首先发出一个简单的身份验证请求(例如 GET 请求)。然后使用与第一个请求相同的摘要质询发出包含实际负载的第二个 PUT 请求。

    sess = requests.Session()
    sess.auth = HTTPDigestAuth("*", "*")
    sess.get(url)
    headers = {'content-type': 'application/xml'}
    with codecs.open('trb-1996-219.xml', 'r', 'utf-8') as xmlfile:
        sess.put(url, data=xmlfile, headers=headers)
    

    【讨论】:

      【解决方案3】:

      我在 python 中使用请求来使用命令上传 XML 文件。 首先打开文件使用 open() file = open("PIR.xsd") fragment = file.read() file.close() 将 XML 文件的数据复制到请求的有效负载中并发布 payload = {'key':'PFAkrzjmuZR957','xmlFragment':fragment} r = requests.post(URL,data=payload) 检查 html 验证码 print (r.text)

      【讨论】:

        猜你喜欢
        • 2013-09-02
        • 1970-01-01
        • 2017-10-11
        • 2019-05-20
        • 2014-04-29
        相关资源
        最近更新 更多