介绍
requests模块是一个专门用来发送http请求的模块
如何发送请求
import requests
"""
使用requests模块发送请求非常简单
首先请求有get、post、delete、put、head
这些请求直接通过requests来调用即可
"""
# 这样就发送了一个get请求
res = requests.get("http://www.baidu.com")
# res就是我们发送请求之后,得到的返回值,这是一个Response对象。里面包含了很多的属性
"""
url:我们请求的url
status_code:返回的状态码
reason:一般网站中跟在状态码后边的那个,比如200 ok,404 not found
headers:返回的头部信息
cookies:返回的cookie
encoding:返回网站的编码
text:网页的html
content:网页的html(字节)
"""
print(res.url) # http://www.baidu.com/
print(res.status_code) # 200
print(res.reason) # OK
print(res.headers) # {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'Keep-Alive', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Wed, 26 Jun 2019 12:22:26 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:36 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Transfer-Encoding': 'chunked'}
print(res.cookies) # <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
print(res.encoding) # ISO-8859-1
# 调用res.text可以直接打印html信息,但是由于可能会出现乱码
# 因此需要加上一个res.encoding = res.apparent_encoding
# 因为实际上获得的最原始的流数据,还是字节的形式,也就是我们上面说的content。
# 只不过requests为了调用者方便,从而进行了封装,将content解码成了text
# 但是使用res.encoding进行解码,有可能会造成编码错误,于是将apparent_encoding赋值给了encoding
# 至于apparent_encoding是什么?
# 实际上是根据python的一个可以对字节流所使用的编码进行检测的第三方模块(chardet),对content检测所得到的编码
from chardet import detect
# 可以看到检测出来是utf-8,如果我们再用ISO-8859-1那么可能会得到乱码
print(detect(res.content).get("encoding")) # utf-8
# 实际上,这在requests内部也是这么做的
"""
@property
def apparent_encoding(self):
return chardet.detect(self.content)['encoding']
"""
# 至于其他的请求也是一样的
# 实际上这些方法在requests中,都统一调用一个方法,都调用requests.request()
"""
requests.get(url) == requests.request('get', url)
requests.post(url) == requests.request('post', url)
requests.head(url) == requests.request('head', url)
requests.delete(url) == requests.request('delete', url)
requests.put(url) == requests.request('put', url)
"""
# 为了方便,创建了这些对应的api
传递url参数
有些时候我们相对url添加一些查询字符串(query string),比如我们百度搜索python,那么就可以输入https://www.baidu.com/s?wd=python,但是这样显然不够人性化,因为requests就是号称for humans。
import requests
# 调用get方法时,可以加上一个参数params,以字典的形式传入
res = requests.get("http://www.baidu.com/s", params={"wd": "python"})
# 可以看到,自动帮我们拼接在一起了
print(res.url) # http://www.baidu.com/s?wd=python
# 当然也可以传递多个参数
res = requests.get("http://httpbin.org/get", params={"k1": "v1", "k2": ["v2", "v3"]})
print(res.url) # http://httpbin.org/get?k1=v1&k2=v2&k2=v3
响应内容
import requests
import re
res = requests.get("http://www.baidu.com/s", params={"wd": "python"})
# res.text可以打印出返回的html页面内容,也就是单击右键查看网页源代码所得到的内容
# 但是之前说过requests,是基于http头部响应的编码进行解码,也就是res.encoding
# 但是返回的内容的编码并不一定是res.encoding,因此我们可以在获取text之前改变编码,然后在获取text的时候就会使用我们指定的编码来对content进行解码了
res.encoding = res.apparent_encoding
text = res.text
match = re.findall(r".{20}python.{20}", text)
for i in match:
print(i)
"""
节选一部分输出
ta-tools='{"title":"python吧-百度贴吧--python学习交流基地
r=baidu&fm=sc&query=python&qid=e49f6f960022996
><th><a href="/s?wd=python%E8%87%AA%E5%AD%A6&r
000013&rsp=0&f=1&oq=python&ie=utf-8&usm=1&rsv_
WRNBwvCY6eGkEholvw">python自学</a></th><td></td>
000001&rsp=1&f=1&oq=python&ie=utf-8&usm=1&rsv_
WRNBwvCY6eGkEholvw">python为什么叫爬虫</a></th><td><
9F%E6%95%99%E7%A8%8Bpython&rsf=1000012&rsp=2&f
wvCY6eGkEholvw">菜鸟教程python</a></th></tr><tr><t
&rsf=8&rsp=3&f=1&oq=python&ie=utf-8&usm=1&rsv_
"""
二进制响应内容
实际上,也正如我们之前说的,我们还可以以二进制的格式获取响应内容。比如图片,不像文本,图片这种内容只能以二进制的格式返回。然而我们之前也说过,即便是文本,仍是以二进制返回的,只不过为了文本方便阅读,在requests内部又封装了一个text,可以方便我们获取文本内容
import requests
res = requests.get("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561565986554&di=028d5ad55109447f9a136ad7654c7268&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fa79025d0a0789582f90110411355bffd81ccdac8.jpg")
# 这个url是百度图片的一张我太太蕾姆的照片,如果这个时候再用text打印,会是一大堆乱码
# 因为我们可以以二进制的格式写入文件里面
with open("太太蕾姆.jpg", "wb") as f:
f.write(res.content)
json响应内容
之前说过返回的是二进制的流数据,text是由content进行解码得到的。那么同理requests中json数据(如果符合json规范的话),也是由content转化得来的。这里不再代码演示了,和text一样,只不过由于没有@property,因此需要调用res.json()。
但需要注意的是,如果返回的内容不符合json格式,导致JSON解码失败,那么 res.json() 就会抛出一个异常。然而成功调用 r.json() 并**不**意味着响应的成功。有的服务器会在失败的响应中包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 会被解码返回。要检查请求是否成功,请使用 r.raise_for_status() 或者检查 r.status_code 是否和你的期望相同。
原始响应内容
在罕见的情况下,你可能会想获取来自服务器的原始套接字响应,那么可以使用res.raw。如果真的想这么做,那么请加上参数stream=True,否则得到的是一个空字节串。
基本上不用这种方式获取数据
import requests
res = requests.get("https://api.github.com/events", stream=True)
raw = res.raw
# 可以对raw进行读取
print(raw.read(10)) # b'x1fx8bx08x00x00x00x00x00x00x03'
定制请求头
很多网站,都设置了反爬虫机制。最常用的反爬虫机制就是,判断请求头,如果请求头不是浏览器的请求头,会直接将你屏蔽掉。
import requests
res = requests.get("http://www.baidu.com")
# 我们可以通过res.request.headers 来看看请求头是什么?
# 之前说的res.headers指的是服务器端返回的http报头
# 而这里的res.request.headers指的是我们传过去的请求头
print(res.request.headers) # {'User-Agent': 'python-requests/2.19.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
# 可以看到默认是'User-Agent': 'python-requests/2.19.1'
# 这是百度,用来搜索的,所以没有做这种限制。本身就是爬虫,再搞反爬,也太。。。。
# 但是其他网站,比如拉钩网,基本上百分百屏蔽
# 所以我们可以加上一个请求头,也是通过字典来传值
res = requests.get("http://www.baidu.com", headers={"User-Agent": "emmmmmmmm"})
# 我这里是随便指定的,真正爬虫的时候,从浏览里拷贝一下就行
print(res.request.headers) # {'User-Agent': 'emmmmmmmm', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
post请求
上面都是关于get请求的,下面我们介绍post请求。post请求一般是用于表单提交的,比如登录页面,我们通过脚本来模拟表单登录
import requests
payload = {"username": "komeijisatori", "password": "123456", "remember": "1"}
# 通过参数data指定
res = requests.post("http://httpbin.org/post", data=payload)
print(res.text)
"""
{
"args": {},
"data": "",
"files": {},
"form": {
"password": "123456",
"remember": "1",
"username": "komeijisatori"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "49",
...
...
...
...
"""
但有些时候,我们想发送的数据并不是以表单形式提交的,也就是需要传递一个string,但是格式是字典格式的。想到了什么,json。
import requests
payload = {"some": "data"}
# 这时候指定参数json即可
res = requests.post("https://api.github.com/some/endpoint", json=payload)
# 或者手动转化为json数据也可以
import json
res = requests.post("https://api.github.com/some/endpoint", json=json.dumps(payload))
post提交文件
如果我们需要提交文件的话,那么requests也是支持的
import requests
payload = {"some": "data"}
res = requests.post("http://httpbin.org/post", files={"file": open("1.txt", "rb")})
print(res.text)
"""
{
"args": {},
"data": "",
"files": {
"file": "this is a file"
},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "155",
"Content-Type": "multipart/form-data; boundary=e34f63359be140870846d7fd5340daff",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.19.1"
},
"json": null,
"origin": "120.244.41.108, 120.244.41.108",
"url": "https://httpbin.org/post"
}
"""
# 此外还可以显示的指定文件名
res = requests.post("http://httpbin.org/post", files={"file": ("2.txt", open("1.txt", "rb"))})
print(res.text)
"""
{
"args": {},
"data": "",
"files": {
"file": "this is a file"
},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "155",
"Content-Type": "multipart/form-data; boundary=ab3d5c2226580ff643ca947c0b4e2cee",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.19.1"
},
"json": null,
"origin": "120.244.41.108, 120.244.41.108",
"url": "https://httpbin.org/post"
}
"""
可以看到,将我们文件的内容打印了出来。但是上传文件的话,我们推荐一种更好的方式
import requests
import os
from requests_toolbelt.multipart.encoder import MultipartEncoder
multipart_encoder = MultipartEncoder(
fields={
# 表单所需要的参数直接填在fields里面即可
# 包括上传的文件也是
'parentId': "xxx",
'size': f'{os.stat("1.png").st_size}',
'lParentId': "xxxxx",
'isDir': "false",
"fileSize": f'{os.stat("1.png").st_size}',
"fileName": "1.png",
"userId": "admin",
"directCheck": "true",
"mode": "0",
'file': ('file', open("1.png", 'rb'))
},
boundary="----WebKitFormBoundaryIHASM5Ndp6lUZPA8"
)
session = requests.session()
# 设置Content-Type
session.headers["Content-Type"] = multipart_encoder.content_type
session.post(
"http://xxx.com",
data=multipart_encoder
)
响应状态码
我们还可以检测返回来的状态码
import requests
res = requests.get("https://www.baidu.com")
print(res.status_code) # 200
# 如果发送了一个错误请求,那么可以调用raise_for_status来抛出异常
# 由于我们这里返回正常,所以打印个None
print(res.raise_for_status()) # None
bad_res = requests.get("http://httpbin.org/status/404")
print(bad_res.status_code) # 404
try:
bad_res.raise_for_status()
except Exception as e:
import traceback
print(traceback.format_exc())
"""
Traceback (most recent call last):
File "D:/mashiro/python模块/requests模块.py", line 14, in <module>
bad_res.raise_for_status()
File "C:python37libsite-packages
equestsmodels.py", line 939, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: NOT FOUND for url: http://httpbin.org/status/404
"""
响应头
import requests
res = requests.get("https://www.baidu.com")
print(res.headers)
"""
{'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform',
'Connection': 'Keep-Alive',
'Content-Encoding': 'gzip',
'Content-Type': 'text/html',
'Date': 'Wed, 26 Jun 2019 15:45:05 GMT',
'Last-Modified': 'Mon, 23 Jan 2017 13:23:55 GMT',
'Pragma': 'no-cache',
'Server': 'bfe/1.0.8.18',
'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/',
'Transfer-Encoding': 'chunked'}
"""
print(res.headers["Content-Type"]) # text/html
cookie
import requests
res = requests.get("https://www.baidu.com")
cookies = res.cookies
print(cookies.get_dict()) # {'BDORZ': '27315'}
# 此外还可以将得到cookie作为参数传进去,cookies={}
# 得到的cookies是一个 RequestsCookieJar类型,行为和字典类型,但是功能比字典更多,并且可以跨路径使用
from requests.cookies import RequestsCookieJar
jar = RequestsCookieJar()
jar.set("cookie1_name", "cookie1_value")
jar.set("cookie2_name", "cookie2_value")
res = requests.get("http://httpbin.org/cookies", cookies=jar)
print(res.text)
"""
{
"cookies": {
"cookie1_name": "cookie1_value",
"cookie2_name": "cookie2_value"
}
}
"""
重定向与请求历史
有些时候当我们访问一个域名时,这个域名已经被修改了,但是呢?又怕你不知道,因此你访问以前的域名依旧可以,但是会被重定向到新的域名。
重定向分为暂时性重定向和永久性重定向。
暂时性重定向:code为302,当我们发表评论时,但是没有登录,此时就会被暂时重定向到登录页面
永久性重定向:code为301,比如我们访问http://www.taobao.com,但是这个域名已经废弃了,因此会被永久性重定向到https://www.taobao.com
调用res.history可以查看历史
import requests
res = requests.get("http://www.taobao.com")
print(res.status_code) # 200
print(res.history) # [<Response [301]>]
此外我们还可以禁止重定向,只需要指定allow_redirects=False即可
import requests
res = requests.get("http://www.taobao.com", allow_redirects=False)
print(res.status_code) # 301
print(res.history) # []
超时
告诉requests,如果在规定的时间内一直没有连接上的话,那么就放弃连接,可以通过timeout指定
import requests
"""
timeout可以是一个元组,比如(m, n)
第一个元素表示发送连接请求所使用的最大时间,如果m秒内连接没有建立成功,则放弃
第二个元素则是等待服务器响应的所使用的最大时间,如果m秒内连接建立好了,但是n秒内服务器一直没有返回数据,也放弃
这两种情况都会抛出一个Timeout异常
如果timeout是一个int,比如m,那么两者的最大时间都是m
"""
try:
res = requests.get("https://www.google.com", timeout=(2, 3))
except requests.exceptions.Timeout as e:
print(e) # HTTPSConnectionPool(host='www.google.com', port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.VerifiedHTTPSConnection object at 0x000001E44616C320>, 'Connection to www.google.com timed out. (connect timeout=2)'))
错误与异常
- ConnectionError:遇到网络问题,如DNS查询失败,拒绝连接时
- HTTPError:如果http请求返回了不成功的状态码,res.raise_for_status会抛出一个异常
- Timeout:若请求超时,则抛出此异常
- TooManyRedirects:若请求超过了最大重定向次数,则抛出此异常
- requests.exceptions.RequestException:所以requests显示抛出的异常,都继承自该异常
会话对象
import requests
"""
之前我们说过,requests里面的get、post等方法,底层都是调用的request方法
但是request调用的是谁呢?
去掉注释的话,长这样
def request(method, url, **kwargs):
with sessions.Session() as session:
return session.request(method=method, url=url, **kwargs)
可以看到首先是调用Session这个类实例化一个session对象,然后调用session下的request方法。
session(会话对象),是一个类实例化的对象,那么将我们的一些参数给保存起来
会话对象可以让我们跨请求保持某些参数,它也会在同一个Session实例发出的所有请求之间保持cookie,期间使用urllib3的connection pooling功能
因为requests主要是基于urllib3的。
如果我们向同一个主机发送多次请求,底层的tcp连接会被重用,从而带来显著的性能提升
"""
session = requests.Session() # 也可以调用requests.session,两者是一样的
session.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
res1 = session.get("http://httpbin.org/cookies")
print(res1.text)
"""
{
"cookies": {
"sessioncookie": "123456789"
}
}
"""
res2 = requests.get("http://httpbin.org/cookies")
print(res2.text)
"""
{
"cookies": {}
}
"""
可以看到,当我们使用res1.text的时候,能够打印出cookie,但是使用res2.text,就打印不出cookie了,这是为什么?
当我们执行session.get('http://httpbin.org/cookies/set/sessioncookie/123456789')的时候,会返回一个cookie,这个cookie已经被保存在会话里了,因此当我们再次使用session(会话)发送请求的时候会自动将这个cookie带过去。但是当我们使用requests.get的时候,则是创建一个新会话。因此只能先拿到cookie,然后再显示的将cookie传过去,但如果是会话的话就不用了,因为会自动的将cookie保存起来
此外会话还可以为请求方法提供缺省数据
import requests
session = requests.Session()
"""
Session这个类里面,有很多的属性,比如headers,auth,cookies,proxies等等
但是没有在__init__中定义,即便如此我们仍然可以通过属性访问的方式来添加
"""
session.headers.update({"User-Agent": "kubernetes"})
# 此时调用get方法的时候,会将headers带过去
res = session.get("http://www.baidu.com")
print(res.request.headers) # {'User-Agent': 'kubernetes', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
# 但是如果我们在请求的方法中又传了相应的参数,那么请求方法中的参数,会覆盖会话当中的参数
res = session.get("http://www.baidu.com", headers={"User-Agent": "docker", "name": "satori"})
print(res.request.headers)
"""
{'User-Agent': 'docker', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*',
'Connection': 'keep-alive', 'name': 'satori',
'Cookie': 'BAIDUID=B2A159333ED50D72BC07D66F1040A68E:FG=1; BIDUPSID=B2A159333ED50D72BC07D66F1040A68E; H_PS_PSSID=26525_1447_21090_29135_29238_28519_29099_29131_28832_29221_26350_29072; PSTM=1561642196; delPer=0; BDSVRTM=0; BD_HOME=0'}
"""
# 可以看到kubernetes被docker覆盖了,而且我们设置的name也出现了。最关键的是,居然还出现了cookie,这是为什么?
# 因为在第一次访问百度的时候,返回给我们一个cookie,第二次访问的时候,使用的是同一个session,所以会将上一步得到的cookie带过去
res = session.get("http://www.baidu.com")
print(res.request.headers)
"""
{'User-Agent': 'kubernetes', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive',
'Cookie': 'BAIDUID=AB0351E419827120EABABE13ECC4C9ED:FG=1; BIDUPSID=AB0351E419827120EABABE13ECC4C9ED; H_PS_PSSID=1422_21110_29135_29237_28519_29098_29131_28838_29220_22160; PSTM=1561642425; delPer=0; BDSVRTM=0; BD_HOME=0'}
"""
# 这里问题又来了,可以看到,我们第三次访问的时候,User-Agent又变回了kubernetes,而且name也没了,cookie还在。这是为什么?
# 因为方法中的参数不会跨请求保持,我们在请求的时候所设置的参数,仅仅对那一次请求有效。如果之后不指定,那么请求还是会使用会话里面的,所以是这个结果。
并且,由于Session类实现了__enter__和__exit__方法,所以还可以使用with语句
请求与响应对象
在任何时候,只要进行了类似于requests.get的调用,都在做两件主要的事情。第一,在构建一个Request对象,该对象被发送到某个服务器上请求或查询一些资源。第二,一旦requests得到一个从服务器返回的相应就会产生一个Response对象、该响应对象包含服务器返回的所有信息,也包含原来创建的Request对象。
import requests
session = requests.Session()
# 先来看看session.request的源码
# 值得一提的是,requests.get、post等请求方法,都是调用的requests.request
# 但是requests.requests实际上也是创建了一个session对象,调用session对象下的request方法,只不过这个session对象在这一次请求之后就没有了,因为我们没有保存
# 而session对象下也有get、post等方法,当然调用也是session.request。
# 因此无论是通过requests主模块、还是创建的session对象,其所调用的get、post、delete等请求方法,最终调用的都是session对象下的request方法
"""
def request(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):
# Create the Request.创建一个Request对象
# 里面主要包装我们的一些参数
req = Request(
method=method.upper(),
url=url,
headers=headers,
files=files,
data=data or {},
json=json,
params=params or {},
auth=auth,
cookies=cookies,
hooks=hooks,
)
# 准备的请求,后面会说
prep = self.prepare_request(req)
proxies = proxies or {}
settings = self.merge_environment_settings(
prep.url, proxies, stream, verify, cert
)
# Send the request.
send_kwargs = {
'timeout': timeout,
'allow_redirects': allow_redirects,
}
send_kwargs.update(settings)
# 发送请求,得到Response对象
resp = self.send(prep, **send_kwargs)
return resp
"""
res = session.get("https://www.baidu.com")
# 获取服务器返回的头部信息
print(res.headers)
"""
{'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform',
'Connection': 'Keep-Alive',
'Content-Encoding': 'gzip',
'Content-Type': 'text/html',
'Date': 'Thu, 27 Jun 2019 14:07:13 GMT',
'Last-Modified': 'Mon, 23 Jan 2017 13:23:55 GMT',
'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18',
'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/',
'Transfer-Encoding': 'chunked'}
"""
# 还可以获取发送给服务器的请求的头部
print(res.request.headers) # {'User-Agent': 'python-requests/2.19.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
准备的请求(prepared request)
当从api或者会话调用中收到一个Response对象时,request属性实际上使用了Prepared Request。因此在发送请求之前,我们可以对body、header做一些额外的处理
import requests
session = requests.Session()
req = requests.Request("get", url="https://www.baidu.com")
prepped = req.prepare()
# 可以做一些额外的处理,这里处理请求头
prepped.headers["User-Agent"] = "satori"
# 调用session的send方法,这个requests模块内部的session.request本质上也是这么做的,返回的就是Response对象
res = session.send(prepped)
print(res.request.headers) # {'User-Agent': 'satori'}
print(res.cookies.get_dict()) # {'BIDUPSID': '6A947E75D63F889E10C89D7E6A2870AA', 'PSTM': '1561645151', 'BD_NOT_HTTPS': '1'}
print(res.url) # https://www.baidu.com/
然而,上述代码会失去 Requests Session 对象的一些优势, 尤其session级别的状态,例如 cookie 就不会被应用到你的请求上去。要获取一个带有状态的PreparedRequest, 可以使用 Session.prepare_request 取代Request.prepare的调用.
只需要将prepped = req.prepare()换成prepped = session.prepare_request()即可
不过感觉不常用,至少我基本上没怎么用过
ssl证书验证
requests可以为HTTPS请求验证ssl证书,就像web浏览器一样。ssl验证默认是开启的,如果证书验证失败,那么requests会抛出一个SSLError,对于那些没有设置ssl的,如果验证失败那么可以在请求方法中加上verify=False,表示不验证
也可以为 verify
传入 CA_BUNDLE 文件的路径,或者包含可信任 CA 证书文件的文件夹路径
或者让其保持在会话中
s = requests.Session()
s.verify = '证书路径'
# 值得一提的是
# 如果 verify 设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理。
客户端证书(不常用)
也可以指定一个本地证书用作客户端证书,可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组:
requests.get('url', cert=('client.cert', 'client.key'))
或者让其保持在会话中
s = requests.Session()
s.cert = 'client.cert'
如果指定了一个错误的证书,那么同样会引发一个SSLError
钩子函数(不常用)
import requests
def foo(response, *args, **kwargs):
print(response.url)
# 绑定一个钩子函数,那么当请求执行完毕后,会触发foo函数,key要指定为response
res = requests.get("http://www.baidu.com", hooks={"response": foo})
# http://www.baidu.com/
自定义身份验证
Requests 允许你使用自己指定的身份验证机制。
任何传递给请求方法的 auth 参数的可调用对象,在请求发出之前都有机会修改请求。
自定义的身份验证机制是作为 requests.auth.AuthBase 的子类来实现的,也非常容易定义。Requests 在 requests.auth 中提供了两种常见的的身份验证方案: HTTPBasicAuth 和 HTTPDigestAuth 。
假设我们有一个web服务,仅在 X-Pizza 头被设置为一个密码值的情况下才会有响应。虽然这不太可能,但就以它为例好了。
from requests.auth import AuthBase
import requests
class PizzaAuth(AuthBase):
def __init__(self, username):
self.username = username
def __call__(self, r):
r.headers['X-Pizza'] = self.username
return r
requests.get('http://pizzabin.org/admin', auth=PizzaAuth('fuck_you'))
流式请求
import requests
# 使用Response.iter_lines(),可以很方便地对流式 API进行迭代。简单地设置stream为True便可以使用iter_lines进行迭代
res = requests.get("https://www.baidu.com", stream=True)
res.encoding = res.apparent_encoding
# 加上decode_unicode=True表示默认解码, 否则得出的是字节类型
# 而且也要加上res.encoding = res.apparent_encoding,否则会是乱码
# 还可以指定chunk_size,默认是512
for line in res.iter_lines(decode_unicode=True):
print(line[: 55])
"""
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-t
</script> <a href=//www.baidu.com/more/
"""
# 除了iter_lines,还有一个iter_content,两者类似,但是这里必须要指定chunk_size,因为默认是1
for line in res.iter_content(chunk_size=200, decode_unicode=True):
print(line[: 100])
"""
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charse
l=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baid
v id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img h
action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden n
t type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"
pan class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </for
/a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.c
/a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.b
ame=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz
= "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
</scrip
/a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度
href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedbac
</p> </div> </div> </div> </body> </html>
"""
代理
如果需要使用代理,可以通过为任意请求方法提供 proxies
参数来配置单个请求
import requests
proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)
若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 语法:
proxies = {
"http": "http://user:pass@10.10.1.10:3128/",
}
要为某个特定的连接方式或者主机设置代理,使用 scheme://hostname 作为 key, 它会针对指定的主机和连接方式进行匹配。
proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
注意,代理 URL 必须包含连接方式。
socks
除了基本的http代理,requests还支持socks代理。如果要使用的话,需要安装第三方库,pip install requests[socks]。
一般我们使用socks都是获取接口数据,在python中还有一个比较好用的模块,叫suds。
from suds.client import Client
client = Client(url="xxx")
res = getattr(client.service, "func")(*params)
但是在requests中,使用socks代理和http代理是一样的
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}