<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>

          相信我,這么寫(xiě)Python代碼,老板給你漲工資

          共 10305字,需瀏覽 21分鐘

           ·

          2021-08-05 21:56

          圖片來(lái)自愛(ài)美劇

          Python HTTP 請(qǐng)求庫(kù)在所有編程語(yǔ)言中是比較實(shí)用的程序。它簡(jiǎn)單、直觀且在 Python 社區(qū)中無(wú)處不在。大多數(shù)與 HTTP 接口程序使用標(biāo)準(zhǔn)庫(kù)中的request或 urllib3。

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

          Request hooks

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

          比如:

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

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

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

          # 創(chuàng)建自定義請(qǐng)求對(duì)象時(shí),修改全局模塊拋出錯(cuò)誤異常
          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è)你只使用一個(gè)托管在API.org上的API,每次調(diào)用使用全部的URL地址

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

          2、安裝requests_toolbelt庫(kù),使用BaseUrlSession指定base_url

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

          設(shè)置默認(rèn)timeout值

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

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

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

          實(shí)際代碼中,我們可以這樣使用:

          import requests

          http = requests.Session()

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

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

          # 通常為特定的請(qǐng)求重寫(xiě)超時(shí)時(shí)間
          response = http.get("https://api.twilio.com/", timeout=10)

          失敗時(shí)重試

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

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

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

          retry_strategy = Retry(
              total=3,
              status_forcelist=[429500502503504],
              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]

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

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

          合并timeouts和retries--超時(shí)與重試

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

          retries = Retry(total=3, backoff_factor=1, status_forcelist=[429500502503504])
          http.mount("https://", TimeoutHTTPAdapter(max_retries=retries))

          調(diào)試HTTP請(qǐng)求

          如果一個(gè)HTTP請(qǐng)求失敗了,可以用下面兩種方法獲取失敗的信息:

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

          • 使用request hooks

          打印HTTP頭部信息

          將logging debug level設(shè)置為大于0的值都會(huì)將HTTP請(qǐng)求的頭部打印在日志中。當(dāng)返回體過(guò)大或?yàn)樽止?jié)流不便于日志時(shí),打印頭部將非常有用。

          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)容

          當(dāng)API返回內(nèi)容不太大時(shí),我們可以使用request hooks與requests_toolbelt的dump工具庫(kù)輸出所有HTTP請(qǐng)求相應(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

          測(cè)試與模擬請(qǐng)求

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

          class TestAPI(unittest.TestCase):
              @responses.activate  #攔截該方法中的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è)定過(guò)的URL發(fā)請(qǐng)求了,不然會(huì)報(bào)錯(cuò)。

          模仿瀏覽器行為

          有些網(wǎng)頁(yè)會(huì)根據(jù)不同瀏覽器發(fā)送不同HTML代碼(為了反爬或適配設(shè)備),可以在發(fā)送請(qǐng)求時(shí)指定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"
          })

          綜上所有的點(diǎn),實(shí)際使用是這樣子的:

          self._enable_https = trueself.host = xxxxself.port = xxxxclass XxxxxXxxxx(object):    def _get_api_session(self, timeout=30):        address_prefix = 'http://'        if self._enable_https:            address_prefix = 'https://'         #設(shè)置URL        sess = sessions.BaseUrlSession(base_url=f"{address_prefix}{self.host}:{self.port}")         #設(shè)置hooks        assert_status_hook = lambda response, *args, **kwargs: response.raise_for_status()        sess.hooks["response"] = [assert_status_hook]        # 重試        retries = Retry(total=3, backoff_factor=1, status_forcelist=[429])        sess.mount(address_prefix, TimeoutHTTPAdapter(max_retries=retries, timeout=timeout))        return sessclass TimeoutHTTPAdapter(HTTPAdapter):    def __init__(self, *args, **kwargs):        self.timeout = 5        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)


          總結(jié):
          以上就是Python-Requests庫(kù)的進(jìn)階用法,在實(shí)際的代碼編寫(xiě)中將會(huì)很有用,不管是開(kāi)發(fā)編寫(xiě)API還是測(cè)試在編寫(xiě)自動(dòng)化測(cè)試代碼,都會(huì)極大的提高所編寫(xiě)代碼的穩(wěn)定性,服務(wù)穩(wěn)定了,升職加薪不是夢(mèng),哈哈哈。

          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  91黄色视频网站 | 一级不免福利在线 | 久久高清视频免费观看久久 | 亚洲最大视频网站 | 四虎黄色片 |