مقدمه
در این مقاله تکنیکهای عملی برای مدیریت User Agent و پروکسی در پروژههای اسکریپینگ با Scrapy را یاد میگیریم. هدف این راهنما این است که پس از خواندن آن بتوانید: تشخیص دهید چرا سایتها درخواستها را بلاک میکنند، چگونه User-Agentها را چرخانده و در Scrapy تنظیم کنید، و چگونه درخواستها را از طریق پروکسیها مسیردهی کنید تا از محدودیتها و تشخیصهای ضدربات عبور کنید.
چرا سایتها درخواستها را مسدود میکنند
در مقیاس بزرگ، مشکل اصلی نه گرفتن یک صفحه بلکه دریافت پایدار پاسخهای HTML است. سرورها با استفاده از ترکیبی از بررسی IP، User-Agent، رفتار زمانی (rate) و الگوهای ترافیک نامتعارف، ترافیک را تحلیل و ترافیکی را که به نظر اسکریپر میآید مسدود میکنند. در نتیجه لازم است هم سرنام (headers) و هم آدرسهای IP را مدیریت کنیم.
- تشخیص بر اساس IP: تعداد زیاد درخواستها از یک IP، باعث throttle یا ban میشود.
- تشخیص بر اساس User-Agent: User-Agent پیشفرض کتابخانهها میتواند واضحاً نشاندهندهٔ یک ربات باشد.
- سنجش رفتار: سرعت، ترتیب صفحات و الگوی دسترسی هم اثرگذار است.
استفاده از User Agent هنگام اسکریپینگ
User Agent یک رشته متنی در هدر HTTP است که نوع مرورگر، سیستمعامل و نسخه را اعلام میکند. مرورگرها و رباتها هرکدام User-Agent متفاوتی ارسال میکنند. نمونهای از یک User-Agent رایج:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Scrapy به طور پیشفرض یک User-Agent مانند Scrapy/VERSION (+https://scrapy.org) ارسال میکند که واضحاً ربات را نشان میدهد؛ لذا در پروژههای واقعی باید آن را تغییر یا چرخاند.
تغییر و چرخش User-Agent در Scrapy
سادهترین راه چرخش User-Agent استفاده از یک middleware آماده است که مجموعهای از User-Agentها را دارد و برای هر درخواست یک مورد تصادفی انتخاب میکند. یکی از بستههای رایج جامعهٔ Scrapy برای این منظور scrapy-user-agents است.
نصب بسته:
pip install scrapy-user-agents
سپس در settings.py باید میدلور پیشفرض UserAgent را غیرفعال کرده و میدلور چرخاننده را اضافه کنید. مثلاً:
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy_user_agents.middlewares.RandomUserAgentMiddleware': 400,
}
توضیح خطوط فوق:
- غیرفعال کردن UserAgentMiddleware پیشفرض تا رشتهٔ ثابت Scrapy ارسال نشود.
- فعالسازی RandomUserAgentMiddleware که از مجموعهای از User-Agentها به صورت تصادفی استفاده میکند.
نکات عملی و محدودیتها:
- چرخاندن User-Agent به تنهایی کافی نیست؛ ترکیب آن با پروکسی و مدیریت نرخ درخواست ضروری است.
- مجموعهٔ User-Agentها ممکن است شامل موارد قدیمی یا نادرست باشد؛ بررسی و بهروزرسانی دورهای مفید است.
پروکسیها: چرخش IP و عبور از محدودیتها
حتی با User-Agentهای تصادفی، سایتها آدرسهای IP را نیز رصد میکنند. وقتی تعداد زیادی درخواست از یک IP مشاهده شود، throttle یا ban رخ میدهد. پروکسیها راه حلی برای تغییر IP منبع درخواستها هستند؛ با استفاده از پروکسی هر درخواست از یک IP متفاوت یا از poolای از IPها ارسال میشود.
انواع پروکسیها:
- پروکسیهای رایگان: معمولاً ناپایدار و کند، مناسب تست و نمونهسازی است.
- پروکسیهای پولی: کیفیت، پوشش جغرافیایی و نرخ موفقیت بالاتر دارند؛ مناسب عملیات در مقیاس بالا.
- پروکسیهای چرخان (rotating): poolای از IPها را مدیریت کرده و برای هر درخواست IP را تغییر میدهند.
مزایا/معایب:
- مزایا: کاهش ریسک ban، افزایش نرخ موفقیت درخواستها، امکان دسترسی به محتوا با محدودیت جغرافیایی.
- معایب: هزینه، پیچیدگی در پیکربندی، نیاز به مدیریت concurrency متناسب با پلن پروکسی.
ادغام پروکسی با Scrapy — الگوی ساده
یک الگوی متداول، استفاده از سرویس پروکسی بهعنوان یک endpoint است که برای هر URL اصلی، آدرس جدیدی برمیگرداند. این تابع نمونهای است که URL را میگیرد و URL پروکسیشده برمیگرداند. توجه: کلید API را از متغیرهای محیطی دریافت کنید، نه در کد سختکد.
import os
from urllib.parse import urlencode
API_KEY = os.environ.get('SCRAPEOPS_API_KEY') # مقداردهی از متغیر محیطی
def get_proxy_url(url: str) -> str:
"""ورودی: آدرس مقصد
خروجی: آدرس endpoint پروکسی که درخواست را به URL مقصد فوروارد میکند.
این تابع یک querystring میسازد که شامل api_key و url مقصد است.
"""
payload = {'api_key': API_KEY, 'url': url}
proxy_url = 'https://proxy.scrapeops.io/v1/?' + urlencode(payload)
return proxy_url
نحوهٔ استفاده در اسپایدر: بهجای ارسال مستقیم به URL مقصد، از get_proxy_url استفاده کنید:
yield scrapy.Request(url=get_proxy_url(start_url), callback=self.parse)
توضیح: ورودی این فراخوانی یک رشتهٔ URL است؛ خروجی یک URL جدید است که داخل آن پارامترها (از جمله کلید API) جاسازی شدهاند. سرور پروکسی درخواست شما را میپذیرد و با IP خودش آن را به مقصد ارسال میکند.
نمونهٔ کاملتر اسپایدر (مرتب و سادهشده)
import scrapy
import os
from urllib.parse import urlencode
from chocolatescraper.itemloaders import ChocolateProductLoader
from chocolatescraper.items import ChocolateProduct
API_KEY = os.environ.get('SCRAPEOPS_API_KEY')
def get_proxy_url(url):
payload = {'api_key': API_KEY, 'url': url}
return 'https://proxy.scrapeops.io/v1/?' + urlencode(payload)
class ChocolateSpider(scrapy.Spider):
name = 'chocolatespider'
def start_requests(self):
start_url = 'https://www.chocolate.co.uk/collections/all'
yield scrapy.Request(url=get_proxy_url(start_url), callback=self.parse)
def parse(self, response):
products = response.css('div.product-item')
for product in products:
loader = ChocolateProductLoader(item=ChocolateProduct(), selector=product)
loader.add_css('name', 'a.product-item-meta__title::text')
loader.add_css('price', 'span.price')
loader.add_css('url', 'div.product-item-meta a::attr(href)')
yield loader.load_item()
next_page = response.css('[rel="next"]::attr(href)').get()
if next_page:
full = response.urljoin(next_page)
yield response.follow(get_proxy_url(full), callback=self.parse)
نکات توضیحی برای کد بالا:
- get_proxy_url: ورودی URL مقصد، خروجی URL پروکسیشده؛ پارامترها با urlencode اضافه میشوند.
- در start_requests درخواست را به آدرس پروکسیشده میفرستیم تا IP منبع تغییر کند.
- در parse مانند معمول دادهها را با ItemLoader استخراج و به خروجی میدهیم؛ سپس اگر صفحهٔ بعدی وجود داشت، مجدداً از پروکسی استفاده میکنیم.
تنظیمات همزمانی و ملاحظات عملی
وقتی تعداد زیادی درخواست میفرستید باید CONCURRENT_REQUESTS و محدودیتهای سرویس پروکسی را هماهنگ کنید. اگر پلن پروکسی محدود به تعداد همزمانی پایین باشد، بالا بردن CONCURRENT_REQUESTS باعث خطا و قطع برخی درخواستها میشود.
# settings.py
CONCURRENT_REQUESTS = 1
DOWNLOADER_MIDDLEWARES = {
# نمونه پیکربندی: میدلورهای مربوط به User-Agent و Proxy را در اینجا کنترل کنید
# 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
# 'scrapy_user_agents.middlewares.RandomUserAgentMiddleware': 400,
# 'scrapy_proxy_pool.middlewares.ProxyPoolMiddleware': 610,
# 'scrapy_proxy_pool.middlewares.BanDetectionMiddleware': 620,
}
نکات مهم:
- مدیریت خطا: از RetryMiddleware و بررسی وضعیتهای HTTP استفاده کنید تا در صورت پاسخهای 403/429 دوباره تلاش مناسب انجام دهید.
- حساسیت به CAPTCHA: پروکسیها ممکن است CAPTCHAها را دور نزنند؛ در اینصورت نیاز به سرویسهایی با پشتیبانی CAPTCHA یا مکانیزمهای انسانی دارید.
- امنیت کلید API: از os.environ برای نگهداری کلیدها استفاده کنید و آنها را در کنترل نسخه قرار ندهید.
- احترام به قوانین: قبل از اسکریپینگ از سیاستهای سایت و مسائل حقوقی/قانونی آگاه باشید.
بهترین روشها و نکات عملکردی
- ترکیب کردن: هم User-Agent چرخان و هم پروکسیهای چرخان را همزمان به کار ببرید تا الگوهای ردیابی کمتر قابل تشخیص باشد.
- محدود کردن نرخ: از DOWNLOAD_DELAY و محدودیت همزمانی استفاده کنید تا رفتار طبیعیتری شبیه به کاربر ایجاد کنید.
- نظارت و مانیتورینگ: لاگگیری، شمارش خطاها و مانیتورینگ تاخیر/موفقیت پاسخها را داشته باشید تا مشکلات سریع پیدا شوند.
- آزمون و خطا: هر سایت واکنش متفاوتی دارد؛ پلن پروکسی، نرخ و ترکیب هددرها را با آزمون A/B تنظیم کنید.
جمعبندی
مدیریت User-Agent و پروکسیها جزء اصولیترین نیازها برای اسکریپینگ در مقیاس بزرگ هستند. استفاده از میدلورهای آماده برای چرخش User-Agent، ارسال درخواستها از طریق یک سرویس پروکسی قابلاعتماد، ذخیره امن کلیدها و تنظیم مناسب concurrency و retry، ترکیبی است که شانس موفقیت شما را افزایش میدهد. در نهایت همیشه رفتار سایت، محدودیتها و جنبههای قانونی را در نظر داشته باشید و قبل از اجرای در مقیاس بزرگ، تست و مانیتورینگ کامل انجام دهید.





