Python 的標(biāo)準(zhǔn)庫(kù) urllib 提供了大部分 HTTP 功能,但使用起來(lái)較繁瑣。通常,我們會(huì)使用另外一個(gè)優(yōu)秀的第三方庫(kù):Requests,它的標(biāo)語(yǔ)是:Requests: HTTP for Humans。
Requests 提供了很多功能特性,幾乎涵蓋了當(dāng)今 Web 服務(wù)的需求,比如:
下面,我們將從以下幾個(gè)方面介紹 Requests 庫(kù):
我們知道,一個(gè) HTTP 請(qǐng)求由三部分構(gòu)成:
如圖所示:

Requests 提供了幾乎所有 HTTP 動(dòng)詞的功能:GET、OPTIONS、HEAD、POST、PUT、PATCH、DELETE,另外,它提供了 headers 參數(shù)讓我們根據(jù)需求定制請(qǐng)求頭。
使用 Requests 發(fā)送一個(gè)請(qǐng)求很方便,比如:
import requests
r = requests.get("http://httpbin.org/get")
r = requests.post("http://httpbin.org/post")
r = requests.put("http://httpbin.org/put")
r = requests.delete("http://httpbin.org/delete")
r = requests.head("http://httpbin.org/get")
r = requests.options("http://httpbin.org/get")
下面,我們重點(diǎn)講一下 GET 請(qǐng)求,POST 請(qǐng)求和定制請(qǐng)求頭。
使用 Requests 發(fā)送 GET 請(qǐng)求非常簡(jiǎn)單,如下:
import requests
r = requests.get("http://httpbin.org/get")
在有些情況下,URL 會(huì)帶參數(shù),比如 https://segmentfault.com/blogs?page=2,這個(gè) URL 有一個(gè)參數(shù) page,值為 2。Requests 提供了 params 關(guān)鍵字參數(shù),允許我們以一個(gè)字典來(lái)提供這些參數(shù),比如:
import requests
payload = {'page': '1', 'per_page': '10'}
r = requests.get("http://httpbin.org/get", params=payload)
通過(guò)打印該 URL,我們可以看到 URL 已被正確編碼:
>>> print r.url
http://httpbin.org/get?per_page=10&page=1
需要注意的是字典里值為 None 的鍵不會(huì)被添加到 URL 的查詢(xún)字符串中。
使用 Requests 發(fā)送 POST 請(qǐng)求也很簡(jiǎn)單,如下:
import requests
r = requests.post("http://httpbin.org/post")
通常,我們?cè)诎l(fā)送 POST 請(qǐng)求時(shí)還會(huì)附上數(shù)據(jù),比如發(fā)送編碼為表單形式的數(shù)據(jù)或編碼為 JSON 形式的數(shù)據(jù),這時(shí),我們可以使用 Requests 提供的 data 參數(shù)。
通過(guò)給 data 參數(shù)傳遞一個(gè) dict,我們的數(shù)據(jù)字典在發(fā)出請(qǐng)求時(shí)會(huì)被自動(dòng)編碼為表單形式,比如:
import requests
payload = {'page': 1, 'per_page': 10}
r = requests.post("http://httpbin.org/post", data=payload)
看看返回的內(nèi)容(省略了部分?jǐn)?shù)據(jù)):
>>> print r.text
{
...
"form": {
"page": "1",
"per_page": "10"
},
...
}
如果給 data 參數(shù)傳遞一個(gè) string,我們的數(shù)據(jù)會(huì)被直接發(fā)布出去,比如:
import json
import requests
payload = {'page': 1, 'per_page': 10}
r = requests.post("http://httpbin.org/post", data=json.dumps(payload))
看看返回:
>>> print r.text
{
"args": {},
"data": "{\"per_page\": 10, \"page\": 1}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "27",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.9.1"
},
"json": {
"page": 1,
"per_page": 10
},
"origin": "13.75.42.240",
"url": "http://httpbin.org/post"
}
在上面,我們自行對(duì) dict 進(jìn)行了編碼,這種方式等價(jià)于使用 json 參數(shù),而給它傳遞 dict,如下:
import requests
payload = {'page': 1, 'per_page': 10}
r = requests.post("http://httpbin.org/post", json=payload)
這種做法跟上面的做法是等價(jià)的,數(shù)據(jù)在發(fā)出時(shí)會(huì)被自動(dòng)編碼。
有時(shí),我們需要為請(qǐng)求添加 HTTP 頭部,我們可以通過(guò)傳遞一個(gè) dict 給 headers 參數(shù)來(lái)實(shí)現(xiàn)。比如:
import requests
url = 'http://httpbin.org/post'
payload = {'page': 1, 'per_page': 10}
headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}
r = requests.post("http://httpbin.org/post", json=payload, headers=headers)
發(fā)送到服務(wù)器的請(qǐng)求的頭部可以通過(guò) r.request.headers 訪(fǎng)問(wèn):
>>> print r.request.headers
{'Content-Length': '27', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 'Connection': 'keep-alive', 'Content-Type': 'application/json'}
服務(wù)器返回給我們的響應(yīng)頭部信息可以通過(guò) r.headers 訪(fǎng)問(wèn):
>>> print r.headers
{'Content-Length': '462', 'Server': 'nginx', 'Connection': 'close', 'Access-Control-Allow-Credentials': 'true', 'Date': 'Mon, 05 Dec 2016 15:41:05 GMT', 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'}
HTTP 響應(yīng)與 HTTP 請(qǐng)求相似,由三部分組成:
如圖所示:

當(dāng)我們使用 requests.* 發(fā)送請(qǐng)求時(shí),Requests 做了兩件事:
對(duì)于響應(yīng)狀態(tài)碼,我們可以訪(fǎng)問(wèn)響應(yīng)對(duì)象的 status_code 屬性:
import requests
r = requests.get("http://httpbin.org/get")
print r.status_code
# 輸出
200
對(duì)于響應(yīng)正文,我們可以通過(guò)多種方式讀取,比如:
r.text 獲取r.json() 獲取r.content 獲取r.raw 獲取我們可以使用 r.text 來(lái)讀取 unicode 形式的響應(yīng),看看例子:
import requests
r = requests.get("https://github.com/timeline.json")
print r.text
print r.encoding
# 輸出
{"message":"Hello there, wayfaring stranger. If you’re reading this then you probably didn’t see our blog post a couple of years back announcing that this API would go away: http://git.io/17AROg Fear not, you should be able to get what you need from the shiny new Events API instead.","documentation_url":"https://developer.github.com/v3/activity/events/#list-public-events"}
utf-8
Requests 會(huì)自動(dòng)解碼來(lái)自服務(wù)器的內(nèi)容,大多數(shù) unicode 字符集都能被正確解碼。
對(duì)于 JSON 響應(yīng)的內(nèi)容,我們可以使用 json() 方法把返回的數(shù)據(jù)解析成 Python 對(duì)象。
看看例子:
import requests
r = requests.get("https://github.com/timeline.json")
if r.status_code == 200:
print r.headers.get('content-type')
print r.json()
# 輸出
application/json; charset=utf-8
{u'documentation_url': u'https://developer.github.com/v3/activity/events/#list-public-events', u'message': u'Hello there, wayfaring stranger. If you\u2019re reading this then you probably didn\u2019t see our blog post a couple of years back announcing that this API would go away: http://git.io/17AROg Fear not, you should be able to get what you need from the shiny new Events API instead.'}
如果 JSON 解碼失敗,r.json() 就會(huì)拋出異常,比如:
import requests
r = requests.get("https://www.baidu.com")
if r.status_code == 200:
print r.headers.get('content-type')
print r.json()
# 輸出
text/html
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-3-9216431f0e2d> in <module>()
1 if r.status_code == 200:
2 print r.headers.get('content-type')
----> 3 print r.json()
4
....
....
ValueError: No JSON object could be decoded
我們也可以以字節(jié)的方式訪(fǎng)問(wèn)響應(yīng)正文,訪(fǎng)問(wèn) content 屬性可以獲取二進(jìn)制數(shù)據(jù),比如用返回的二進(jìn)制數(shù)據(jù)創(chuàng)建一張圖片:
import requests
url = 'https://github.com/reactjs/redux/blob/master/logo/logo.png?raw=true'
r = requests.get(url)
image_data = r.content # 獲取二進(jìn)制數(shù)據(jù)
with open('/Users/Ethan/Downloads/redux.png', 'wb') as fout:
fout.write(image_data)
在少數(shù)情況下,我們可能想獲取來(lái)自服務(wù)器的原始套接字響應(yīng),這可以通過(guò)訪(fǎng)問(wèn)響應(yīng)對(duì)象的 raw 屬性來(lái)實(shí)現(xiàn),但要確保在初始請(qǐng)求中設(shè)置了 stream=True,比如:
import requests
url = 'https://github.com/reactjs/redux/blob/master/logo/logo.png?raw=true'
r = requests.get(url, stream=True)
print r.raw
r.raw.read(10)
# 輸出
<requests.packages.urllib3.response.HTTPResponse object at 0x1113b0a90>
'\x89PNG\r\n\x1a\n\x00\x00'
默認(rèn)情況下,除了 HEAD,Requests 會(huì)自動(dòng)處理所有重定向。我們可以使用響應(yīng)對(duì)象的 history 屬性來(lái)追蹤重定向,Response.history 是一個(gè) Response 對(duì)象的列表,這個(gè)對(duì)象列表按照從最老到最近的請(qǐng)求進(jìn)行排序。
比如,點(diǎn)擊某些網(wǎng)站的鏈接,它會(huì)將頁(yè)面重定向到其他網(wǎng)站:
>>> import requests
>>> headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}
>>> r = requests.get('https://toutiao.io/k/c32y51', headers=headers)
>>> r.status_code
200
>>> r.url # 發(fā)生了重定向,響應(yīng)對(duì)象的 url,跟請(qǐng)求對(duì)象不一樣
u'http://www.jianshu.com/p/490441391db6?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io'
>>> r.history
[<Response [302]>]
>>> r.history[0].text
u'<html><body>You are being <a
可以看到,我們?cè)L問(wèn)網(wǎng)址 https://toutiao.io/k/c32y51 被重定向到了下面的鏈接:
http://www.jianshu.com/p/490441391db6?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io'
我們還看到 r.history 包含了一個(gè) Response 對(duì)象列表,我們可以用它來(lái)追蹤重定向。
如果請(qǐng)求方法是 GET、POST、PUT、OPTIONS、PATCH 或 DELETE,我們可以通過(guò) all_redirects 參數(shù)禁止重定向:
>>> import requests
>>> headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}
>>> r = requests.get('https://toutiao.io/k/c32y51', headers=headers, allow_redirects=False)
>>> r.url # 禁止重定向,響應(yīng)對(duì)象的 url 跟請(qǐng)求對(duì)象一致
u'https://toutiao.io/k/c32y51'
>>> r.history
[]
>>> r.text
u'<html><body>You are being <a
>>> import requests
>>> url = 'http://exmaple.com/some/cookie/setting/url'
>>> r = requests.get(url)
>>> r.cookies['some_key']
'some_value'
cookies 參數(shù):>>> import requests
>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(key1='value1')
>>> r = requests.get(url, cookies=cookies)
>>> r.text
u'{\n "cookies": {\n "key1": "value1"\n }\n}\n'
>>> print r.text
{
"cookies": {
"key1": "value1"
}
}
我們知道,HTTP 協(xié)議是無(wú)狀態(tài)的,這意味著每個(gè)請(qǐng)求都是獨(dú)立的,如果后續(xù)的處理需要用到前面的信息,則數(shù)據(jù)需要重傳,為了解決這個(gè)問(wèn)題,我們可以使用 Cookie 或 Session 來(lái)存儲(chǔ)某些特定的信息,比如用戶(hù)名、密碼等,這樣,當(dāng)用戶(hù)在不同 Web 頁(yè)面跳轉(zhuǎn)或再次登錄網(wǎng)站時(shí),就不用重新輸入用戶(hù)名和密碼(當(dāng)然,如果 Cookie 被刪除和 Session 過(guò)期則需要重新輸入)。
Requests 提供了會(huì)話(huà)對(duì)象讓我們能夠跨請(qǐng)求保持某些參數(shù),也可以在同一個(gè) Session 實(shí)例發(fā)出的所有請(qǐng)求之間保持 Cookie。
下面,我們看看會(huì)話(huà)對(duì)象的使用。
下面是一個(gè)跨請(qǐng)求保持 Cookie 的例子:
>>> import requests
>>> s = requests.Session()
>>> s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
<Response [200]>
>>> r = s.get("http://httpbin.org/cookies")
>>> print r.text
{
"cookies": {
"sessioncookie": "123456789"
}
}
會(huì)話(huà)還可用來(lái)為請(qǐng)求方法提供缺省數(shù)據(jù),通過(guò)設(shè)置會(huì)話(huà)對(duì)象的屬性來(lái)實(shí)現(xiàn):
import requests
s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})
# x-test 和 x-test2 都會(huì)被發(fā)送
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
Requests 支持基本的 HTTP 代理 和 SOCKS 代理(2.10.0 新增功能)。
如果需要使用 HTTP 代理,我們可以為任意請(qǐng)求方法提供 proxies 參數(shù),如下:
import requests
proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)
我們也可以通過(guò)設(shè)置環(huán)境變量 HTTP_PROXY 和 HTTPS_PROXY 來(lái)配置代理:
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get("http://example.org")
Requests 自 2.10.0 版起,開(kāi)始支持 SOCKS 協(xié)議的代理,如果要使用,我們還需安裝一個(gè)第三方庫(kù):
$ pip install requests[socks]
SOCKS 代理的使用和 HTTP 代理類(lèi)似:
import requests
proxies = {
"http": "socks5://user:pass@host:port",
"https": "socks5://user:pass@host:port",
}
requests.get("http://example.org", proxies=proxies)
大部分 Web 服務(wù)都需要身份認(rèn)證,并且有多種不同的認(rèn)證類(lèi)型,比如:
下面介紹一下基本身份認(rèn)證和 OAuth 認(rèn)證。
基本身份認(rèn)證(HTTP Basic Auth)是最簡(jiǎn)單的一種身份認(rèn)證,一般需要身份認(rèn)證的 Web 服務(wù)也都接受 HTTP Basic Auth,Requests 提供了非常簡(jiǎn)單的形式讓我們使用 HTTP Basic Auth:
>>> from requests.auth import HTTPBasicAuth
>>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
由于 HTTP Basic Auth 非常常見(jiàn),Requests 提供了一種簡(jiǎn)寫(xiě)的形式:
requests.get('https://api.github.com/user', auth=('user', 'pass'))
OAuth 是一種常見(jiàn)的 Web API 認(rèn)證方式,目前的版本是 2.0。Requests 并不直接支持 OAuth 認(rèn)證,而是要配合另外一個(gè)庫(kù)一起使用,該庫(kù)是 requests-oauthlib。
下面以 GitHub 為例,介紹一下 OAuth 2 認(rèn)證。
>>> # Credentials you get from registering a new application
>>> client_id = '<the id you get from github>'
>>> client_secret = '<the secret you get from github>'
>>> # OAuth endpoints given in the GitHub API documentation
>>> authorization_base_url = 'https://github.com/login/oauth/authorize'
>>> token_url = 'https://github.com/login/oauth/access_token'
>>> from requests_oauthlib import OAuth2Session
>>> github = OAuth2Session(client_id)
>>> # Redirect user to GitHub for authorization
>>> authorization_url, state = github.authorization_url(authorization_base_url)
>>> print 'Please go here and authorize,', authorization_url
>>> # Get the authorization verifier code from the callback url
>>> redirect_response = raw_input('Paste the full redirect URL here:')
>>> # Fetch the access token
>>> github.fetch_token(token_url, client_secret=client_secret,
>>> authorization_response=redirect_response)
>>> # Fetch a protected resource, i.e. user profile
>>> r = github.get('https://api.github.com/user')
>>> print r.content
更多關(guān)于 OAuth 工作流程的信息,可以參考 OAuth 官方網(wǎng)站,關(guān)于 requests-oauthlib 庫(kù)的使用,可以參考官方文檔。