<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python-Requests庫進階用法——timeouts, retries, hooks

          共 6058字,需瀏覽 13分鐘

           ·

          2021-07-31 02:21

          Python HTTP 請求庫在所有編程語言中是比較實用的程序。它簡單、直觀且在 Python 社區(qū)中無處不在。大多數(shù)與 HTTP 接口程序使用標準庫中的request或 urllib3。

          由于簡單的API,請求很容易立即生效,但該庫還為高級需求提供了可擴展性。假如你正在編寫一個API密集型client或網(wǎng)路爬蟲,可能需要考慮網(wǎng)絡(luò)故障、靠譜的調(diào)試跟蹤和語法分析。

          Request hooks

          在使用第三方API時,通常需要驗證返回的響應(yīng)是否確實有效。Requests提供簡單有效的方法raise_for_status(),它斷言響應(yīng)HTTP狀態(tài)代碼不是4xx或5xx,即校驗請求沒有導(dǎo)致cclient或服務(wù)器錯誤。

          比如:

          response?=?requests.get('https://api.github.com/user/repos?page=1')
          #?斷言沒有錯誤
          response.raise_for_status()

          如果每次調(diào)用都需要使用raise_for_status(),則此操作可能會重復(fù)。幸運的是,request庫提供了一個“hooks”(鉤子)接口,可以附加對請求過程某些部分的回調(diào),確保從同一session對象發(fā)出的每個請求都會被檢查。

          我們可以使用hooks來確保為每個響應(yīng)對象調(diào)用raise_for_status()。

          #?創(chuàng)建自定義請求對象時,修改全局模塊拋出錯誤異常
          http?=?requests.Session()

          assert_status_hook?=?lambda?response,?*args,?**kwargs:?response.raise_for_status()
          http.hooks["response"]?=?[assert_status_hook]

          http.get("https://api.github.com/user/repos?page=1")

          >?HTTPError:?401?Client?Error:?Unauthorized?for?url:?https://api.github.com/user/repos?page=1

          設(shè)置base URLs

          requests中可以用兩種方法指定URL:
          1、假設(shè)你只使用一個托管在API.org上的API,每次調(diào)用使用全部的URL地址

          requests.get('https://api.org/list/')
          requests.get('https://api.org/list/3/item')

          2、安裝requests_toolbelt庫,使用BaseUrlSession指定base_url

          from?requests_toolbelt?import?sessions
          http?=?sessions.BaseUrlSession(base_url="https://api.org")
          http.get("/list")
          http.get("/list/item")

          設(shè)置默認timeout值

          Request官方文檔建議對所有的代碼設(shè)置超時。
          如果你的python程序是同步的,忘記設(shè)置請求的默認timeout可能會導(dǎo)致你的請求或者有應(yīng)用程序掛起。
          timeout的設(shè)定同樣有兩種方法:
          1、每次都在get語句中指定timeout的值。
          (不可取,只對本次請求有效)。

          requests.get('https://github.com/',?timeout=0.001)

          2、使用Transport Adapters設(shè)置統(tǒng)一的timeout時間(使用Transport Adapters,我們可以為所有HTTP調(diào)用設(shè)置默認超時,這確保了即使開發(fā)人員忘記在他的單個調(diào)用中添加timeout=1參數(shù),也可以設(shè)置一個合理的超時,但這是允許在每個調(diào)用的基礎(chǔ)上重寫。):
          下面是一個帶有默認超時的自定義Transport Adapters的例子,在構(gòu)造http client和send()方法時,我們重寫構(gòu)造函數(shù)以提供默認timeout,以確保在沒有提供timeout參數(shù)時使用默認超時。

          from?requests.adapters?import?HTTPAdapter

          DEFAULT_TIMEOUT?=?5?#?seconds

          class?TimeoutHTTPAdapter(HTTPAdapter):
          ????def?__init__(self,?*args,?**kwargs):
          ????????self.timeout?=?DEFAULT_TIMEOUT
          ????????if?"timeout"?in?kwargs:
          ????????????self.timeout?=?kwargs["timeout"]
          ????????????del?kwargs["timeout"]
          ????????super().__init__(*args,?**kwargs)

          ????def?send(self,?request,?**kwargs):
          ????????timeout?=?kwargs.get("timeout")
          ????????if?timeout?is?None:
          ????????????kwargs["timeout"]?=?self.timeout
          ????????return?super().send(request,?**kwargs)

          實際代碼中,我們可以這樣使用:

          import?requests

          http?=?requests.Session()

          #?此掛載對http和https都有效
          adapter?=?TimeoutHTTPAdapter(timeout=2.5)
          http.mount("https://",?adapter)
          http.mount("http://",?adapter)

          #?設(shè)置默認超時為2.5秒
          response?=?http.get("https://api.twilio.com/")

          #?通常為特定的請求重寫超時時間
          response?=?http.get("https://api.twilio.com/",?timeout=10)

          失敗時重試

          網(wǎng)絡(luò)連接有丟包、擁擠,服務(wù)器出現(xiàn)故障。如果我們想要構(gòu)建一個真正健壯的程序,我們需要考慮失敗重試策略。

          向HTTP client添加重試策略非常簡單。創(chuàng)建一個HTTPAdapter來適應(yīng)我們的策略。

          from?requests.adapters?import?HTTPAdapter
          from?requests.packages.urllib3.util.retry?import?Retry

          retry_strategy?=?Retry(
          ????total=3,
          ????status_forcelist=[429,?500,?502,?503,?504],
          ????method_whitelist=["HEAD",?"GET",?"OPTIONS"]
          )
          adapter?=?HTTPAdapter(max_retries=retry_strategy)
          http?=?requests.Session()
          http.mount("https://",?adapter)
          http.mount("http://",?adapter)

          response?=?http.get("https://en.wikipedia.org/w/api.php")

          其他參數(shù):

          • 最大重試次數(shù)total=10

          • 引起重試的HTTP狀態(tài)碼status_forcelist=[413, 429, 503]

          • 允許重試的請求方法method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]

          • 兩次重試的間隔參數(shù)backoff_factor=0

          合并timeouts和retries--超時與重試

          綜合上面學(xué)到的,我們可以通過這種方法將timeouts與retries結(jié)合到同一個Adapter中

          retries?=?Retry(total=3,?backoff_factor=1,?status_forcelist=[429,?500,?502,?503,?504])
          http.mount("https://",?TimeoutHTTPAdapter(max_retries=retries))

          調(diào)試HTTP請求

          如果一個HTTP請求失敗了,可以用下面兩種方法獲取失敗的信息:

          • 使用內(nèi)置的調(diào)試日志

          • 使用request hooks

          打印HTTP頭部信息

          將logging debug level設(shè)置為大于0的值都會將HTTP請求的頭部打印在日志中。當返回體過大或為字節(jié)流不便于日志時,打印頭部將非常有用。

          import?requests
          import?http

          http.client.HTTPConnection.debuglevel?=?1

          requests.get("https://www.google.com/")

          #?Output?輸出信息
          send:?b'GET?/?HTTP/1.1\r\nHost:?www.google.com\r\nUser-Agent:?python-requests/2.22.0\r\nAccept-Encoding:?gzip,?deflate\r\nAccept:?*/*\r\nConnection:?keep-alive\r\n\r\n'
          reply:?'HTTP/1.1?200?OK\r\n'
          header:?Date:?Fri,?28?Feb?2020?12:13:26?GMT
          header:?Expires:?-1
          header:?Cache-Control:?private,?max-age=0

          打印所有HTTP內(nèi)容

          當API返回內(nèi)容不太大時,我們可以使用request hooks與requests_toolbelt的dump工具庫輸出所有HTTP請求相應(yīng)內(nèi)容。

          import?requests
          from?requests_toolbelt.utils?import?dump

          def?logging_hook(response,?*args,?**kwargs):
          ????data?=?dump.dump_all(response)
          ????print(data.decode('utf-8'))

          http?=?requests.Session()
          http.hooks["response"]?=?[logging_hook]

          http.get("https://api.openaq.org/v1/cities",?params={"country":?"BA"})

          # Output 輸出信息如下:
          <?GET?/v1/cities?country=BA?HTTP/1.1
          <?Host:?api.openaq.org

          >?HTTP/1.1?200?OK
          >?Content-Type:?application/json;?charset=utf-8
          >?Transfer-Encoding:?chunked
          >?Connection:?keep-alive
          >
          {
          ???"meta":{
          ??????"name":"openaq-api",
          ??????"license":"CC?BY?4.0",
          ??????"website":"https://docs.openaq.org/",
          ??????"page":1,
          ??????"limit":100,
          ??????"found":1
          ???},
          ???"results":[
          ??????{
          ?????????"country":"BA",
          ?????????"name":"Gora?de",
          ?????????"city":"Gora?de",
          ?????????"count":70797,
          ?????????"locations":1
          ??????}
          ???]
          }

          dump工具的用法:https://toolbelt.readthedocs.io/en/latest/dumputils.html

          測試與模擬請求

          測試第三方API有時不能一直發(fā)送真實的請求(比如按次收費的接口,還有沒開發(fā)完的=_=),測試中我們可以用getsentry/responses作為樁模塊攔截程序發(fā)出的請求并返回預(yù)定的數(shù)據(jù),造成返回成功的假象。

          class?TestAPI(unittest.TestCase):
          [email protected]??#攔截該方法中的HTTP調(diào)用
          ????def?test_simple(self):
          ????????response_data?=?{
          ????????????????"id":?"ch_1GH8so2eZvKYlo2CSMeAfRqt",
          ????????????????"object":?"charge",
          ????????????????"customer":?{"id":?"cu_1GGwoc2eZvKYlo2CL2m31GRn",?"object":?"customer"},
          ????????????}
          ????????#?模擬?Stripe?API
          ????????responses.add(
          ????????????responses.GET,
          ????????????"https://api.stripe.com/v1/charges",
          ????????????json=response_data,
          ????????)

          ????????response?=?requests.get("https://api.stripe.com/v1/charges")
          ????????self.assertEqual(response.json(),?response_data)

          一旦攔截成立就不能再向其他未設(shè)定過的URL發(fā)請求了,不然會報錯。

          模仿瀏覽器行為

          有些網(wǎng)頁會根據(jù)不同瀏覽器發(fā)送不同HTML代碼(為了反爬或適配設(shè)備),可以在發(fā)送請求時指定User-Agent將自己偽裝成特定瀏覽器。

          import?requests
          http?=?requests.Session()
          http.headers.update({
          ????"User-Agent":?"Mozilla/5.0?(X11;?Ubuntu;?Linux?x86_64;?rv:68.0)?Gecko/20100101?Firefox/68.0"
          })

          總結(jié):
          以上就是Python-Requests庫的進階用法,在實際的代碼編寫中將會很有用,不管是開發(fā)編寫API還是測試在編寫自動化測試代碼,都會極大的提高所編寫代碼的穩(wěn)定性。

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  乱伦无码中文字幕 | 久久久99国产精品免费 | 亚洲欧洲在线aa观看视频 | 日韩欧美中文字幕视频 | 亚洲字幕网 |