服务器接收文件时,有时会使用表单接收的方式,这意味着我们需要使用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)命令,效果更佳