前面几篇文章的案例介绍,都是通过urllib完成的,不知各位发现没,使用起来比较繁琐,1. 创建Request对象,2. 调用urlopen方法,3. 拿到返回后,还需要进行read()并进行解码操作,4. 如果是https请求的话,还需要创建context对象等等,巴拉巴拉,使用还是非常不便的。
接下来就介绍一下Requests库及使用,通过这个库,可以大大提升效率,相信等看完了,会有种相见恨晚的感觉。
那么,Requests是什么呢?Requests就是用python语言基于urllib编写的,采用的是Apache2 Licensed开源协议的HTTP库。
基本使用
安装
pip insitall requests
Get请求
- 引入requests库
- 发送请求
- 接受返回并处理数据
无参
import requests
def main():
resp = requests.get('http://www.baidu.com')
print(resp.text)
if __name__ == '__main__':
main()
url带参数
Requests 可以使用 params 关键字参数,以一个字符串字典来提供这些参数,拼装成url?key=value&key2=value2
形式。
示例:
import requests
def main():
params1 = {'name': '旺财', 'age': 12}
resp = requests.get('http://www.baidu.com', params=params1)
print(resp.url)
print('*' * 50)
params2 = {'name': 'zhangsan', 'age': 34, 'location': ['bj', 'sh']}
resp = requests.get('http://www.baidu.com', params=params2)
print(resp.url)
if __name__ == '__main__':
main()
输出为:
http://www.baidu.com/?name=%E6%97%BA%E8%B4%A2&age=12
**************************************************
http://www.baidu.com/?name=zhangsan&age=34&location=bj&location=sh
可以看到,requests把url 键值参数已经进行的encode操作。
Post请求
键值对参数为一个dict类型数据,调用requests的post方法即可。
def demo_post():
url = 'http://www.baidu.com'
data = {'name': '旺财', 'age': 12}
resp = requests.post(url, data=data)
其它HTTP请求
r = requests.put('http://httpbin.org/put', data = {'key':'value'})
r = requests.delete('http://httpbin.org/delete')
r = requests.head('http://httpbin.org/get')
r = requests.options('http://httpbin.org/get')
响应内容
Requests 会自动解码来自服务器的内容,大多数 unicode 字符集都能被无缝地解码。
请求发出后,Requests 会基于 HTTP 头部对响应的编码作出有根据的推测。当你访问 r.text 时,Requests 会使用其推测的文本编码。
def demo_response():
url = 'http://www.baidu.com'
resp = requests.get(url)
print(resp.status_code)
print(resp.encoding)
print(resp.reason)
print(resp.text)
输出为:
200
ISO-8859-1
OK
<!DOCTYPE html>
200
ISO-8859-1
OK
<!DOCTYPE html> ......
我们可以查看 Requests 使用了什么编码,并且能够使用 r.encoding 属性来改变它:
resp.encoding = 'gbk'
print(resp.encoding) # gbk
如果你改变了编码,每当你访问 r.text ,Request 都将会使用 r.encoding 的新值。你可能希望在使用特殊逻辑计算出文本的编码的情况下来修改编码。比如 HTTP 和 XML 自身可以指定编码。这样的话,你应该使用 r.content 来找到编码,然后设置 r.encoding 为相应的编码。这样就能使用正确的编码解析 r.text 了。
在你需要的情况下,Requests 也可以使用定制的编码。如果你创建了自己的编码,并使用 codecs 模块进行注册,你就可以轻松地使用这个解码器名称作为 r.encoding 的值, 然后由 Requests 来为你处理编码。
二进制响应内容
我们可以以字节的方式访问请求响应体,对于非文本请求:
def demo_image():
# 图片URL
url = 'http://d.hiphotos.baidu.com/image/pic/item/8435e5dde71190efbdcd14ddc21b9d16fdfa60fa.jpg'
resp = requests.get(url)
print(resp.content)
输出为:
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,\x00\x00...
Requests 会自动为你解码 gzip
和 deflate
传输编码的响应数据。
例如,以请求返回的二进制数据创建一张图片,你可以使用如下代码:
def demo_image():
# 图片URL
url = 'http://d.hiphotos.baidu.com/image/pic/item/8435e5dde71190efbdcd14ddc21b9d16fdfa60fa.jpg'
resp = requests.get(url)
image = Image.open(BytesIO(resp.content))
print(image)
JSON响应内容
Requests 中也有一个内置的 JSON 解码器,可以帮助我们处理 JSON 数据:
def demo_json():
response = requests.get('https://api.github.com/events')
print(response.json())
输出为:
[{'id': '7440343408', 'type': 'PushEvent', 'actor': {'id': 35484254,...}]
如果 JSON 解码失败,r.json()
就会抛出一个异常。例如,响应内容是 401 (Unauthorized)
,尝试访问 r.json()
将会抛出ValueError: No JSON object could be decoded
异常。
需要注意的是,成功调用 r.json()
并不意味着响应的成功。有的服务器会在失败的响应中包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 会被解码返回。要检查请求是否成功,请使用r.raise_for_status()
或者检查 r.status_code
是否和你的期望相同。
原始响应内容
在罕见的情况下,可能想获取来自服务器的原始套接字响应,那么可以使用 r.raw
。 并且在初始请求中设置 stream=True
。
r = requests.get('https://api.github.com/events', stream=True)
r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
但一般情况下,应该以下面的模式将文本流保存到文件:
with open(filename, 'wb') as fd:
for chunk in r.iter_content(chunk_size):
fd.write(chunk)
使用 Response.iter_content
将会处理大量你直接使用 Response.raw
不得不处理的。 当流下载时,上面是优先推荐的获取内容方式。 Note that chunk_size can be freely adjusted to a number that may better fit your use cases
。
定制请求头
如果想为请求添加 HTTP 头部,只要简单地传递一个 dict 给 headers 参数就可以了。
def demo_add_headers():
url = 'http://www.baidu.com'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
}
resp = requests.get(url, headers=headers)
注意: 定制 header 的优先级低于某些特定的信息源,例如:
- 如果在 .netrc 中设置了用户认证信息,使用 headers= 设置的授权就不会生效。而如果设置了 auth= 参数,
.netrc
的设置就无效了。 - 如果被重定向到别的主机,授权 header 就会被删除。
- 代理授权 header 会被 URL 中提供的代理身份覆盖掉。
- 在我们能判断内容长度的情况下,header 的 Content-Length 会被改写
更进一步讲,Requests 不会基于定制 header 的具体情况改变自己的行为。只不过在最后的请求中,所有的 header 信息都会被传递进去。
注意: 所有的 header 值必须是 string、bytestring 或者 unicode。尽管传递 unicode header 也是允许的,但不建议这样做。
更加复杂的post请求
上面介绍了通过传递一个dict类型的data,完成一般的post请求。
很多时候想要发送的数据并非编码为表单形式的。如果你传递一个 string 而不是一个 dict,那么数据会被直接发布出去。
def demo_fuza_post():
url = 'http://www.baidu.com'
data = {'name': '旺财', 'age': 12}
print(json.dumps(data)) #{"name": "\u65fa\u8d22", "age": 12}
resp = requests.post(url, data=json.dumps(data))
此处除了可以自行对 dict 进行编码,还可以使用 json 参数直接传递,然后它就会被自动编码。这是 2.4.2 版的新加功能:
resp = requests.post(url, json=data)
post上传文件
Requests 使得上传多部分编码文件变得很简单:
def demo_upload_file():
url = 'http://www.baidu.com'
files = {'file': open('test.ppt', 'rb')}
response = requests.post(url, files=files)
其实封装数据为:
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}
还可以显式地设置文件名,文件类型和请求头:
files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
此外,还可以发送作为文件来接收的字符串:
files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
发送请求为:
{
...
"files": {
"file": "some,data,to,send\\nanother,row,to,send\\n"
},
...
}
如果你发送一个非常大的文件作为 multipart/form-data
请求,你可能希望将请求做成数据流。默认下 requests 不支持, 但有个第三方包requests-toolbelt
是支持的。你可以阅读toolbelt
文档 来了解使用方法。
强烈建议用二进制模式(binary mode
)打开文件。这是因为 Requests 可能会试图为你提供Content-Length header
,在它这样做的时候,这个值会被设为文件的字节数(bytes
)。如果用文本模式(text mode
)打开文件,就可能会发生错误。
状态响应码
我们可以检测当前请求返回的状态码。
def demo_status_code():
resp = requests.get('http://www.baidu.com')
print(resp.status_code)#200
为方便引用,Requests还附带了一个内置的状态码查询对象:
print(resp.status_code == requests.codes.ok)
如果发送了一个错误请求(一个 4XX 客户端错误,或者 5XX 服务器错误响应),我们可以通过 Response.raise_for_status()
来抛出异常:
def demo_status_code():
resp = requests.get('http://www.snsd.com/404/aaa.html')
print(resp.status_code)
print(resp.status_code == requests.codes.ok)
resp.raise_for_status()
输出为:
404
False
Traceback (most recent call last):
File "/Users/***/01-demo.py", line 88, in <module>
main()
File "/Users/***/01-demo.py", line 8, in main
demo_status_code()
File "/Users/***/01-demo.py", line 15, in demo_status_code
resp.raise_for_status()
File "/Users/***/.virtualenvs/env3.6/lib/python3.6/site-packages/requests/models.py", line 935, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: http://www.snsd.com/404/aaa.html
响应头
我们可以查看以一个 Python 字典形式展示的服务器响应头:
def demo_resp_headers():
resp = requests.get('http://www.baidu.com')
print(resp.headers)
输出为:
{'Server': 'bfe/1.0.8.18', 'Date': 'Tue, 27 Mar 2018 10:21:41 GMT', 'Content-Type': 'text/html', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:36 GMT', 'Transfer-Encoding': 'chunked', 'Connection': 'Keep-Alive', 'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Pragma': 'no-cache', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Content-Encoding': 'gzip'}
我们可以使用任意大写形式来访问这些响应头字段:
print(resp.headers['Server'])
print(resp.headers.get('Content-Type'))
Cookie
如果响应中包含Cookie,可以方便的访问他们。
def demo_cookies():
resp = requests.get('http://www.baidu.com')
for item in resp.cookies.items():
print(item)
print(resp.cookies)
print(resp.cookies.get('BDORZ'))
输出为:
('BDORZ', '27315')
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
27315
要想发送你的cookies到服务器,可以使用 cookies 参数:
cookies = dict(cookies_are='working')
resp = requests.get('http://www.baidu.com', cookies=cookies)
# {"cookies": {"cookies_are": "working"}}
Cookie 的返回对象为 RequestsCookieJar,它的行为和字典类似,但界面更为完整,适合跨域名跨路径使用。你还可以把 Cookie Jar 传到 Requests 中:
>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'http://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'
重定向与请求历史
默认情况下,除了 HEAD, Requests 会自动处理所有重定向。
可以使用响应对象的 history 方法来追踪重定向。
Response.history 是一个 Response 对象的列表,为了完成请求而创建了这些对象。这个对象列表按照从最老到最近的请求进行排序。
例如,Github 将所有的 HTTP 请求重定向到 HTTPS:
>>> r = requests.get('http://github.com')
>>> r.url
'https://github.com/'
>>> r.status_code
200
>>> r.history
[<Response [301]>]
如果你使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通过 allow_redirects 参数禁用重定向处理:
>>> r = requests.get('http://github.com', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]
如果你使用了 HEAD,你也可以启用重定向:
>>> r = requests.head('http://github.com', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[<Response [301]>]
超时
可以告诉 requests 在经过以 timeout 参数设定的秒数时间之后停止等待响应。基本上所有的生产代码都应该使用这一参数。如果不使用,你的程序可能会永远失去响应:
>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
timeout
仅对连接过程有效,与响应体的下载无关。timeout
并不是整个下载响应的时间限制,而是如果服务器在timeout
秒内没有应答,将会引发一个异常(更精确地说,是在 timeout
秒内没有从基础套接字上接收到任何字节的数据时)If no timeout is specified explicitly, requests do not time out
。
错误与异常
遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 ConnectionError
异常。
如果 HTTP 请求返回了不成功的状态码, Response.raise_for_status()
会抛出一个 HTTPError
异常。
若请求超时,则抛出一个 Timeout
异常。
若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects
异常。
所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException
。