برنامهای که با دنیای بیرون حرف میزند
فرض کنید کارگاه میخواهد قیمت سفارشهای صادراتی را با نرخ روز دلار نشان دهد، یا وضعیت ارسال را از API یک شرکت پست بگیرد. کتابخانهی requests استاندارد عملی پایتون برای HTTP است (python -m pip install requests). فرستادن درخواست یک خط است؛ کار حرفهای، رفتار درست وقتی چیزی خراب میشود است، و در ایران چیزها زیاد خراب میشوند: قطعی، کندی، فیلتر و 403 تحریم.
کد کامل با همهی محافظها
import logging
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
log = logging.getLogger(__name__)
API_URL = "https://api.example.ir/v1/rates" # آدرس فرضی
def make_session() -> requests.Session:
session = requests.Session()
retry = Retry(
total=3,
backoff_factor=0.5, # مکث بین تلاشها، هر بار دو برابر
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=("GET",),
)
session.mount("https://", HTTPAdapter(max_retries=retry))
session.headers["User-Agent"] = "carpet-orders/1.0"
return session
def usd_rate(session: requests.Session) -> int | None:
try:
resp = session.get(API_URL, params={"symbol": "USD"}, timeout=(3.05, 10))
resp.raise_for_status()
return int(resp.json()["price"])
except requests.Timeout:
log.warning("سرور در زمان مقرر پاسخ نداد")
except requests.ConnectionError:
log.warning("اتصال برقرار نشد: اینترنت، DNS یا فیلتر")
except requests.exceptions.RetryError:
log.warning("سرور بعد از چند تلاش هم خطای 5xx یا 429 داد")
except requests.HTTPError as exc:
code = exc.response.status_code
reason = "احتمالاً تحریم یا کلید API" if code == 403 else "خطای HTTP"
log.warning("%s (%s)", reason, code)
except (ValueError, KeyError):
log.warning("پاسخ JSON معتبر نبود یا فیلد price نداشت")
return None
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
rate = usd_rate(make_session())
print("نرخ دلار:", rate if rate is not None else "فعلاً در دسترس نیست")
استثناها چه میگویند؟
| استثنا | معنی | واکنش منطقی |
|---|---|---|
Timeout | اتصال یا پاسخ در زمان تعیینشده نرسید | تلاش دوباره یا مقدار ذخیرهشدهی قبلی |
ConnectionError | اصلاً وصل نشد (DNS، قطعی، فیلتر) | پیام روشن به کاربر، بررسی پراکسی |
HTTPError با 403 | سرور شما را نمیپذیرد؛ برای سرویسهای خارجی معمولاً تحریم IP ایران | تلاش دوباره فایده ندارد؛ پراکسی یا سرویس جایگزین |
HTTPError با 401 | کلید یا توکن نامعتبر | بررسی تنظیمات، نه retry |
ValueError از json() | پاسخ JSON نیست، مثلاً صفحهی HTML فیلترینگ | لاگ کردن resp.text[:200] |
پراکسی و سایتهای داخلی
requests پراکسی را از متغیرهای محیطی میخواند، یا میتوانید صریحاً بدهید. برای سرویس خارجی پراکسی لازم است و برای سایت داخلی نه:
$env:HTTPS_PROXY = "http://127.0.0.1:10809"
$env:NO_PROXY = "localhost,127.0.0.1,.ir"
python rates.py
proxies = {"https": "http://127.0.0.1:10809"}
resp = requests.get("https://api.example.com/status", proxies=proxies, timeout=10)
برای ارسال داده هم session.post(url, json={"code": "KSH-101"}, timeout=10) بنویسید؛ آرگومان json= هم بدنه را میسازد و هم هدر Content-Type را درست تنظیم میکند.
نکتههایی که کمتر کسی میداند
- requests هیچ timeout پیشفرضی ندارد؛ درخواست بدون timeout میتواند برای همیشه گیر کند و برنامهی زمانبندیشده را بیصدا متوقف کند.
- timeout «زمان کل» نیست: عدد دوم سقف فاصلهی بین دو تکهی داده است. پاسخی که آهسته ولی پیوسته برسد، میتواند دقیقهها طول بکشد.
- وقتی تلاشهای Retry روی کدهای 5xx تمام شود، استثنا
RetryErrorاست نهHTTPError؛ اگر فقط HTTPError را بگیرید، برنامه با traceback میافتد. - سایتی که در هدر charset اعلام نکند، با کدگذاری ISO-8859-1 خوانده میشود و
resp.textفارسی را بههمریخته نشان میدهد؛ قبل از خواندن متنresp.encoding = "utf-8"بگذارید. - اگر پراکسی در متغیرهای محیطی تنظیم باشد، درخواست به سایتهای داخلی هم از آن رد میشود و کند یا مسدود میشود؛
NO_PROXYبا پسوند.irیاsession.trust_env = Falseمشکل را حل میکند.