

خطای HTTP 503 در هنگام اسکریپینگ با Scrapy یکی از شایعترین و در عین حال گیجکنندهترین مشکلات است: گاهی سرور واقعاً در دسترس نیست و گاهی بهصورت عمدی درخواستهای رباتها را با 503 بلاک میکند. در این مقاله بهصورت گامبهگام روشهای عیبیابی و رفع این خطا را برای توسعهدهندههای پایتون سطح متوسط توضیح میدهم. در پایان شما میدانید چگونه تشخیص دهید سرور واقعاً down است یا ترافیک شما بلاک شده، چگونه با تغییر User-Agent و هدرها مشکل را حل کنید، و چه زمانی باید از پراکسی چرخان استفاده کنید.
قبل از هر تغییری، تشخیص اینکه 503 واقعی است یا «جعلی» (یعنی سرور شما را بهعنوان اسکرپر تشخیص داده) ضروری است.
نتیجهگیری: اگر فقط از Scrapy خطای 503 میگیرید ولی مرورگر OK است → سرور شما را تشخیص میدهد؛ در غیر این صورت صبر و پیگیری وضعیت سرور لازم است.
در پروژههای واقعی، سریعترین و پربازدهترین راه حل برای رفع 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 متوجه میشوند که درخواست از Scrapy میآید و بلافاصله 503 یا سایر کدهای بلاک را برمیگردانند. دو رویکرد معمول وجود دارد:
# روش ساده: 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 هم ارسال شوند).
اگر مشکل از 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,
}
نکتههای عملی:
حتی با تمام بهینهسازیها، 503 گاهی یک پاسخ موقت است (مثلاً بهخاطر throttling یا maintenance). برای پایداری بهتر:
# مثال ساده 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:
اگر دنبال راهحل سریع و آماده هستید، Proxy Aggregatorها مثل ScrapeOps میتوانند بسیاری از کارهای پیچیده را برایتان انجام دهند؛ اما برای درک عمیق و کنترل کامل، ترکیب User-Agent چرخان، هدرهای سازگار و پراکسیهای با کیفیت بهترین نتیجه را میدهد.


