مقدمه
خطای 403 Forbidden در هنگام اسکریپینگ با Scrapy یکی از رایجترین و در عین حال گمراهکنندهترین خطاهاست؛ لاگها معمولاً فقط میگویند «Ignored response <403 ...>» و دلیل دقیق را نشان نمیدهند. در این راهنما قدمبهقدم روشهای تشخیص و رفع خطای 403 را با تمرکز روی Scrapy، تنظیم هدرها، چرخش یوزر-اجنت و پراکسیها توضیح میدهیم. پس از خواندن این مقاله شما خواهید توانست:
- تشخیص دهید که 403 به خاطر نیاز به احراز هویت است یا بلاک شدن بهعنوان اسکرابر،
- تنظیمات Scrapy را برای نمایش بدنهٔ پاسخهای 403 تغییر دهید،
- یوزر-اجنتها و هدرها را به شکل ایمن و چرخشی پیکربندی کنید،
- استراتژیهای پراکسی و بهترین روشهای پایدارسازی را پیادهسازی کنید.
چگونه بفهمیم 403 به چه دلیلی رخ داده؟
قبل از هر راهحلی باید تشخیص دهیم 403 ناشی از چیست. دو حالت معمول وجود دارد:
- منبع واقعاً نیاز به احراز هویت یا دسترسی دارد (مثلاً صفحهٔ لاگین یا API محافظتشده).
- وبسایت شما را بهعنوان ربات شناسایی کرده و عمداً پاسخ 403 یا صفحهٔ بن بازمیگرداند.
روشهای تشخیص سریع:
- درخواست را با مرورگر باز کنید و به محتوای بدنه نگاه کنید: آیا فرم لاگین یا پیام «access denied» وجود دارد؟
- هدرهای پاسخ را بررسی کنید: وجود سرویسهایی مثل Cloudflare یا نامهای شناختهشده ممکن است نشاندهندهٔ چالش ضد-بات باشد.
- کپی سرایپتی از همان URL با یک مرورگر واقعی یا ابزار مثل curl بگیرید و تفاوتها را مقایسه کنید (مثلاً کوکیها، جاوااسکریپت، چالشهای JS).
دیدن بدنهٔ پاسخ 403 در Scrapy
بهصورت پیشفرض میدلویر خطای HTTP در Scrapy پاسخهای 403 را از مسیر معمول callback حذف میکند. برای دیدن بدنه و عیبیابی باید اجازه دهید این پاسخها در callback ظاهر شوند. دو روش متداول:
- افزودن صریح در کلاس اسپایدر:
class MySpider(scrapy.Spider):
name = 'myspider'
# حالا پاسخهای 403 به callback شما فرستاده میشوند
handle_httpstatus_list = [403]
def start_requests(self):
yield scrapy.Request('https://example.com/protected', callback=self.parse)
def parse(self, response):
if response.status == 403:
# اکنون میتوانید body را ببینید و آنالیز کنید
self.logger.info('Got 403 body: %s', response.text[:200])
توضیح کوتاه: handle_httpstatus_list یک لیست از کدهای HTTP است که Scrapy نباید آنها را نادیده بگیرد. پس از این، در parse میتوانید response.status و response.text را بررسی کنید.
گامهای عمومی جهت رفع 403 (خلاصهٔ استراتژی)
- فاصلهگذاری و تصادفیسازی درخواستها (تا سرور شما را ربات تشخیص ندهد).
- ارسال یوزر-اجنتهای واقعی و چرخشی.
- بهینهسازی هدرها تا درخواست شبیه مرورگر واقعی شود.
- استفاده از پراکسیهای چرخشی در حجمهای بالا.
Randomising Request Delays (تنظیم تأخیرها)
ارسال درخواستها با فواصل زمانی ثابت باعث شناسایی آسان میشود. Scrapy تنظیمات سادهای برای این دارد:
# settings.py
DOWNLOAD_DELAY = 2 # به ثانیه
# به طور پیشفرض RANDOMIZE_DOWNLOAD_DELAY = True است
شرح: اگر DOWNLOAD_DELAY روی 2 قرار گیرد، Scrapy بین 1 و 3 ثانیه بهطور تصادفی بین هر درخواست صبر میکند (حدوداً 0.5 تا 1.5 برابر مقدار). برای پروژههای کوچک، افزایش این مقدار احتمال بن شدن را کاهش میدهد.
Fake User-Agents و روشهای چرخشی
اگر سرور از روی User-Agent شما را شناسایی کند، سادهترین راه ارسال یک یوزر-اجنت واقعی یا بهتر: چرخش بین یک مجموعهٔ بزرگ از یوزر-اجنتهاست.
روش ساده (در settings.py):
# settings.py
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0'
این روش برای تراکنشهای کوچک کافی است اما اگر همه درخواستها یک یوزر-اجنت داشته باشند، تشخیص دشوار نیست.
روش بهتر: استفاده از یک middleware که یوزر-اجنت را برای هر درخواست تصادفی میکند. نمونههای متداول در اکوسیستم Scrapy وجود دارد؛ نصب معمولاً با pip است:
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 یوزر-اجنت تصادفی را انتخاب میکند، و در صورت خطا middleware retry مربوط، دوباره با یوزر-اجنت جدید تلاش میکند.
بهینهسازی Request Headers
بسیاری از سیستمهای ضد-بات نه تنها User-Agent را نگاه میکنند، بلکه سازگاری سایر هدرها را با آن بررسی میکنند (مثل Accept، Accept-Language، Sec-Fetch-* و غیره). ارسال هدرهای یک مرورگر واقعی شانس بلاک شدن را کاهش میدهد.
مثال: افزودن هدرهای بهدستآمده از یک مرورگر معمول:
# example_spider.py
class BookSpider(scrapy.Spider):
name = 'bookspider'
url_list = ['http://books.toscrape.com']
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/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.url_list:
yield scrapy.Request(url=url, callback=self.parse, headers=self.HEADERS)
def parse(self, response):
# پردازش معمول
pass
توضیح: اینجا هدرها را روی هر درخواست میفرستیم تا رفتار درخواستها به نمونههای واقعی نزدیک شود. مهم است که هدرها با یوزر-اجنت همخوانی داشته باشند؛ یعنی اگر یوزر-اجنت یک مرورگر Chrome است، هدرهای Chrome را ارسال کنید.
استفاده از پراکسیهای چرخشی
اگر آیپیهای شما قبلاً بن شده باشند یا حجم درخواستها بالا باشد، نیاز به پراکسی چرخشی دارید. یکی از middlewareهای رایج برای Scrapy، scrapy-rotating-proxies است.
pip install scrapy-rotating-proxies
نمونهٔ پیکربندی:
# settings.py
ROTATING_PROXY_LIST = [
'proxy1.example:8000',
'proxy2.example:8031',
'proxy3.example:8032',
]
DOWNLOADER_MIDDLEWARES.update({
'rotating_proxies.middlewares.RotatingProxyMiddleware': 610,
'rotating_proxies.middlewares.BanDetectionMiddleware': 620,
})
شرح: با این پیکربندی هر درخواست از یکی از پراکسیهای لیست عبور میکند و میدلویری که شناسایی بن را انجام میدهد، پراکسیهای مشکلدار را حذف یا دور میزند.
نکات امنیتی و بهترینروشها
- همیشه به قوانین سایت و فایل robots.txt احترام بگذارید و اگر داده حساس یا نیازمند مجوز است از روشهای قانونی استفاده کنید.
- برای حجم بالا از پراکسیهای معتبر و با چرخهٔ تعویض مناسب استفاده کنید تا IPتان پایدار نباشد.
- کریتهای ورود یا توکنهای شخصی را در کد مخفی نگه دارید و از متغیرهای محیطی یا vault استفاده کنید.
- مانیتورینگ خطا و retry هوشمند را فعال کنید: تعداد تلاشها، backoff تصاعدی و لاگبرداری از الگوهای بن مهم است.
تشخیص Cloudflare و سطوح امنیتی
Cloudflare یکی از رایجترین سرویسهای ضد-بات است. وقتی با صفحه یا جوابهایی مواجه شدید که شامل challenge یا ریدایرکت جاوااسکریپتی هستند، احتمالاً Cloudflare در کار است. راهکارها شامل استفاده از پراکسیهایی که این چالشها را حل میکنند، یا سرویسهای مخصوص bypass است. معمولاً سطوح امنیتی مختلفی وجود دارد (از کم تا بالا) که به ترتیب منابع و هزینهٔ بیشتری برای عبور نیاز دارند؛ انتخاب درست بستگی به سطح بررسی و حجم درخواست شما دارد.
مثال کامل: ترکیب همهٔ موارد
معمولاً باید چند روش را همزمان به کار ببرید: تأخیر تصادفی، یوزر-اجنت چرخشی، هدرهای بهینه و پراکسی چرخشی. یک جریان کاری نمونه:
- اول با handle_httpstatus_list بدنهٔ 403 را ببینید و دلیل را تشخیص دهید.
- تنظیم DOWNLOAD_DELAY و فعالسازی randomize.
- نصب و پیکربندی middleware یوزر-اجنت تصادفی.
- بهینهسازی هدرها بر اساس یوزر-اجنت انتخابشده.
- در صورت ادامهٔ بن، انتقال ترافیک به پراکسیهای چرخشی و مانیتور کردن نرخ خطا.
جمعبندی
خطای 403 در Scrapy معمولاً یا به دلیل نیاز به احراز هویت است یا به این خاطر که وبسایت شما را بهعنوان یک ربات شناسایی کرده است. برای عیبیابی ابتدا بدنهٔ پاسخ را ببینید، سپس از تکنیکهایی مثل تأخیر تصادفی، یوزر-اجنتهای چرخشی، بهینهسازی هدرها و پراکسیهای چرخشی استفاده کنید. اجرای همزمان این روشها در محیط تولید، همراه با لاگبرداری و مانیتورینگ، بهترین شانس را برای کاهش 403 و پایدارسازی اسکریپینگ شما فراهم میکند.





