Python Requests實(shí)現(xiàn)API請(qǐng)求重試與超時(shí)配置全解析
?在電商物流追蹤、金融數(shù)據(jù)監(jiān)控等場景中,API請(qǐng)求的穩(wěn)定性直接決定系統(tǒng)可靠性。當(dāng)順豐API因網(wǎng)絡(luò)抖動(dòng)返回503錯(cuò)誤,或因跨地域調(diào)用出現(xiàn)10秒延遲時(shí),如何確保程序不崩潰且數(shù)據(jù)不丟失?本文通過真實(shí)案例拆解,用Requests庫實(shí)現(xiàn)"防抖動(dòng)+抗異常"的健壯請(qǐng)求方案。
一、血淚教訓(xùn):那些年踩過的API坑
某跨境電商系統(tǒng)在"黑色星期五"大促期間突發(fā)故障:調(diào)用順豐國際件接口時(shí),30%的請(qǐng)求因超時(shí)失敗,導(dǎo)致2000+包裹狀態(tài)同步延遲。事后分析發(fā)現(xiàn)三大元兇:
- 固定超時(shí)陷阱:設(shè)置
timeout=5導(dǎo)致所有跨洋請(qǐng)求必然超時(shí)(實(shí)際平均響應(yīng)時(shí)間8秒) - 暴力重試雪崩:簡單for循環(huán)重試5次,瞬間產(chǎn)生10倍請(qǐng)求量擊垮順豐網(wǎng)關(guān)
- 代理池污染:使用失效代理IP發(fā)起請(qǐng)求,觸發(fā)順豐反爬機(jī)制封禁整個(gè)IP段
這些場景揭示核心問題:API請(qǐng)求需要"有智慧的等待"和"有策略的堅(jiān)持" 。
二、超時(shí)配置:給請(qǐng)求裝上"安全閥"
1. 連接超時(shí) vs 讀取超時(shí)
import requests
try:
# 連接超時(shí)3秒(TCP握手階段)
# 讀取超時(shí)10秒(服務(wù)器處理階段)
response = requests.get(
'https://api.sf-express.com/track',
params={'trackingNumber': 'SF123456789'},
timeout=(3, 10) # 元組形式分別設(shè)置
)
print(response.json())
except requests.exceptions.ConnectTimeout:
print("連接服務(wù)器失敗,請(qǐng)檢查網(wǎng)絡(luò)")
except requests.exceptions.ReadTimeout:
print("服務(wù)器處理超時(shí),請(qǐng)稍后重試")
關(guān)鍵決策點(diǎn):
- 國內(nèi)API調(diào)用:
timeout=(2, 5)(連接2秒,讀取5秒) - 跨境API調(diào)用:
timeout=(5, 15)(考慮國際鏈路延遲) - 文件上傳場景:需增加
write_timeout參數(shù)(需httpx等庫支持)
2. 動(dòng)態(tài)超時(shí)策略
某物流監(jiān)控系統(tǒng)采用分級(jí)超時(shí)機(jī)制:
def get_dynamic_timeout(retry_count):
base_timeout = 3 # 基礎(chǔ)超時(shí)
if retry_count > 0:
return min(base_timeout * (2 ** retry_count), 30) # 指數(shù)退避,最大30秒
return base_timeout
# 使用示例
for i in range(3):
try:
timeout = get_dynamic_timeout(i)
response = requests.get(url, timeout=timeout)
break
except Exception as e:
print(f"第{i+1}次嘗試失敗,超時(shí)時(shí)間調(diào)整為{timeout}秒")
三、重試機(jī)制:讓請(qǐng)求學(xué)會(huì)"堅(jiān)持"
1. 指數(shù)退避重試(推薦方案)
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_retry_session(retries=3, backoff_factor=1, status_forcelist=(500, 502, 503, 504)):
session = requests.Session()
retry = Retry(
total=retries,
read=True, # 允許讀取超時(shí)重試
connect=True, # 允許連接超時(shí)重試
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
allowed_methods=["GET", "POST"] # 支持POST請(qǐng)求重試
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
# 使用示例
session = create_retry_session()
response = session.get('https://api.sf-express.com/track', params={'trackingNumber': 'SF123456789'})
參數(shù)深解:
backoff_factor=1:第1次重試等待1秒,第2次2秒,第3次4秒status_forcelist:僅對(duì)5xx服務(wù)器錯(cuò)誤和429限流錯(cuò)誤重試allowed_methods:默認(rèn)不重試POST請(qǐng)求,需顯式聲明
2. 熔斷機(jī)制實(shí)現(xiàn)(避免雪崩)
from collections import deque
import time
class CircuitBreaker:
def __init__(self, max_failures=3, reset_timeout=60):
self.failures = deque(maxlen=max_failures)
self.reset_timeout = reset_timeout
def is_open(self):
if len(self.failures) < self.failures.maxlen:
return False
# 如果最近max_failures次請(qǐng)求都失敗,且最后一次失敗在reset_timeout秒內(nèi)
return (time.time() - self.failures[-1]) < self.reset_timeout
def record_failure(self):
self.failures.append(time.time())
def record_success(self):
self.failures.clear()
# 結(jié)合重試使用
breaker = CircuitBreaker(max_failures=3, reset_timeout=30)
def safe_request():
if breaker.is_open():
raise Exception("Service unavailable, circuit breaker open")
try:
response = create_retry_session().get(url)
if response.status_code == 200:
breaker.record_success()
return response
else:
breaker.record_failure()
raise Exception("API request failed")
except Exception as e:
breaker.record_failure()
raise e
四、代理配置:突破封禁的"隱身術(shù)"
1. 代理池實(shí)戰(zhàn)方案
import random
from requests.adapters import HTTPAdapter
class ProxyPool:
def __init__(self):
self.proxies = [
{"http": "http://1.1.1.1:8080", "https": "http://1.1.1.1:8080"},
{"http": "http://2.2.2.2:8080", "https": "http://2.2.2.2:8080"},
# 更多代理...
]
self.failed_proxies = set()
def get_proxy(self):
available_proxies = [p for p in self.proxies if p not in self.failed_proxies]
if not available_proxies:
raise Exception("No available proxies")
return random.choice(available_proxies)
def mark_failed(self, proxy):
self.failed_proxies.add(proxy)
# 使用示例
proxy_pool = ProxyPool()
session = requests.Session()
for _ in range(3): # 嘗試3個(gè)不同代理
try:
proxy = proxy_pool.get_proxy()
response = session.get(
'https://api.sf-express.com/track',
proxies=proxy,
timeout=(3, 10)
)
if response.status_code == 200:
print("Success with proxy:", proxy)
break
else:
proxy_pool.mark_failed(proxy)
except Exception:
proxy_pool.mark_failed(proxy)
else:
print("All proxies failed")
2. Tor代理配置(高匿名場景)
import requests
def make_tor_request(url):
proxies = {
'http': 'socks5h://127.0.0.1:9050',
'https': 'socks5h://127.0.0.1:9050'
}
try:
response = requests.get(url, proxies=proxies, timeout=(5, 15))
print("Tor出口IP:", response.json()['origin'])
return response
except Exception as e:
print("Tor請(qǐng)求失敗:", e)
# 需提前安裝Tor服務(wù)并啟動(dòng)
# sudo apt install tor # Ubuntu系統(tǒng)
# sudo service tor start
五、完整實(shí)戰(zhàn)案例
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import logging
from datetime import datetime
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
class SFExpressTracker:
def __init__(self, app_key, app_secret):
self.app_key = app_key
self.app_secret = app_secret
self.session = self._create_session()
def _create_session(self):
"""創(chuàng)建帶重試和超時(shí)的會(huì)話"""
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def _generate_sign(self, params):
"""生成API簽名(簡化版)"""
import hashlib
sorted_params = sorted(params.items(), key=lambda x: x[0])
raw_str = self.app_secret + ''.join([f"{k}{v}" for k, v in sorted_params]) + self.app_secret
return hashlib.md5(raw_str.encode()).hexdigest().upper()
def query_track(self, tracking_number):
"""查詢物流軌跡"""
url = "https://bsp-ois.sf-express.com/bsp-ois/express/service/queryTrack"
params = {
"appKey": self.app_key,
"trackNumber": tracking_number,
"timestamp": str(int(datetime.now().timestamp()))
}
params["sign"] = self._generate_sign(params)
try:
response = self.session.get(
url,
params=params,
timeout=(3, 10) # 連接3秒,讀取10秒
)
response.raise_for_status()
data = response.json()
if data.get('success'):
return data['data']['tracks']
else:
logging.warning(f"API返回錯(cuò)誤: {data.get('errorMsg', '未知錯(cuò)誤')}")
return None
except requests.exceptions.RequestException as e:
logging.error(f"請(qǐng)求失敗: {str(e)}")
return None
# 使用示例
if __name__ == "__main__":
tracker = SFExpressTracker(app_key="YOUR_APP_KEY", app_secret="YOUR_APP_SECRET")
tracks = tracker.query_track("SF123456789")
if tracks:
for step in tracks:
print(f"{step['acceptTime']} {step['acceptAddress']} - {step['remark']}")
六、常見問題Q&A
Q1:被網(wǎng)站封IP怎么辦?
A:立即啟用備用代理池,建議使用住宅代理(如站大爺IP代理),配合每請(qǐng)求更換IP策略。對(duì)于大規(guī)模爬取,可采用Tor網(wǎng)絡(luò)或IP輪換中間件。
Q2:如何選擇重試次數(shù)?
A:遵循"3次黃金法則":
- 首次請(qǐng)求
- 指數(shù)退避重試2次(總計(jì)3次)
- 超過3次仍失敗應(yīng)觸發(fā)熔斷或人工干預(yù)
Q3:代理請(qǐng)求速度慢怎么解決?
A:
- 測試代理延遲:
curl --socks5 127.0.0.1:9050 https://httpbin.org/ip - 使用代理評(píng)分機(jī)制,淘汰高延遲代理
- 對(duì)代理請(qǐng)求添加
User-Agent和常規(guī)請(qǐng)求頭
Q4:如何記錄重試日志?
A:擴(kuò)展Retry類實(shí)現(xiàn)自定義日志:
from urllib3.util.retry import Retry
import logging
class LoggingRetry(Retry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = logging.getLogger(__name__)
def new(self, **kw):
self.logger.debug(f"Creating new retry adapter with params: {kw}")
return super().new(**kw)
def increment(self, method, url, response=None, error=None, **kwargs):
self.logger.warning(f"Retry attempt {self.total - self._remaining + 1} for {method} {url}")
return super().increment(method, url, response, error, **kwargs)
Q5:POST請(qǐng)求重試需要注意什么?
A:
- 確保請(qǐng)求是冪等的(如使用唯一請(qǐng)求ID)
- 在重試前檢查響應(yīng)是否已部分處理
- 考慮使用
idempotency-key請(qǐng)求頭(如Stripe API要求)
通過合理組合超時(shí)配置、智能重試和代理策略,可構(gòu)建出應(yīng)對(duì)各種異常場景的健壯API請(qǐng)求系統(tǒng)。實(shí)際開發(fā)中建議結(jié)合Prometheus監(jiān)控重試率、失敗率等指標(biāo),持續(xù)優(yōu)化請(qǐng)求策略。
以上就是Python Requests實(shí)現(xiàn)API請(qǐng)求重試與超時(shí)配置全解析的詳細(xì)內(nèi)容,更多關(guān)于Python Requests請(qǐng)求重試與超時(shí)配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python報(bào)錯(cuò)TypeError: ‘dict‘ object is not&
在Python開發(fā)的旅程中,報(bào)錯(cuò)信息就像是一個(gè)個(gè)路障,阻礙著我們前進(jìn)的步伐,而“TypeError: ‘dict’ object is not iterable”這個(gè)報(bào)錯(cuò),常常讓開發(fā)者們陷入困惑,那么,這個(gè)報(bào)錯(cuò)究竟是怎么產(chǎn)生的呢?又該如何有效地解決它呢?讓我們一起深入探討,找到解決問題的方法2024-10-10
使用Python防止SQL注入攻擊的實(shí)現(xiàn)示例
這篇文章主要介紹了使用Python防止SQL注入攻擊的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
python Pandas中數(shù)據(jù)的合并與分組聚合
大家好,本篇文章主要講的是python Pandas中數(shù)據(jù)的合并與分組聚合,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
能讓你輕松的實(shí)現(xiàn)自然語言處理的5個(gè)Python庫
今天教大家如何你輕松的實(shí)現(xiàn)自然語言預(yù)處理,僅僅需要5個(gè)python庫,文中介紹的非常詳細(xì),對(duì)正在學(xué)習(xí)python的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05
Python+Qt身體特征識(shí)別人數(shù)統(tǒng)計(jì)源碼窗體程序(使用步驟)
這篇文章主要介紹了Python+Qt身體特征識(shí)別人數(shù)統(tǒng)計(jì)源碼窗體程序(使用步驟),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12
Python上級(jí)目錄文件導(dǎo)入的幾種方法(from.import)
有時(shí)候我們可能需要import另一個(gè)路徑下的python文件,下面這篇文章主要給大家介紹了關(guān)于Python上級(jí)目錄文件導(dǎo)入的幾種方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
教你使用Python建立任意層數(shù)的深度神經(jīng)網(wǎng)絡(luò)
這篇文章主要介紹了Python建立任意層數(shù)的深度神經(jīng)網(wǎng)絡(luò),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08

