Python接口測試之requests模塊詳解

本篇將介紹如何使用Requests來做接口測試

首先先確認一下事情:

先從一些簡單的例子開始。

創建一個請求

用Requests來創建一個請求是很簡單的。

先導入Requests模塊:

>>> import requests

我們來嘗試獲取一個網頁。看以下例子:

>>> r = requests.get('https://api.github.com/events')

現在有一個名爲r的Response對象。可以從這個對象中獲取所有的網頁信息。

Requests的簡單API意味着所有形式的HTTP請求都是顯而易見的。例如,可以這樣寫POST請求:

>>> r = requests.post('https://httpbin.org/post', data = {'key':'value'})

其他類型的HTTP請求:PUT,DELETE,HEAD和OPTIONS都十分簡單:

>>> r = requests.put('https://httpbin.org/put', data = {'key':'value'})
>>> r = requests.delete('https://httpbin.org/delete')
>>> r = requests.head('https://httpbin.org/get')
>>> r = requests.options('https://httpbin.org/get')

在URL中傳遞參數

你是否希望在URL的查詢字符串中發送一些數據。如果手動構建URL,則這些數據會寫在問號後作爲URL中的key/value給出,如 httpbin.org/get?key=val。Requests允許你使用params關鍵字參數,將這些參數作爲字符串字典提供。舉例來說, 如果你想傳 key1=value1 和 key2=value2 給 httpbin.org/get, 你可以這樣寫:

>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.get('https://httpbin.org/get', params=payload)

您可以通過打印URL看到URL已正確編碼:

>>> print(r.url)
https://httpbin.org/get?key2=value2&key1=value1

注意任何字典key對應的valule如果爲 None 都不會被添加到URL的查詢字符串中.

你還可以將列表作爲value:

>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}

>>> r = requests.get('https://httpbin.org/get', params=payload)
>>> print(r.url)
https://httpbin.org/get?key1=value1&key2=value2&key2=value3

響應內容

我們可以讀出服務器響應的內容:

>>> import requests

>>> r = requests.get('https://api.github.com/events')
>>> r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...

Requests將自動解碼來自服務器的內容。 大多數unicode字符集都是無縫解碼的。

當你發出請求時,Requests會根據HTTP頭對響應的編碼進行有根據的猜測。當你訪問 r.text 時,將使用由Requests猜測的文本編碼。你可以使用 r.encoding 屬性找出Requests正在使用的編碼,並進行更改:

>>> r.encoding
'utf-8'
>>> r.encoding = 'ISO-8859-1'

如果更改編碼,當你調用 r.text 時,Requests就會使用 r.encoding 的新值。你可能希望在任何可以應用特殊邏輯來計算內容編碼的情況下執行此操作。例如,HTML和XML可以在其正文中指定其編碼。在這種情況下,你應該使用 r.content 查找編碼,然後設置 r.encoding 。這樣你在使用 r.text 時就會是正確的編碼了。

如果你需要,Requests也可以使用自定義編碼。如果你已創建了自己的編碼並使用了 codecs 模塊註冊過了,則只需使用編解碼器名稱作爲 r.encoding 的值,並且Requests將爲您處理解碼。

二進制響應內容

對於非文本請求,你還可以以字節爲單位訪問響應正文:

>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...

gzip 和 deflate 傳輸編碼會自動爲你解碼。

例如,要從請求返回的二進制數據創建映像,可以使用以下代碼:

>>> from PIL import Image
>>> from io import BytesIO

>>> i = Image.open(BytesIO(r.content))

JSON響應內容

如果你正在處理JSON數據,還有一個內置的JSON解碼器:

>>> import requests

>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...

如果JSON解碼失敗, r.json() 會引發異常。例如,如果響應獲得204(無內容),或者響應包含無效的JSON,則嘗試 r.json() 會引發 ValueError: No JSON object could be decoded.

應該注意的是,對 r.json() 的調用成功並不表示響應成功。某些服務器可能會在失敗的響應中返回JSON對象(例如,使用HTTP 500的錯誤詳細信息)。 這樣的JSON將被解碼並返回。 要檢查請求是否成功,請使用 r.raise_for_status() 或檢查 r.status_code 是否符合預期。

原始響應內容

在極少數情況下,你希望從服務器獲取原始socket響應,您可以訪問 r.raw 。如果要執行此操作,請確保在初始請求中設置 stream=True 。你可以這樣寫:

>>> r = requests.get('https://api.github.com/events', stream=True)

>>> r.raw
<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=128):
        fd.write(chunk)

當直接使用 Response.raw 時,使用 Response.iter_content 將處理很多您必須處理的事情。 流式下載時,以上是檢索內容的首選和推薦方法。 請注意, chunk_size 可以自由調整爲更適合您的用例的數字。

注意

關於使用 Response.iter_content 與 Response.raw的重要說明。 Response.iter_content 將自動解碼 gzip 並 deflate 傳輸編碼。 Response.raw 是原始的字節流-它不會轉換響應內容。 如果您確實需要訪問返回的字節,請使用 Response.raw

自定義Headers

如果您想將HTTP headers添加到請求中,只需將一個 dict 傳遞給 headers 參數即可。

例如,在上一個示例中,我們沒有指定用戶代理:

>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}

>>> r = requests.get(url, headers=headers)

注意:自定義headers的優先級低於更具體的信息源。 例如:

  • 如果在 .netrc中指定了證書,則使用headers =的授權headers將被覆蓋,然後將被 auth= 參數覆蓋。
  • 如果你脫離主機重定向,則將刪除授權headers。
  • URL中提供的代理證書將覆蓋Proxy-Authorization headers。
  • 當我們可以確定內容的長度時,Content-Length headers將被覆蓋。

此外,“請求”根本不會根據指定的自定義headers更改其行爲。 headers只是傳遞到最終請求中。

注意:所有header的值必須是字符串,字節字符串或unicode。 建議在允許的情況下,避免傳遞unicodeheader值。

更復雜的POST請求

通常,你希望發送一些表單編碼的數據,就像HTML表單一樣。 爲此,只需將字典傳遞給 data 參數即可。 提出請求後,您的數據字典將自動進行表單編碼:

>>> payload = {'key1': 'value1', 'key2': 'value2'}

>>> r = requests.post("https://httpbin.org/post", data=payload)
>>> print(r.text)
{
  ...
  "form": {
    "key2": "value2",
    "key1": "value1"
  },
  ...
}

data 參數中一個key可以有多個value。 這可以通過使 data 成爲元組列表或以列表爲值的字典來完成。 當表單具有使用同一key的多個元素時,這特別有用:

>>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
>>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
>>> payload_dict = {'key1': ['value1', 'value2']}
>>> r2 = requests.post('https://httpbin.org/post', data=payload_dict)
>>> print(r1.text)
{
  ...
  "form": {
    "key1": [
      "value1",
      "value2"
    ]
  },
  ...
}
>>> r1.text == r2.text
True

有時候,你可能想發送未經格式編碼的數據。 如果您傳遞的是 string 而不是 dic,則該數據將直接發佈。

例如,GitHub API v3接受JSON編碼的POST / PATCH數據:

>>> import json

>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}

>>> r = requests.post(url, data=json.dumps(payload))

除了自己對 dict 進行編碼外,您還可以使用 json 參數(版本2.4.2中添加)直接傳遞它,它將自動進行編碼:

>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}

>>> r = requests.post(url, json=payload)

請注意,如果傳了 data 或 files ,則會忽略 json 參數。

在請求中使用 json 參數會將header中的 Content-Type 更改爲 application/json

發佈多部分編碼的文件

通過請求,可以輕鬆上傳多部分編碼的文件:

>>> url = 'https://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}

>>> r = requests.post(url, files=files)
>>> r.text
{
  ...
  "files": {
    "file": "<censored...binary...data>"
  },
  ...
}

你還可以設置文件名,content_type和headers:

>>> 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": "<censored...binary...data>"
  },
  ...
}

如果需要,你還可以發送字符串作爲文件接收:

>>> url = 'https://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}

>>> r = requests.post(url, files=files)
>>> r.text
{
  ...
  "files": {
    "file": "some,data,to,send\\nanother,row,to,send\\n"
  },
  ...
}

如果你要發佈很大的文件作爲一個 multipart/form-data 請求,則可能需要流式傳輸請求。 默認情況下, requests 不支持此功能,但是有一個單獨的軟件包-requests-toolbelt。 你應該閱讀 the toolbelt’s documentation 以獲取有關使用方法的更多詳細信息。

要在一個請求中發送多個文件,請參閱 advanced 部分。

警告

強烈建議您以二進制模式打開文件。 這是因爲請求可能會嘗試爲您提供 Content-Length header,如果這樣做,此值將被設置爲文件中的字節數(bytes)。 如果以文本模式(text mode)打開文件,可能會發生錯誤。

響應狀態碼

我們可以檢查響應狀態代碼:

>>> r = requests.get('https://httpbin.org/get')
>>> r.status_code
200

請求還帶有內置的狀態碼查找對象,以方便參考:

>>> r.status_code == requests.codes.ok
True

如果我們提出了錯誤的請求(4XX客戶端錯誤或5XX服務器錯誤響應),則可以使用 Response.raise_for_status()引發該請求:

>>> bad_r = requests.get('https://httpbin.org/status/404')
>>> bad_r.status_code
404

>>> bad_r.raise_for_status()
Traceback (most recent call last):
  File "requests/models.py", line 832, in raise_for_status
    raise http_error
requests.exceptions.HTTPError: 404 Client Error

但是,由於我們的r狀態代碼爲200,所以當我們調用 raise_for_status() 時,我們得到:

>>> r.raise_for_status()
None

 

響應Headers

我們可以使用Python字典查看服務器的響應headers:

>>> r.headers
{
    'content-encoding': 'gzip',
    'transfer-encoding': 'chunked',
    'connection': 'close',
    'server': 'nginx/1.0.4',
    'x-runtime': '148ms',
    'etag': '"e1ca502697e5c9317743dc078f67693f"',
    'content-type': 'application/json'
}

不過,字典很特殊:它僅用於HTTP headers。 根據 RFC 7230,HTTP Header名稱不區分大小寫。

因此,我們可以使用所需的任何大寫字母訪問標頭:

>>> r.headers['Content-Type']
'application/json'

>>> r.headers.get('content-type')
'application/json'

還有特別之處在於,服務器可以多次發送具有不同值的相同header,但是請求將它們組合在一起,以便可以按照 RFC 7230在單個映射中的字典中表示它們:

接收者可以通過將每個隨後的字段值按順序附加到合併後的字段值上並用逗號分隔,將多個具有相同字段名的頭字段組合成一對“field-name: field-value”,而不改變消息的語義。

Cookies

如果響應中包含一些Cookie,你可以快速訪問它們:

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)

>>> r.cookies['example_cookie_name']
'example_cookie_value'

要將你自己的cookie發送到服務器,可以使用 cookies 參數:

>>> url = 'https://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')

>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'

Cookie是在RequestsCookieJar中返回的,其行爲類似於 dict ,但還提供了更完整的界面,適用於多個域或路徑。 Cookie jars也可以傳遞給請求:

>>> 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 = 'https://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

重定向和歷史

默認情況下,請求將對HEAD以外的所有動詞執行位置重定向。

我們可以使用Response對象的 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]>]

超時

你可以使用 timeout 參數告訴請求在給定的秒數後停止等待響應。 幾乎所有生產代碼都應在幾乎所有請求中使用此參數。 否則,可能會導致程序無限期掛起:

>>> requests.get('https://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 秒數內未在基礎套接字上接收到任何字節),則會引發異常。 如果未明確指定超時,則請求不會超時。

錯誤和異常

Requests will raise a ConnectionError exception.如果出現網絡問題(例如DNS故障,連接被拒絕等),請求將引發 ConnectionError 異常。

如果HTTP請求返回的狀態碼失敗,則Response.raise_for_status()將引發 HTTPError 。

如果請求超時,則會引發超時異常。

如果請求超過配置的最大重定向數,則會引發 TooManyRedirects 異常。

請求顯式引發的所有異常均繼承自 requests.exceptions.RequestException

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章