

در این مقاله تکنیکهای عملی برای مدیریت User Agent و پروکسی در پروژههای اسکریپینگ با Scrapy را یاد میگیریم. هدف این راهنما این است که پس از خواندن آن بتوانید: تشخیص دهید چرا سایتها درخواستها را بلاک میکنند، چگونه User-Agentها را چرخانده و در Scrapy تنظیم کنید، و چگونه درخواستها را از طریق پروکسیها مسیردهی کنید تا از محدودیتها و تشخیصهای ضدربات عبور کنید.
در مقیاس بزرگ، مشکل اصلی نه گرفتن یک صفحه بلکه دریافت پایدار پاسخهای HTML است. سرورها با استفاده از ترکیبی از بررسی IP، User-Agent، رفتار زمانی (rate) و الگوهای ترافیک نامتعارف، ترافیک را تحلیل و ترافیکی را که به نظر اسکریپر میآید مسدود میکنند. در نتیجه لازم است هم سرنام (headers) و هم آدرسهای IP را مدیریت کنیم.
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 استفاده از یک 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,
}
توضیح خطوط فوق:
نکات عملی و محدودیتها:
حتی با User-Agentهای تصادفی، سایتها آدرسهای IP را نیز رصد میکنند. وقتی تعداد زیادی درخواست از یک IP مشاهده شود، throttle یا ban رخ میدهد. پروکسیها راه حلی برای تغییر IP منبع درخواستها هستند؛ با استفاده از پروکسی هر درخواست از یک IP متفاوت یا از poolای از IPها ارسال میشود.
انواع پروکسیها:
مزایا/معایب:
یک الگوی متداول، استفاده از سرویس پروکسی بهعنوان یک 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)
نکات توضیحی برای کد بالا:
وقتی تعداد زیادی درخواست میفرستید باید 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,
}
نکات مهم:
مدیریت User-Agent و پروکسیها جزء اصولیترین نیازها برای اسکریپینگ در مقیاس بزرگ هستند. استفاده از میدلورهای آماده برای چرخش User-Agent، ارسال درخواستها از طریق یک سرویس پروکسی قابلاعتماد، ذخیره امن کلیدها و تنظیم مناسب concurrency و retry، ترکیبی است که شانس موفقیت شما را افزایش میدهد. در نهایت همیشه رفتار سایت، محدودیتها و جنبههای قانونی را در نظر داشته باشید و قبل از اجرای در مقیاس بزرگ، تست و مانیتورینگ کامل انجام دهید.


