由於以前沒有使用requests上傳過文件,所以今天在使用它上傳文件的時候遇見了一個坑,接下來我們就來一層一層解析這個坑
以科大訊飛官網上傳音頻文件爲例
首先是喜聞樂見地打開Fiddler進行抓包操作,流程也很簡單,很快就抓下來了上傳文件的這個包,見下圖
然後就進入了懵逼模式,content-type裏面這個boundary是啥,以前咋沒見過呢,還有data裏面的這些個東西又是啥,還來亂碼了,奇怪了奇怪了,於是去翻閱了資料。
根據http/1.1 rfc 2616的協議規定,我們的請求方式只有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE等,那爲爲何我們還會有multipart/form-data請求之說呢?這就要從頭來說了。
http協議規定以ASCII碼傳輸,建立在tcp,ip協議智商的引用規範,規範內容把http請求分成3個部分,狀態行,請求頭,請求體。所有的方法,實現都是圍繞如何使用和組織這三部分來完成了,萬變不離其宗,http的知識大家可以問度娘。
既然上面請求方式裏面沒有multipart/form-data那這個請求又是怎麼回事呢,其實是一回事,multipart/form-data也是在post基礎上演變而來的,具體如下:
1.multipart/form-data的基礎方式是post,也就是說通過post組合方式來實現的。
2.multipart/form-data於post方法的不同之處在於請求頭和請求體。
3.multipart/form-data的請求頭必須包含一個特殊的頭信息:Content-Type,其值也必須爲multipart/form-data,同時還需要規定一個內容分割用於分割請求提中多個post的內容,如文件內容和文本內容是需要分隔開來的,不然接收方就無法解析和還原這個文件了,具體的頭信息如下:
Content-Type: multipart/form-data; boundary=${bound}
參考 什麼是multipart/form-data請求
然後美滋滋地直接用requests去上傳了一波文件
import requests
url = 'https://www.iflyrec.com/XFTJService/web/audio/upload?folder=1416861992900653'
files = {'file': open('video.wav', 'rb')}
r = requests.post(url, files=files)
print(r.text)
好的 然後毫無疑問地報錯
話說這不應該啊 不是在我想象中這樣就應該OK了麼 於是硬着頭皮再面向百度了一波–
然後參考了我抓的包,發現multipart/form-data 請求在requests裏面其實是有實例的,使用元組形式上傳files,於是完成了以下代碼
import requests
headers = {
"Host": "www.iflyrec.com",
"Connection": "keep-alive",
"Content-Length": "137110",
"Origin": "https://www.iflyrec.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6776.400 QQBrowser/10.3.2601.400",
"Accept": "*/*",
"Referer": "https://www.iflyrec.com/html/addArtificialOrder.html",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9",
# "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryZjPRjN0wAzGytw34",
"Cookie": "login_err=0; from=baidu; keywords=%25e8%25ae%25af%25e9%25a3%259e+%25e8%25af%25ad%25e9%259f%25b3%25e8%25af%2586%25e5%2588%25ab; Hm_lvt_aee4c3bed3d9924f2f184904ecb37065=1542615755; Hm_lvt_c711619f2461980a3dbceed5874b0c6a=1542615755; storage=QsIwv8WBIrqRiBa62wvKbo8oswgQSz4Wr4bjQk8jm+scRW7Z6rVZZv1WwElHU0BN+ZAmvzlXvxc2gaeHLi2yHQ==; ec_im_local_status=0; CUSTOM_INVITE_CONTENT=; ec_invite_state=0; Hm_lpvt_aee4c3bed3d9924f2f184904ecb37065=1542615766; ec_im_tab_num=1; ec_invite_state_time=1542615765582; Hm_lpvt_c711619f2461980a3dbceed5874b0c6a=1542615766"
}
# files = {'file': open('333.wav', 'rb')}
files = {'id': (None, 'WU_FILE_0'),
'name': (None, '333.wav'),
'type': (None, 'audio/wav'),
'lastModifiedDate': (None, 'Tue Aug 21 2018 17:22:12 GMT+0800 (中國標準時間)'),
'size': (None,'136380'),
'file': ('333.wav', open('333.wav', 'rb'), 'audio/wav')} #
r = requests.post('https://www.iflyrec.com/XFTJService/web/audio/upload?folder=1416861992900653', headers=headers, files=files)
print(r.text)
print(r.headers)
print(r.status_code)
運行成功圖如下
需要注意的是,我將headers請求頭裏面的content-type屬性註釋了,如果加上了,則會報錯,然後我把這個模擬請求抓包下來看了看,他自動加上了Content-Type: multipart/form-data; boundary=${bound},所以這個boundary應該是上傳文件的標識,上傳文件的時候content-type會有一個默認值,我們不去指定,也就沒問題了。