服務器接收文件時,有時會使用表單接收的方式,這意味着我們需要使用Python的requests上傳表單數據和文件。
常用的方式一般如下:
data = { 'name': 'nginx' } files = {'file': open("abc.csv", 'rb')} response = requests.post(url, data=data, files=files)
files是封裝好的參數,直接包括了文件內容,文件名,格式等,data則是表單內容,但這樣做有一個問題,文件是中文名時,requests就會報錯,哪怕encode轉碼成utf8也沒用
百度發現除了requests的這個方法,還可以用一個第三方包MultipartEncoder,而這個包相對來說比較靈活。
一般是from requests_toolbelt.multipart.encoder import MultipartEncoder,這樣導入使用
由於公司項目需要兼容各種環境,不主張使用大量第三方庫,我精簡模塊后提取出my_compat 文件,變成 from my_compat import MultipartEncoder這樣導入使用
但MultipartEncoder也存在無法轉化中文名的問題,所以我在代碼里取了巧,先把文件名轉化成可解析的字符,然后用to_string方法解析,最后把解析后的字符串轉化回去
from requests_toolbelt.multipart.encoder import MultipartEncoder from my_compat import MultipartEncoder import urllib import requests import json encoded_name = urllib.quote(file_name.encode('utf-8'))//取巧做法,先轉化字符 with open(res_path, 'rb') as f_: m = MultipartEncoder( fields={'file': (encoded_name, f_, 'application/octet-stream')} ) decoded_m = m.to_string()//解析時不支持中文 decoded_m = decoded_m.replace(encoded_name, file_name)//替代轉化 response = requests.post(url, data=decoded_m, headers={'Content-Type': m.content_type, 'charset': 'UTF-8'}, verify=False) try: content = json.loads(response.content) except ValueError: content = response.content return content, response.status_code
后來發現這樣做其實很不方便,所以我就閱讀MultipartEncoder的源碼,發現content_type其實就是一個很簡單的隨機字符串的構造,而數據的字符流只要符合一定規范就可以構造,再結合requests包,寫出了如下的代碼,
#coding=utf8 import requests from uuid import uuid4 import os file_name='test' url= boundary=uuid4().hex header={'Content-Type': 'multipart/form-data; boundary={0}'.format(boundary),'charset': 'UTF-8'} with open(r'C:\test'.decode('utf8'), 'r') as f: content=f.readlines() print content content=''.join(content) datas = '--{0}{1}Content-Disposition: form-data; name="file"; filename="{2}"{1}Content-Type: application/octet-stream{1}{1}{3}{1}--{0}--{1}'. \ format(boundary,os.linesep, file_name, content,boundary) print repr(datas) print header response = requests.post(url, data=datas, headers=header, verify=False) print response.status_code,response.text
在windows上調試可以,但在linux上調試一直報錯,后來把os.linesep換成指定的'\r\n'分隔符就可以成功了,不知道是我們公司服務器設置問題還是這個庫的解析問題。
#coding=utf8 import requests from uuid import uuid4 import os file_name='test' url= boundary=uuid4().hex header={'Content-Type': 'multipart/form-data; boundary={0}'.format(boundary),'charset': 'UTF-8'} with open(r'C:\test'.decode('utf8'), 'r') as f: content=f.readlines() print content content=''.join(content) datas = '--{0}{1}Content-Disposition: form-data; name="file"; filename="{2}"{1}Content-Type: application/octet-stream{1}{1}{3}{1}--{0}--{1}'. \ format(boundary,'\r\n', file_name, content,boundary) print repr(datas) print header response = requests.post(url, data=datas, headers=header, verify=False) print response.status_code,response.text
結合saltstack,在proxy上執行的 "salt '{}' cp.push {}".format(path, agent_id, file_path)命令,效果更佳