某些post接口,需要發送multipart/form-data類型的數據,如何使用python requests來模擬這種類型的請求發送呢?
根據http/1.1 rfc 2616的協議規定,我們的請求方式有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE等。
http協議規定以ASCII碼傳輸,建立在tcp,ip協議之上的引用規范。規范內容把http請求分成3個部分:狀態行,請求頭,請求體。所有的方法,實現都是圍繞如何使用和組織這三部分來完成了,萬變不離其宗。
post請求有兩種編碼格式:application/x-www-form-urlencoded 和 multipart/form-data
application/x-www-form-urlencoded
- application/x-www-form-urlencoded 常用在前端表單提交時,參數格式為:key=value&key=value。
- 如果參數中有特殊字符,則要進行url編碼,編碼格式就是application/x-www-form-urlencoded(規則:將鍵值對的參數用&連接起來;有空格轉換為+加號;有特殊符號轉換為ASCII HEX值)。
如:參數值中有&,需要轉換為ASCII HEX值%26,對應ASCII中38位置的&。 - application/x-www-form-urlencoded是瀏覽器默認的編碼格式。對於Get請求,是將參數轉換為?key=value&key=value格式,連接到url后。

multipart/form-data
multipart/form-data格式不僅可以傳輸參數,還可以傳輸文件。也是在post基礎上演變而來的,具體如下:
- multipart/form-data的基礎方式是post,即基於post請求來實現的。
- multipart/form-data與普通post方法的不同之處在於請求頭和請求體。
- multipart/form-data的請求頭必須包含一個特殊的頭信息:Content-Type,其值也必須為multipart/form-data;同時還需要規定一個內容分割用於分割請求體中不同參數的內容。
具體的頭信息如下:Content-Type: multipart/form-data; boundary=${bound} - multipart/form-data的請求體也是一個字符串,和普通post請求的不同之處在於它的構造方式。普通post請求是簡單的鍵值對連接,而multipart/form-data則是添加了分隔符、參數描述信息等內容的構造體。


關於boundary:
- 上面說到普通post請求使用 & 來分隔參數,那服務器使用
multipart/form-data格式接收POST請求時,使用何種方式來分割參數的呢?答案是boundary。 - 由上圖可以發現,HTTP的Body中使用兩個短橫線”–”加上boundary字符串作為不同參數的分割,而且不管是值參數(Value)還是文件參數(File)在Boundary內部都有自己的描述信息,並不是URL參數的簡單移動。
並且在結束的時候,不僅前綴要加雙短橫線,后綴也要加,代表結束。

boundary快問快答:
Q:boundary的值是用戶可以自由定義的嗎?
A:是的,但為了避免和正常文本重復了,盡量要使用復雜一點的內容。
Q:boundary的作用?
A:分割參數,類似於普通post請求中的 &
使用python發送multipart/from-data類型數據
有兩種方式:
- 手動組建form-data並修改headers
- 通過files參數傳遞form-data,推薦此種方式,這里只說這種方式
在官方網站上,requests模擬一個表單數據的格式如下:
files = {{name}: (<filename>, <file object>,<content type>, <per-part headers>)}
這一行模擬出來的post數據為:
Content-Disposition: form-data; name={name};filename=<filename>
Content-Type: <content type>
<file object>
--boundary
注:如果filename 和 content-Type不寫,那么響應模擬post的數據就不會有二者。
通過上述說明,我們可以構造出files后直接post請求發送即可:
files = {
'schoolId': (None, -1),
'schoolName': (None, ""),
"reward": (None, 5),
"publishText": (None, "測試測試"),
"tags": (None, 1),
'image': ('image.jpg', open('%s/resource/upload_images/image.jpg' % PATH_DIR, 'rb'), 'application/octet-stream')
}
response = requests.post(url, files=files)
