خطای HTTP 503 در هنگام اسکریپینگ با Scrapy یکی از شایعترین و در عین حال گیجکنندهترین مشکلات است: گاهی سرور واقعاً در دسترس نیست و گاهی بهصورت عمدی درخواستهای رباتها را با 503 بلاک میکند. در این مقاله بهصورت گامبهگام روشهای عیبیابی و رفع این خطا را برای توسعهدهندههای پایتون سطح متوسط توضیح میدهم. در پایان شما میدانید چگونه تشخیص دهید سرور واقعاً down است یا ترافیک شما بلاک شده، چگونه با تغییر User-Agent و هدرها مشکل را حل کنید، و چه زمانی باید از پراکسی چرخان استفاده کنید.
تشخیص: آیا سرور واقعاً از کار افتاده است؟
قبل از هر تغییری، تشخیص اینکه 503 واقعی است یا «جعلی» (یعنی سرور شما را بهعنوان اسکرپر تشخیص داده) ضروری است.
- گام 1 — باز کردن URL با مرورگر: اگر در مرورگر دسکتاپ یا موبایل صفحه را میبینید، احتمالاً 503 مربوط به بلاک شدن ربات است.
- گام 2 — تست با headless browser: از Selenium یا Playwright استفاده کنید تا رفتار واقعی یک مرورگر را شبیهسازی کنید؛ اگر headless هم موفق است، مشکل از پیکربندی Scrapy شماست.
- گام 3 — تست زمانبندی و تکرار: اگر سرور در زمانهای مختلف هم 503 میدهد (بدون تغییر در هدرها یا IP)، ممکن است سرور واقعاً تحت نگهداری یا بارگذاری بالا باشد.
نتیجهگیری: اگر فقط از Scrapy خطای 503 میگیرید ولی مرورگر OK است → سرور شما را تشخیص میدهد؛ در غیر این صورت صبر و پیگیری وضعیت سرور لازم است.
راه حل سریع: استفاده از یک Proxy Aggregator
در پروژههای واقعی، سریعترین و پربازدهترین راه حل برای رفع 503 ناشی از بلاک شدن، استفاده از یک پراکسی هوشمند است که هدرها، یوزر-ایجنت و روتینگ را بهینه میکند. نام سرویسها (مثلاً ScrapeOps) را میتوان مستقیماً استفاده کرد؛ این سرویسها با یک endpoint پروکسی کار میکنند که URL هدف را بهعنوان پارامتر میگیرد.
# نمونه تابع ساخت URL برای یک Proxy Aggregator
from urllib.parse import urlencode
API_KEY = 'YOUR_API_KEY'
def get_proxy_endpoint(url: str) -> str:
"""ورودی: url (str) — آدرس مقصد
خروجی: proxy_url (str) — آدرسی که باید به جای url اصلی به Scrapy بدهیم
کارکرد: پارامترهای مورد نیاز سرویس پراکسی را میسازد و رشته URL نهایی را برمیگرداند."""
payload = {'api_key': API_KEY, 'url': url}
proxy_url = 'https://proxy.example.com/v1/?' + urlencode(payload)
return proxy_url
# استفاده در spider
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=get_proxy_endpoint(url), callback=self.parse)
توضیح کوتاه خطبهخط: تابع get_proxy_endpoint ورودی URL مقصد را میگیرد، پارامترها را urlencode میکند و URL نهایی پراکسی را میسازد. در spider کافی است هنگام ساخت Request بهجای URL اصلی، URL پراکسی را بگذارید؛ پراکسی ترافیک را بازنویسی و بهینه میکند.
استفاده از User-Agentهای جعلی
بسیاری از سایتها با بررسی User-Agent متوجه میشوند که درخواست از Scrapy میآید و بلافاصله 503 یا سایر کدهای بلاک را برمیگردانند. دو رویکرد معمول وجود دارد:
- روش ساده: تنظیم USER_AGENT در settings.py
- روش بهتر: چرخاندن User-Agent با middleware (مثلاً scrapy-fake-useragent)
# روش ساده: settings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
محدودیت: اگر روی همه درخواستها یک User-Agent ثابت بگذارید، سرویسهای پیشرفتهتر میتوانند با تحلیل الگوی ترافیک شما را شناسایی کنند.
# نصب scrapy-fake-useragent
pip install scrapy-fake-useragent
# پیکربندی نمونه در settings.py
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,
}
FAKEUSERAGENT_PROVIDERS = [
'scrapy_fake_useragent.providers.FakeUserAgentProvider',
'scrapy_fake_useragent.providers.FakerProvider',
'scrapy_fake_useragent.providers.FixedUserAgentProvider',
]
# مقدار USER_AGENT بهعنوان Fallback
USER_AGENT = ''
توضیح: این middleware بهصورت خودکار از یک لیست واقعی User-Agent استفاده میکند و برای هر درخواست یکی را انتخاب میکند؛ همچنین اگر یک User-Agent باعث خطا شد، Middleware مربوطه میتواند آن درخواست را دوباره با User-Agent دیگری امتحان کند.
بهینهسازی هدرهای درخواست
فراتر از User-Agent، وبسایتهای حرفهای هدرهای دیگر مثل Accept-Encoding، sec-ch-ua و Sec-Fetch-* را بررسی میکنند تا مطمئن شوند که مجموعه هدرها با هم سازگارند. اگر فقط User-Agent را تغییر دهید ولی سایر هدرها همان Scrapy پیشفرض باشند، احتمال تشخیص شما بالاست.
# هدرهای سادهای که Scrapy معمولا میفرستد
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en
User-Agent: Scrapy/VERSION (+https://scrapy.org)
# نمونه هدرهای شبیهسازیشده یک مرورگر (ساختگی برای مثال)
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
sec-ch-ua: "Chromium";v="116"; "Google Chrome";v="116"
# اضافه کردن هدرها در یک Spider (مثال ساده)
import scrapy
class BookSpider(scrapy.Spider):
name = 'bookspider'
start_urls = ['http://books.toscrape.com']
DEFAULT_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
}
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url=url, callback=self.parse, headers=self.DEFAULT_HEADERS)
def parse(self, response):
# پردازش پاسخ: خروجی نمونه یک آیتم کتاب
for article in response.css('article.product_pod'):
yield {
'url': article.css('h3 > a::attr(href)').get(),
'title': article.css('h3 > a::attr(title)').get(),
}
توضیح: در این مثال یک دیکشنری هدر تعریف شده و به هر درخواست اضافه میشود. دقت کنید که User-Agent باید با سایر هدرها سازگار باشد (مثلاً اگر User-Agent مربوط به کروم است، بهتر است headerهای Chrome-like هم ارسال شوند).
استفاده از پراکسیهای چرخان (Rotating Proxies)
اگر مشکل از IP است (یعنی سرور آدرس IP شما را بلاک کرده یا نرخ درخواستها از یک IP زیاد است)، باید از پراکسیهای چرخان استفاده کنید تا هر یا چند درخواست از یک IP متفاوت فرستاده شود.
# نصب scrapy-rotating-proxies
pip install scrapy-rotating-proxies
# نمونه تنظیمات در settings.py
ROTATING_PROXY_LIST = [
'proxy1.example.com:8000',
'proxy2.example.com:8031',
'proxy3.example.com:8032',
]
DOWNLOADER_MIDDLEWARES = {
# سایر middleware های شما ...
'rotating_proxies.middlewares.RotatingProxyMiddleware': 610,
'rotating_proxies.middlewares.BanDetectionMiddleware': 620,
}
نکتههای عملی:
- کیفیت پراکسی مهم است: پراکسیهای رایگان معمولا کند و ناپایدارند و خودشان ممکن است بلاک شوند.
- مخلوطی از روتیشن User-Agent و پراکسی معمولاً نتایج بهتر میدهد.
- نرخ درخواست (rate limit) را کاهش دهید تا رفتار طبیعیتری داشته باشید و از بن شدن کامل جلوگیری کنید.
مدیریت خطا، Retry و پایداری
حتی با تمام بهینهسازیها، 503 گاهی یک پاسخ موقت است (مثلاً بهخاطر throttling یا maintenance). برای پایداری بهتر:
- از Retry Middleware با backoff نمایی استفاده کنید تا در مواجهه با 503 بهصورت هوشمند دوباره تلاش شود.
- لاگ کنید چه IP، چه User-Agent و چه هدرهایی هنگام 503 بودهاند تا الگوها را ببینید.
- محدودیت سرعت و پراکسی را با توجه به سیاست سایت هدف تنظیم کنید.
# مثال ساده middleware برای مدیریت 503 و retry (نمونه آموزشی)
import time
from scrapy import signals
from scrapy.http import Response
class Retry503Middleware:
"""یک middleware ساده که در صورت دریافت 503 تا N بار با تاخیر افزایشیابنده ریتری میکند."""
MAX_RETRIES = 3
def process_response(self, request, response, spider):
if response.status == 503:
retries = request.meta.get('retry_times', 0)
if retries < self.MAX_RETRIES:
wait = 2 ** retries # backoff نمایی: 1s, 2s, 4s
spider.logger.info(f"503 received. retrying in {wait}s (attempt {retries+1})")
time.sleep(wait)
new_req = request.copy()
new_req.meta['retry_times'] = retries + 1
return new_req
return response
توضیح: این middleware یک نمونه آموزشی است — در عمل از sleep در middleware اصلی استفاده نکنید (بدخیم است). بهتر است از قابلیتهای async و scheduler خود Scrapy یا افزونههایی برای backoff و retry استفاده کنید.
جمعبندی و چکلیست نهایی
خلاصه گامهای عملی برای رفع 503 در اسکریپینگ با Scrapy:
- ابتدا بررسی کنید آیا سایت در مرورگر باز میشود یا خیر؛ تشخیص «بلاک شدن» یا «down بودن» کلید است.
- اگر سایت شما را بلاک کرده: User-Agent را تغییر دهید و برای مقیاس بزرگ از چرخش User-Agent استفاده کنید.
- هدرها را طوری تنظیم کنید که با User-Agent شما همخوانی داشته باشند (Accept-Encoding، Sec-Fetch-* و غیره).
- در اسکریپهای بزرگ از پراکسی چرخان استفاده کنید و کیفیت پراکسی را بسنجید.
- برای پایداری از Retry هوشمند، لاگینگ دقیق و نرخمحدود (rate limiting) استفاده کنید.
اگر دنبال راهحل سریع و آماده هستید، Proxy Aggregatorها مثل ScrapeOps میتوانند بسیاری از کارهای پیچیده را برایتان انجام دهند؛ اما برای درک عمیق و کنترل کامل، ترکیب User-Agent چرخان، هدرهای سازگار و پراکسیهای با کیفیت بهترین نتیجه را میدهد.





