مقدمه
در این راهنمای عملی برای توسعهدهندگان پایتون میآموزیم چطور با استفاده از HTTPX درخواستهایی را که ناموفق میشوند دوباره تلاش کنیم تا اسکریپر پایدارتر و قابل اطمینانتری بسازیم. هدف این مقاله ارائهٔ دو رویکرد رایج—استفاده از یک استراتژی Retry و نوشتن منطق سفارشی—همراه با مثالهای کد، توضیحات مرحلهبهمرحله و نکات عملی برای تولیدکنندگان اسکریپر است.
پس از خواندن این مطلب، شما میتوانید: یک استراتژی Retry با پارامترهای معمول پیکربندی کنید، منطق مبتنی بر محتوا برای تشخیص صفحات بن (ban) پیادهسازی کنید، و بهترین روشها برای کاهش ریسک بلاک یا خطا در اسکریپینگ را بهکار بگیرید.
استراتژی Retry با httpx.Retry
ایدهٔ کلی: از یک شیء Retry برای تعریف قوانینی استفاده میکنیم که چه زمانهایی و چند بار باید درخواست تکرار شود. این روش ساده و خوانا است و مناسب سناریوهایی است که میتوان تصمیم به تکرار را فقط بر اساس وضعیت HTTP یا خطاهای شبکه گرفت.
مثال زیر نمونهٔ پایهای است که پارامترهای متداول را تنظیم میکند و یک درخواست GET ساده انجام میدهد:
import httpx
retry_strategy = httpx.Retry(
total=3, # تعداد کل تلاشها (شامل درخواست اولیه)
status_forcelist=[500,502,503,504],
backoff_factor=0.5, # ضریب افزایش تاخیر بین تلاشها
method_whitelist=["GET"] # روشهایی که مجاز به Retry هستند
)
def make_request():
url = "http://quotes.toscrape.com/"
with httpx.Client() as client:
response = client.get(url, retry=retry_strategy)
if response.status_code == 200:
return response.json()
else:
return None
# اجرای نمونه
data = make_request()
if data is not None:
print(data)
else:
print("Request failed after retries.")توضیح ورودیها/خروجیها و نقش توابع:
retry_strategy: شیء پیکربندیِ Retry که قواعد تکرار را نگه میدارد.
make_request(): تابعی که آدرس را میگیرد (در مثال ثابت است)، یک Client باز میکند، درخواست را ارسال میکند و یا دادهٔ JSON را برمیگرداند یا None اگر پس از تلاشها پاسخی مناسب دریافت نشود.
شرح گامبهگام مهمترین خطوط:
- تعریف retry_strategy با پارامترهای قابل تنظیم.
- باز کردن یک httpx.Client و ارسال درخواست با retry=retry_strategy.
- بررسی کد وضعیت و برگرداندن نتیجه یا None.
پارامترهای رایج Retry و معنی آنها
total: حداکثر دفعات تلاش (شامل تلاش اولیه). نمونهٔ پیشفرض 3 است.
backoff_factor: عامل تعیینکننده رشد تاخیر بین تلاشها. تاخیر معمولاً به صورت نمایی افزایش مییابد.
status_forcelist: لیست کدهای HTTP که دریافت آنها سبب تکرار شود (مثلاً 500, 502, 503, 504).
method_whitelist: محدود کردن متدهای HTTP که میتوانند Retry شوند (برای جلوگیری از rerun عملیات غیر idempotent مانند POST).
status_allowlist: حالت معکوس؛ تنها وضعیتهای مشخصشده مجاز به Retry خواهند بود.
method_retry: یک callable که بر اساس روش و وضعیت تصمیم به Retry میگیرد؛ مناسب برای منطقهای پیچیده.
backoff_max: حداکثر تاخیر بین تلاشها تا از افزایش بیش از حد جلوگیری شود.
raise_on_redirect و raise_on_status: تعیین میکنند که آیا برای ریدایرکت یا وضعیت ناموفق استثنا بالا رود یا نه.
الگوریتم Backoff و مثال توالی تاخیر
معمولترین الگوریتمی که استفاده میشود اینگونه است:
{backoff_factor} * (2 ** ({retry_number} - 1))مثال توالیها (برای سهمیهٔ retryهای متوالی):
# backoff_factor = 0.5
0.5, 1.0, 2.0, 4.0, ...
# backoff_factor = 1
1, 2, 4, 8, ...نکتهٔ عملی: همواره از ترکیب backoff با jitter (افزودن تصادفیسازی کوچک به تاخیر) استفاده کنید تا همزمانی دوبارهٔ تعداد زیادی از درخواستها را کاهش دهید و خطر spike را کم کنید.
منطق سفارشی (بررسی محتوا و تشخیص صفحات بن)
مسئلهٔ رایج در اسکریپینگ این است که برخی سایتها در عوض بازگرداندن کد خطا، یک صفحهٔ بن یا کپچا با کد 200 ارسال میکنند. در این موارد باید محتوا را چک کنیم و بر اساس آن تصمیم به Retry بگیریم.
مثال زیر نشان میدهد چگونه میتوان تا زمانی که محتوای پاسخ بن را نشان میدهد تکرار انجام داد:
import httpx
retry_strategy = httpx.Retry(
total=3,
status_forcelist=[500, 502, 503, 504],
)
def make_request():
url = "http://quotes.toscrape.com/"
with httpx.Client() as client:
for _ in range(retry_strategy.total + 1):
response = client.get(url, retry=retry_strategy)
if response.status_code == 200:
# بررسی محتوای صفحه برای الگوی بن
if 'Robot or human? ' in response.text:
print("Retrying due to error in response")
continue
else:
return response
elif response.status_code == 404:
return response
else:
print("Retrying due to non-200 status code")
continue
return None
# استفاده
response = make_request()
if response is not None:
print(response.text)
else:
print("Request failed after retries.")نکات فنی و توضیح خطبهخط:
حلقهٔ for بر اساس total تنظیم شده تا تعداد تلاشها کنترل شده باشد.
بعد از دریافت 200، محتوای response.text بررسی میشود تا بن/کپچا تشخیص داده شود. اگر الگو یافت شد، تکرار انجام میشود.
برای 404 معمولا تلاش مجدد بیمعنی است؛ در این مثال مستقیما پاسخ بازگردانده میشود.
اگر هیچ تلاش موفقی یافت نشد، None بازگردانده میشود تا فراخوان بداند عملیات ناموفق بوده است.
نکات عملی، امنیت و عملکرد
- تعیین متدهای قابل تکرار: عملیات غیر idempotent (مانند عملیات مالی یا POSTهایی که باعث ایجاد داده میشوند) نباید بدون بررسی خاصی دوباره اجرا شوند.
- استفاده از jitter: برای کاهش همزمانی فراخوانها میان کلاینتها، مقدار تاخیر را با مقدار تصادفی کوچک جمع کنید.
- حداکثر تاخیر: backoff_max تعیین کنید تا تأخیرها از حد معقول خارج نشوند.
- محدودیت نرخ (rate limiting): به ریتلیمیت سرور احترام بگذارید؛ ترکیب retry با نرخسنج محلی یا صفبندی مفید است.
- پروکسی و چرخش آیپی: برای اسکریپ بزرگ یا سیستمهای توزیعشده از پراکسیهای چرخان استفاده کنید؛ اما مراقب سیاستهای سرویسدهنده باشید.
- مدیریت استثناها: خطاهای شبکه (مانند connection timeout، DNS error) را catch کنید و برای آنها رفتار جداگانهای (مثلاً Retry بیشتر یا تغییر پراکسی) تعریف کنید.
- مانیتورینگ و متریک: شمارش Retryها، نرخ موفقیت پس از Retry و زمان پاسخ را ثبت کنید تا بتوانید استراتژی را بهینه کنید.
- ملاحظات امنیتی و اخلاقی: قوانین سایت و فایل robots.txt را در نظر داشته باشید؛ از ضربه زدن به سرور (DDoS-like) با تلاشهای مکرر بپرهیزید.
- کنترل همزمانی: وقتی تعداد زیادی ریکوئست بهطور همزمان اجرا میشود، Retryها میتوانند فشار مضاعف ایجاد کنند؛ از صفبندی و محدودیت همزمانی استفاده کنید.
تطبیق با حالت async
اگر اسکریپر شما از HTTPX بهصورت async استفاده میکند، منطق بسیار مشابه است اما باید AsyncClient و async/await را بهکار ببرید. اصول retry، backoff و بررسی محتوا یکسان باقی میماند، فقط پیادهسازی حلقه و مدیریت کانکشن متفاوت است.
جمعبندی
دو رویکرد اصلی برای Retry در HTTPX عبارتاند از: استفاده از یک استراتژی Retry آماده برای شرایط سادهتر، و نوشتن منطق سفارشی برای سناریوهای خاص مثل تشخیص صفحات بن با کد 200. همیشه از backoff نمایی بههمراه jitter استفاده کنید، متدهای غیر idempotent را با احتیاط مجوز Retry بدهید و مانیتورینگ و محدودیتهای نرخ را فراموش نکنید. با رعایت این نکات، اسکریپر شما مقاومتر در برابر خطاها و بلاکهای موقت خواهد شد.





