Python requests上传文件实现步骤
官方文档:https://2.python-requests.org//en/master/
工作中涉及到一个功能,需要上传附件到一个接口,接口参数如下:
使用httppost提交附件multipart/form-data格式,url:http://test.com/flow/upload,
字段列表:
md5://md5加密(随机值_当时时间戳)
filesize://文件大小
file://文件内容(须含文件名)
返回值:
{"success":true,"uploadName":"tmp.xml","uploadPath":"uploads\/201311\/758e875fb7c7a508feef6b5036119b9f"}
由于工作中主要用python,并且项目中已有使用requests库的地方,所以计划使用requests来实现,本来以为是很简单的一个小功能,结果花费了大量的时间,requests官方的例子只提到了上传文件,并不需要传额外的参数:
https://2.python-requests.org//en/master/user/quickstart/#post-a-multipart-encoded-file
>>>url='https://httpbin.org/post'
>>>files={'file':('report.xls',open('report.xls','rb'),'application/vnd.ms-excel',{'Expires':'0'})}
>>>r=requests.post(url,files=files)
>>>r.text
{
...
"files":{
"file":""
},
...
}
但是如果涉及到了参数的传递时,其实就要用到requests的两个参数:data、files,将要上传的文件传入files,将其他参数传入data,request库会将两者合并到一起做一个multipart,然后发送给服务器。
最终实现的代码是这样的:
withopen(file_name)asf:
content=f.read()
request_data={
'md5':md5.md5('%d_%d'%(0,int(time.time()))).hexdigest(),
'filesize':len(content),
}
files={'file':(file_name,open(file_name,'rb'))}
MyLogger().getlogger().info('url:%s'%(request_url))
resp=requests.post(request_url,data=request_data,files=files)
虽然最终代码可能看起来很简单,但是其实我费了好大功夫才确认这样是OK的,中间还翻了requests的源码,下面记录一下翻阅源码的过程:
首先,找到post方法的实现,在requests.api.py中:
defpost(url,data=None,json=None,**kwargs): r"""SendsaPOSTrequest. :paramurl:URLforthenew:class:`Request`object. :paramdata:(optional)Dictionary,listoftuples,bytes,orfile-like objecttosendinthebodyofthe:class:`Request`. :paramjson:(optional)jsondatatosendinthebodyofthe:class:`Request`. :param\*\*kwargs:Optionalargumentsthat``request``takes. :return::class:`Response`object :rtype:requests.Response """ returnrequest('post',url,data=data,json=json,**kwargs)
这里可以看到它调用了request方法,咱们继续跟进request方法,在requests.api.py中:
defrequest(method,url,**kwargs): """Constructsandsendsa:class:`Request`. :parammethod:methodforthenew:class:`Request`object:``GET``,``OPTIONS``,``HEAD``,``POST``,``PUT``,``PATCH``,or``DELETE``. :paramurl:URLforthenew:class:`Request`object. :paramparams:(optional)Dictionary,listoftuplesorbytestosend inthequerystringforthe:class:`Request`. :paramdata:(optional)Dictionary,listoftuples,bytes,orfile-like objecttosendinthebodyofthe:class:`Request`. :paramjson:(optional)AJSONserializablePythonobjecttosendinthebodyofthe:class:`Request`. :paramheaders:(optional)DictionaryofHTTPHeaderstosendwiththe:class:`Request`. :paramcookies:(optional)DictorCookieJarobjecttosendwiththe:class:`Request`. :paramfiles:(optional)Dictionaryof``'name':file-like-objects``(or``{'name':file-tuple}``)formultipartencodingupload. ``file-tuple``canbea2-tuple``('filename',fileobj)``,3-tuple``('filename',fileobj,'content_type')`` ora4-tuple``('filename',fileobj,'content_type',custom_headers)``,where``'content-type'``isastring definingthecontenttypeofthegivenfileand``custom_headers``adict-likeobjectcontainingadditionalheaders toaddforthefile. :paramauth:(optional)AuthtupletoenableBasic/Digest/CustomHTTPAuth. :paramtimeout:(optional)Howmanysecondstowaitfortheservertosenddata beforegivingup,asafloat,ora:ref:`(connecttimeout,read timeout) `tuple. :typetimeout:floatortuple :paramallow_redirects:(optional)Boolean.Enable/disableGET/OPTIONS/POST/PUT/PATCH/DELETE/HEADredirection.Defaultsto``True``. :typeallow_redirects:bool :paramproxies:(optional)DictionarymappingprotocoltotheURLoftheproxy. :paramverify:(optional)Eitheraboolean,inwhichcaseitcontrolswhetherweverify theserver'sTLScertificate,orastring,inwhichcaseitmustbeapath toaCAbundletouse.Defaultsto``True``. :paramstream:(optional)if``False``,theresponsecontentwillbeimmediatelydownloaded. :paramcert:(optional)ifString,pathtosslclientcertfile(.pem).IfTuple,('cert','key')pair. :return::class:`Response `object :rtype:requests.Response Usage:: >>>importrequests >>>req=requests.request('GET','https://httpbin.org/get') """ #Byusingthe'with'statementwearesurethesessionisclosed,thuswe #avoidleavingsocketsopenwhichcantriggeraResourceWarninginsome #cases,andlooklikeamemoryleakinothers. withsessions.Session()assession: returnsession.request(method=method,url=url,**kwargs)
这个方法的注释比较多,从注释里其实已经可以看到files参数使用传送文件,但是还是无法知道当需要同时传递参数和文件时该如何处理,继续跟进session.request方法,在requests.session.py中:
defrequest(self,method,url, params=None,data=None,headers=None,cookies=None,files=None, auth=None,timeout=None,allow_redirects=True,proxies=None, hooks=None,stream=None,verify=None,cert=None,json=None): """Constructsa:class:`Request`,preparesitandsendsit. Returns:class:`Response `object. :parammethod:methodforthenew:class:`Request`object. :paramurl:URLforthenew:class:`Request`object. :paramparams:(optional)Dictionaryorbytestobesentinthequery stringforthe:class:`Request`. :paramdata:(optional)Dictionary,listoftuples,bytes,orfile-like objecttosendinthebodyofthe:class:`Request`. :paramjson:(optional)jsontosendinthebodyofthe :class:`Request`. :paramheaders:(optional)DictionaryofHTTPHeaderstosendwiththe :class:`Request`. :paramcookies:(optional)DictorCookieJarobjecttosendwiththe :class:`Request`. :paramfiles:(optional)Dictionaryof``'filename':file-like-objects`` formultipartencodingupload. :paramauth:(optional)Authtupleorcallabletoenable Basic/Digest/CustomHTTPAuth. :paramtimeout:(optional)Howlongtowaitfortheservertosend databeforegivingup,asafloat,ora:ref:`(connecttimeout, readtimeout) `tuple. :typetimeout:floatortuple :paramallow_redirects:(optional)SettoTruebydefault. :typeallow_redirects:bool :paramproxies:(optional)Dictionarymappingprotocolorprotocoland hostnametotheURLoftheproxy. :paramstream:(optional)whethertoimmediatelydownloadtheresponse content.Defaultsto``False``. :paramverify:(optional)Eitheraboolean,inwhichcaseitcontrolswhetherweverify theserver'sTLScertificate,orastring,inwhichcaseitmustbeapath toaCAbundletouse.Defaultsto``True``. :paramcert:(optional)ifString,pathtosslclientcertfile(.pem). IfTuple,('cert','key')pair. :rtype:requests.Response """ #CreatetheRequest. req=Request( method=method.upper(), url=url, headers=headers, files=files, data=dataor{}, json=json, params=paramsor{}, auth=auth, cookies=cookies, hooks=hooks, ) prep=self.prepare_request(req) proxies=proxiesor{} settings=self.merge_environment_settings( prep.url,proxies,stream,verify,cert ) #Sendtherequest. send_kwargs={ 'timeout':timeout, 'allow_redirects':allow_redirects, } send_kwargs.update(settings) resp=self.send(prep,**send_kwargs) returnresp
先大概看一下这个方法,先是准备request,最后一步是调用send,推测应该是发送请求了,所以我们需要跟进到prepare_request方法中,在requests.session.py中:
defprepare_request(self,request): """Constructsa:class:`PreparedRequest`for transmissionandreturnsit.The:class:`PreparedRequest`hassettings mergedfromthe:class:`Request `instanceandthoseofthe :class:`Session`. :paramrequest::class:`Request`instancetopreparewiththis session'ssettings. :rtype:requests.PreparedRequest """ cookies=request.cookiesor{} #BootstrapCookieJar. ifnotisinstance(cookies,cookielib.CookieJar): cookies=cookiejar_from_dict(cookies) #Mergewithsessioncookies merged_cookies=merge_cookies( merge_cookies(RequestsCookieJar(),self.cookies),cookies) #Setenvironment'sbasicauthenticationifnotexplicitlyset. auth=request.auth ifself.trust_envandnotauthandnotself.auth: auth=get_netrc_auth(request.url) p=PreparedRequest() p.prepare( method=request.method.upper(), url=request.url, files=request.files, data=request.data, json=request.json, headers=merge_setting(request.headers,self.headers,dict_class=CaseInsensitiveDict), params=merge_setting(request.params,self.params), auth=merge_setting(auth,self.auth), cookies=merged_cookies, hooks=merge_hooks(request.hooks,self.hooks), ) returnp
在prepare_request中,生成了一个PreparedRequest对象,并调用其prepare方法,跟进到prepare方法中,在requests.models.py中:
defprepare(self, method=None,url=None,headers=None,files=None,data=None, params=None,auth=None,cookies=None,hooks=None,json=None): """Preparestheentirerequestwiththegivenparameters.""" self.prepare_method(method) self.prepare_url(url,params) self.prepare_headers(headers) self.prepare_cookies(cookies) self.prepare_body(data,files,json) self.prepare_auth(auth,url) #Notethatprepare_authmustbelasttoenableauthenticationschemes #suchasOAuthtoworkonafullypreparedrequest. #ThisMUSTgoafterprepare_auth.Authenticatorscouldaddahook self.prepare_hooks(hooks)
这里调用许多prepare_xx方法,这里我们只关心处理了data、files、json的方法,跟进到prepare_body中,在requests.models.py中:
defprepare_body(self,data,files,json=None):
"""PreparesthegivenHTTPbodydata."""
#Checkiffile,fo,generator,iterator.
#Ifnot,runthroughnormalprocess.
#Nottin'onyou.
body=None
content_type=None
ifnotdataandjsonisnotNone:
#urllib3requiresabytes-likebody.Python2'sjson.dumps
#providesthisnatively,butPython3givesaUnicodestring.
content_type='application/json'
body=complexjson.dumps(json)
ifnotisinstance(body,bytes):
body=body.encode('utf-8')
is_stream=all([
hasattr(data,'__iter__'),
notisinstance(data,(basestring,list,tuple,Mapping))
])
try:
length=super_len(data)
except(TypeError,AttributeError,UnsupportedOperation):
length=None
ifis_stream:
body=data
ifgetattr(body,'tell',None)isnotNone:
#Recordthecurrentfilepositionbeforereading.
#Thiswillallowustorewindafileintheevent
#ofaredirect.
try:
self._body_position=body.tell()
except(IOError,OSError):
#ThisdifferentiatesfromNone,allowingustocatch
#afailed`tell()`laterwhentryingtorewindthebody
self._body_position=object()
iffiles:
raiseNotImplementedError('Streamedbodiesandfilesaremutuallyexclusive.')
iflength:
self.headers['Content-Length']=builtin_str(length)
else:
self.headers['Transfer-Encoding']='chunked'
else:
#Multi-partfileuploads.
iffiles:
(body,content_type)=self._encode_files(files,data)
else:
ifdata:
body=self._encode_params(data)
ifisinstance(data,basestring)orhasattr(data,'read'):
content_type=None
else:
content_type='application/x-www-form-urlencoded'
self.prepare_content_length(body)
#Addcontent-typeifitwasn'texplicitlyprovided.
ifcontent_typeand('content-type'notinself.headers):
self.headers['Content-Type']=content_type
self.body=body
这个函数比较长,需要重点关注L52,这里调用了_encode_files方法,我们跟进这个方法:
def_encode_files(files,data):
"""Buildthebodyforamultipart/form-datarequest.
Willsuccessfullyencodefileswhenpassedasadictoralistof
tuples.Orderisretainedifdataisalistoftuplesbutarbitrary
ifparametersaresuppliedasadict.
Thetuplesmaybe2-tuples(filename,fileobj),3-tuples(filename,fileobj,contentype)
or4-tuples(filename,fileobj,contentype,custom_headers).
"""
if(notfiles):
raiseValueError("Filesmustbeprovided.")
elifisinstance(data,basestring):
raiseValueError("Datamustnotbeastring.")
new_fields=[]
fields=to_key_val_list(dataor{})
files=to_key_val_list(filesor{})
forfield,valinfields:
ifisinstance(val,basestring)ornothasattr(val,'__iter__'):
val=[val]
forvinval:
ifvisnotNone:
#Don'tcallstr()onbytestrings:inPy3itallgoeswrong.
ifnotisinstance(v,bytes):
v=str(v)
new_fields.append(
(field.decode('utf-8')ifisinstance(field,bytes)elsefield,
v.encode('utf-8')ifisinstance(v,str)elsev))
for(k,v)infiles:
#supportforexplicitfilename
ft=None
fh=None
ifisinstance(v,(tuple,list)):
iflen(v)==2:
fn,fp=v
eliflen(v)==3:
fn,fp,ft=v
else:
fn,fp,ft,fh=v
else:
fn=guess_filename(v)ork
fp=v
ifisinstance(fp,(str,bytes,bytearray)):
fdata=fp
elifhasattr(fp,'read'):
fdata=fp.read()
eliffpisNone:
continue
else:
fdata=fp
rf=RequestField(name=k,data=fdata,filename=fn,headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
body,content_type=encode_multipart_formdata(new_fields)
returnbody,content_type
OK,到此为止,仔细阅读完这个段代码,就可以搞明白requests.post方法传入的data、files两个参数的作用了,其实requests在这里把它俩合并在一起了,作为post的body。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。