Python 標准庫中的 urllib2 模塊提供了你所需要的大多數 HTTP 功能,但是它的 API 太渣了。它是為另一個時代、另一個互聯網所創建的。它需要巨量的工作,甚至包括各種方法覆蓋,來完成最簡單的任務。
Requests 完全滿足如今網絡的需求。
- 國際化域名和 URLs
- Keep-Alive & 連接池
- 持久的 Cookie 會話
- 類瀏覽器式的 SSL 加密認證
- 基本/摘要式的身份認證
- 優雅的鍵/值 Cookies
- 自動解壓
- Unicode 編碼的響應體
- 多段文件上傳
- 連接超時
- 支持 .netrc
- 適用於 Python 2.6—3.4
- 線程安全
會話對象:
會話就是session,session的實現是基於cookie的,所以會話對象能夠跨請求保持一些參數,也可以在同一個session實例發出的所有請求之間保持cookies。
import requests s = requests.Session() s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') r=s.get('http://httpbin.org/cookies')
會話對象也可以為其你去提供缺省數據,通過會話對象的屬性提哦給你數據來實現的
s = requests.Session() s.auth = ('user', 'pass') s.headers.update({'x-test': 'true'}) # both 'x-test' and 'x-test2' are sent s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
會話對象傳入的參數會自動覆蓋初始化中的默認參數。
請求與響應對象
任何時候調用requests.*()你都在做兩件主要的事情。其一,你在構建一個 Request 對象, 該對象將被發送到某個服務器請求或查詢一些資源。其二,一旦 requests 得到一個從 服務器返回的響應就會產生一個 Response 對象。該響應對象包含服務器返回的所有信息, 也包含你原來創建的 Request 對象。
r = requests.get('http://www.baidu.com') r.headers
獲取響應頭的信息。查看http協議了解響應頭的內容
下面的代碼是獲取請求的內容
r.request.headers
Prepared Requests
當你從一個api請求或者一個session請求接受到一個響應對象的時候,請求的參數實際上是PreparedRequest 對象使用的,如果你想在提交請求之前在請求體body或者請求頭header做一些操作的時候。
from requests import Request, Session s = Session() req = Request('GET', url, data=data, headers=header ) prepped = req.prepare() # do something with prepped.body # do something with prepped.headers resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code)
可以理解。Requests對象其實就是當PreparedRequest 沒有被修改時直接提交的。最后的請求方式是 requests.* 或者 Session.*.
上面的代碼在使用Requests的session對象時很可能會丟失一些優勢,session級別的狀態,比如說cookie就不會在request請求中,用Session.prepare_request()代替Request.prepare() 就可以完美的實現session級別的狀態。
from requests import Request, Session s = Session() req = Request('GET', url, data=data headers=headers ) prepped = s.prepare_request(req) # do something with prepped.body # do something with prepped.headers resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code)
SSL證書驗證
SSL證書通過在客戶端瀏覽器和Web服務器之間建立一條SSL安全通道(Secure socket layer(SSL)安全協議是由Netscape Communication公司設計開發。該安全協議主要用來提供對用戶和服務器的認證;對傳送的數據進行加密和隱藏;確保數據在傳送中不被改變,即數據的完整性,現已成為該領域中全球化的標准。由於SSL技術已建立到所有主要的瀏覽器和WEB服務器程序中,因此,僅需安裝服務器證書就可以激活該功能了)。即通過它可以激活SSL協議,實現數據信息在客戶端和服務器之間的加密傳輸,可以防止數據信息的泄露。保證了雙方傳遞信息的安全性,而且用戶可以通過服務器證書驗證他所訪問的網站是否是真實可靠。
Requests可以為HTTPS請求驗證SSL證書,就像web瀏覽器一樣。要想檢查某個主機的SSL證書,你可以使用 verify 參數:
requests.get('https://kennethreitz.com', verify=True)
在該域名上我沒有設置SSL,所以失敗了。但Github設置了SSL:
requests.get('https://github.com', verify=True)
如果你將 verify 設置為False,Requests也能忽略對SSL證書的驗證
requests.get('https://kennethreitz.com', verify=False)
默認情況下, verify 是設置為True的。選項 verify 僅應用於主機證書。
你也可以指定一個本地證書用作客戶端證書,可以是單個文件(包含密鑰和證書)或一個包含兩個文件路徑的元組:
requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))
如果你指定了一個錯誤路徑或一個無效的證書:
requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem') SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
響應體內容工作流
默認情況下,當你進行網絡請求后,響應體會立即被下載。你可以通過 stream 參數覆蓋這個行為,推遲下載響應體直到訪問 Response.content 屬性:
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master' r = requests.get(tarball_url, stream=True)
此時僅有響應頭被下載下來了,連接保持打開狀態,因此允許我們根據條件獲取內容:
if int(r.headers['content-length']) < TOO_LONG: content = r.content ...
當講一個request請求的stream=True時,連接connection不會被返回放入到連接池中去除非你讀取完requests中所有的數據或者直接調用Requests.close()方法。這必然導致連接的效率很低,當只需要響應的正文部分(或者什么都不需要讀取),可以試試contextlib.closing
from contextlib import closing with closing(requests.get('http://httpbin.org/get', stream=True)) as r: # Do things with the response here.
保持活動狀態(持久連接)
好消息 - 歸功於urllib3,同一會話內的持久連接是完全自動處理的!同一會話內你發出的任何請求都會自動復用恰當的連接!只有所有的響應體數據被讀取完畢連接才會被釋放為連接池;所以確保將 stream設置為 False 或讀取 Response 對象的 content 屬性。
流式上傳
Requests支持流式上傳,這允許你發送大的數據流或文件而無需先把它們讀入內存。要使用流式上傳,僅需為你的請求體提供一個類文件對象即可:
with open('massive-body') as f: requests.post('http://some.url/streamed', data=f)
塊編碼請求
對於出去和進來的請求,Requests也支持分塊傳輸編碼。要發送一個塊編碼的請求,僅需為你的請求體提供一個生成器(或任意沒有具體長度(without a length)的迭代器)
def gen(): yield 'hi' yield 'there' requests.post('http://some.url/chunked', data=gen())
多文件同時上傳
當需要在一次請求中上傳多個文件的時候,如:
<input type=”file” name=”images” multiple=”true” required=”true”/>
這時候可以將文件設置成一個元組列表(文件名稱,文件信息)。
url = 'http://httpbin.org/post' multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')), ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))] r = requests.post(url, files=multiple_files) r.text
流式請求
使用 requests.Response.iter_lines() 你可以很方便地對流式API(例如 Twitter的流式API )進行迭代。簡單地設置 stream 為 True 便可以使用 iter_lines() 對相應進行迭代:
import json import requests r = requests.get('http://httpbin.org/stream/20', stream=True) for line in r.iter_lines(): # filter out keep-alive new lines if line: print(json.loads(line))
代理
如果需要使用代理,你可以通過為任意請求方法提供 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_PROXY 和 HTTPS_PROXY 來配置代理。
$ 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")
若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 語法:
proxies = { "http": "http://user:pass@10.10.1.10:3128/", }
編碼方式
當你收到一個響應時,Requests會猜測響應的編碼方式,用於在你調用 Response.text方法時 對響應進行解碼。Requests首先在HTTP頭部檢測是否存在指定的編碼方式,如果不存在,則會使用 charade 來嘗試猜測編碼方式
只有當HTTP頭部不存在明確指定的字符集,並且 Content-Type 頭部字段包含 text 值之時, Requests才不去猜測編碼方式
在這種情況下, RFC 2616 指定默認字符集 必須是 ISO-8859-1 。Requests遵從這一規范。如果你需要一種不同的編碼方式,你可以手動設置 Response.encoding 屬性,或使用原始的 Response.content
HTTP動詞
Requests提供了幾乎所有HTTP動詞的功能:GET,OPTIONS, HEAD,POST,PUT,PATCH和DELETE。 以下內容為使用Requests中的這些動詞以及Github API提供了詳細示例。
我將從最常使用的動詞GET開始。HTTP GET是一個冪等的方法,從給定的URL返回一個資源。因而, 當你試圖從一個web位置獲取數據之時,你應該使用這個動詞。一個使用示例是嘗試從Github上獲取 關於一個特定commit的信息。假設我們想獲取Requests的commit a050faf 的信息。我們可以 這樣去做
import requests r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
我們應該確認Github是否正確響應。如果正確響應,我們想弄清響應內容是什么類型的。像這樣去做:
if (r.status_code == requests.codes.ok): ... print r.headers['content-type'] ... application/json; charset=utf-8
可見,GitHub返回了JSON數據,非常好,這樣就可以使用 r.json 方法把這個返回的數據解析成Python對象
>>> commit_data = r.json() >>> print commit_data.keys() [u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message'] >>> print commit_data[u'committer'] {u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'} >>> print commit_data[u'message'] makin' history
到目前為止,一切都非常簡單。嗯,我們來研究一下GitHub的API。我們可以去看看文檔, 但如果使用Requests來研究也許會更有意思一點。我們可以借助Requests的OPTIONS動詞來看看我們剛使用過的url 支持哪些HTTP方法
>>> verbs = requests.options(r.url) >>> verbs.status_code 500
額,這是怎么回事?毫無幫助嘛!原來GitHub,與許多API提供方一樣,實際上並未實現OPTIONS方法。 這是一個惱人的疏忽,但沒關系,那我們可以使用枯燥的文檔。然而,如果GitHub正確實現了OPTIONS, 那么服務器應該在響應頭中返回允許用戶使用的HTTP方法,例如
>>> verbs = requests.options('http://a-good-website.com/api/cats') >>> print verbs.headers['allow'] GET,HEAD,POST,OPTIONS
轉而去查看文檔,我們看到對於提交信息,另一個允許的方法是POST,它會創建一個新的提交。 由於我們正在使用Requests代碼庫,我們應盡可能避免對它發送笨拙的POST。作為替代,我們來 玩玩GitHub的Issue特性
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482') >>> r.status_code 200 >>> issue = json.loads(r.text) >>> print issue[u'title'] Feature any http verb in docs >>> print issue[u'comments'] 3
使用https://api.github.com/repos/kennethreitz/requests/issues/482為例
r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482') >>> r.status_code 200 >>> issue = json.loads(r.text) >>> print issue[u'title'] Feature any http verb in docs >>> print issue[u'comments']
有3個評論。我們來看一下最后一個評論
>>> r = requests.get(r.url + u'/comments') >>> r.status_code 200 >>> comments = r.json() >>> print comments[0].keys() [u'body', u'url', u'created_at', u'updated_at', u'user', u'id'] >>> print comments[2][u'body'] Probably in the "advanced" section
嗯,那看起來似乎是個愚蠢之處。我們發表個評論來告訴這個評論者他自己的愚蠢。那么,這個評論者是誰呢?
>>> print comments[2][u'user'][u'login'] kennethreitz
好,我們來告訴這個叫肯尼思的家伙,這個例子應該放在快速上手指南中。根據GitHub API文檔, 其方法是POST到該話題。我們來試試看
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"}) >>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments" >>> r = requests.post(url=url, data=body) >>> r.status_code 404
這有點古怪哈。可能我們需要驗證身份。那就有點糾結了,對吧?不對。Requests簡化了多種身份驗證形式的使用, 包括非常常見的Basic Auth
>>> from requests.auth import HTTPBasicAuth >>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password') >>> r = requests.post(url=url, data=body, auth=auth) >>> r.status_code 201 >>> content = r.json() >>> print(content[u'body']) Sounds great! I'll get right on it.
精彩!噢,不!我原本是想說等我一會,因為我得去喂一下我的貓。如果我能夠編輯這條評論那就好了! 幸運的是,GitHub允許我們使用另一個HTTP動詞,PATCH,來編輯評論。我們來試試
>>> print(content[u"id"]) 5804413 >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."}) >>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413" >>> r = requests.patch(url=url, data=body, auth=auth) >>> r.status_code 200
非常好。現在,我們來折磨一下這個叫肯尼思的家伙,我決定要讓他急得團團轉,也不告訴他是我在搗蛋。 這意味着我想刪除這條評論。GitHub允許我們使用完全名副其實的DELETE方法來刪除評論。我們來清除該評論。
>>> r = requests.delete(url=url, auth=auth) >>> r.status_code 204 >>> r.headers['status'] '204 No Content'
很好。不見了。最后一件我想知道的事情是我已經使用了多少限額(ratelimit)。查查看,GitHub在響應頭部發送這個信息, 因此不必下載整個網頁,我將使用一個HEAD請求來獲取響應頭
>>> r = requests.head(url=url, auth=auth) >>> print r.headers ... 'x-ratelimit-remaining': '4995' 'x-ratelimit-limit': '5000' ...
響應頭鏈接字段
許多HTTP API都有響應頭鏈接字段的特性,它們使得API能夠更好地自我描述和自我顯露。
GitHub在API中為 分頁 使用這些特性,例如:
>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10' >>> r = requests.head(url=url) >>> r.headers['link'] '<https://api.github.com/users/kennethreitz/repos?page=2&per_page=10>; rel="next", <https://api.github.com/users/kennethreitz/repos?page=6&per_page=10>; rel="last"'
Requests會自動解析這些響應頭鏈接字段,並使得它們非常易於使用:
>>> r.links["next"] {'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'} >>> r.links["last"] {'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
Blocking Or Non-Blocking:阻塞、非阻塞
使用默認傳輸適配器,不提供任何形式的非阻塞IO請求。響應。內容屬性將阻塞,直到整個反應已經被下載。如果你需要更多的粒度,庫的流特性(見流式請求)允許您檢索響應的小批量。然而,這些調用仍然阻止。
如果你擔心使用阻塞IO,有很多的項目,將請求與Python的一個異步性框架。兩個優秀的例子是grequests和requests-futures。
Timeouts:超時
大多數請求外部服務器應該有一個超時,以防服務器沒有響應及時。沒有超時,那么您的代碼就會掛幾分鍾或者更多。
連接超時的秒數請求將等待你的客戶建立一個連接到一個遠程計算機(對應於connect())調用套接字。它是一個很好的實踐設置連接超時略大於3的倍數,這是默認的TCP數據包傳輸窗口。
一旦客戶端連接到服務器,發送HTTP請求,讀取超時的秒數客戶端將等待服務器發送一個響應。(具體地說,它的秒數,客戶端從服務器將字節之間等待發送。在99.9%的情況下,這是時間服務器發送的第一個字節)。
如果你為超時指定一個值,如下:
r = requests.get('https://github.com', timeout=5)
超時的值將被應用到連接和讀取超時。指定一個元組如果你想單獨設置值:
r = requests.get('https://github.com', timeout=(3.05, 27))
如果遠程服務器非常緩慢,你可以告訴請求永遠等待響應,通過沒有作為一個超時值,然后等待
r = requests.get('https://github.com', timeout=None)