公司需要大量轉移檔案,於是我們寫了一隻小程式去爬清單,然後將資料從一個網頁下載上傳至另一個網頁。
一開始測試都很ok,直到我們遇到了一個使用日文名稱的檔案。
在交叉測試之後,下載那邊是沒有問題的,我們使用linux,雖然在command line看到的檔名是亂碼,但是不影響實際上使用。
上傳則是一直 http code 500,訊息也只有internal server error。
import requests
requests.post('url', headers=headers, files={'file': ('日文.zip', ByteIO(content))})
我的直覺就是檔名的問題(公司有很多日文檔案,每次都被婊),於是上網google了一下。
看到了這篇
然後點進去了他的blog看了一下
# coding: utf8
import requests
import re
def rewrite_request(prepared_request):
filename = u'tête-à-tête.txt'.encode('utf-8')
prepared_request.body = re.sub(b'filename\*=.*', b'filename=' + filename, prepared_request.body)
return prepared_request
requests.post('http://localhost/upload.php', files={
'fieldname': (u'tête-à-tête.txt', 'some contents')
}, auth=rewrite_request)
我實際跑了一下,發現什麼都沒做的request.post,body的filename吃到utf-8檔名確實會自動消失,上傳沒有檔名的檔案當然會爆炸。
不過blog的解法是一個醜醜的workaround,於是我研究了一下preparerequest自己寫了一個
import requests
s = requests.Session()
prepped = s.prepare_request(requests.Request('POST', url,
headers=headers, files={'file': ('IWLLREPLACETHIS',io.BytesIO(attachment_file)) }))
prepped.body = re.sub(b'IWLLREPLACETHIS', attachment_name.encode('utf-8'), prepped.body)
prepped.headers['content-length'] = len(prepped.body)
settings = s.merge_environment_settings(prepped.url, {}, None, None, None)
r = s.send(prepped, **{**settings, 'timeout': 10 })
我先塞了一個簡易但不容易重複的字串在filename裡,然後直接取代字串。
另外需要更新headers的content-length,replace body裡面的filename會改變整個body的size,像在我們的api上傳檔案會檢查content-length與body size是否一致,若是不修正一樣是跳internal server error。
這個小問題卡了我1-2個小時檢查,2-3個小時測試+寫code,最後又卡1小時在content-length
寫到這裡,一切都是requests裡的Urllib3的錯!!!!(python2的版本好像是好的)
來追根究底一下
嗯....看了很久 看不太懂,我說說我的想法
Urllib3的filename是吃rfc2231 spec的,長出來應該是這樣filename*=UTF-8''name(但我記得我自己實測name是空的)
但是html 5在multipart/form-data有自己的spec,不支援2231 spec,所以不管怎樣都沒用
在github上已經討論了很久,也有人開了pr,不過urllib3不是只寫給html的,算是一個big change,所以一直沒有merge
網路上如果google python 上傳 中文,應該是一堆文章,但不要跟那些文章一樣去改requests的source code啊
太暴力了XDDDDD