Назад к блогу

Настройка прокси в Scrapy фреймворке: полное руководство с примерами кода

Полное руководство по интеграции прокси в Scrapy: от базовой настройки до продвинутых методов ротации IP-адресов с примерами рабочего кода.

📅14 февраля 2026 г.

Scrapy — один из самых мощных Python-фреймворков для веб-скрейпинга, но без правильной настройки прокси ваши парсеры будут получать блокировки уже через несколько минут работы. В этом руководстве я покажу все способы интеграции прокси в Scrapy: от простейшей настройки до продвинутых методов ротации IP-адресов с автоматической обработкой ошибок.

Материал основан на реальном опыте парсинга крупных e-commerce площадок и защищённых сайтов. Вы получите готовые примеры кода, которые можно сразу использовать в своих проектах.

Почему Scrapy без прокси получает блокировки

Современные сайты используют многоуровневую защиту от парсинга. Даже если вы настроили User-Agent и задержки между запросами, ваш IP-адрес выдаёт автоматизацию по нескольким признакам:

  • Частота запросов: один IP делает 100+ запросов в минуту — явный признак бота
  • Паттерны поведения: последовательный обход страниц без случайных переходов
  • Отсутствие JavaScript: Scrapy не выполняет JS, что легко детектится
  • Геолокация: доступ из дата-центра вместо домашней сети

Результат — ban по IP на несколько часов или дней. Особенно агрессивную защиту используют маркетплейсы (Amazon, Wildberries, Ozon), социальные сети и сайты с Cloudflare. Прокси решают эту проблему, распределяя запросы между множеством IP-адресов.

Важно: Даже с прокси нужно соблюдать rate limits. Рекомендуемая скорость: 1-3 запроса в секунду на один IP. Для высокоскоростного парсинга используйте пул из 50+ прокси с ротацией.

Базовая настройка прокси в Scrapy

Самый простой способ — указать прокси прямо в настройках паука. Этот метод подходит для тестирования или парсинга небольших объёмов данных с одним прокси-сервером.

Метод 1: Через meta в Request

import scrapy

class MySpider(scrapy.Spider):
    name = 'example'
    start_urls = ['https://example.com']
    
    def start_requests(self):
        proxy = 'http://username:password@proxy.example.com:8080'
        
        for url in self.start_urls:
            yield scrapy.Request(
                url=url,
                callback=self.parse,
                meta={'proxy': proxy}
            )
    
    def parse(self, response):
        # Ваша логика парсинга
        self.log(f'Scraped {response.url} via {response.meta["proxy"]}')

Формат прокси зависит от протокола и метода аутентификации:

  • http://proxy.example.com:8080 — без аутентификации
  • http://user:pass@proxy.example.com:8080 — с логином/паролем
  • socks5://user:pass@proxy.example.com:1080 — SOCKS5 прокси

Метод 2: Глобальная настройка в settings.py

# settings.py

# HTTP прокси для всех запросов
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'

# Настройка через переменные окружения
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'

Этот метод удобен для быстрого тестирования, но не подходит для продакшена: нет ротации IP, при падении прокси весь парсер останавливается, невозможно использовать разные прокси для разных сайтов.

Создание кастомного Proxy Middleware

Для продакшен-парсинга нужен собственный middleware, который будет управлять пулом прокси, обрабатывать ошибки и автоматически ротировать IP. Вот базовая реализация:

# middlewares.py

import random
from scrapy import signals
from scrapy.exceptions import NotConfigured

class RandomProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
    
    @classmethod
    def from_crawler(cls, crawler):
        # Загружаем список прокси из настроек
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        
        if not proxy_list:
            raise NotConfigured('PROXY_LIST not configured')
        
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # Выбираем случайный прокси из пула
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        
        spider.logger.info(f'Using proxy: {proxy}')
    
    def process_exception(self, request, exception, spider):
        # При ошибке пробуем другой прокси
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        
        spider.logger.warning(
            f'Proxy error, switching to: {proxy}'
        )
        
        return request

Теперь настраиваем использование middleware в settings.py:

# settings.py

# Список прокси (можно загружать из файла или API)
PROXY_LIST = [
    'http://user1:pass1@proxy1.example.com:8080',
    'http://user2:pass2@proxy2.example.com:8080',
    'http://user3:pass3@proxy3.example.com:8080',
    # ... добавьте 50+ прокси для эффективной ротации
]

# Подключаем middleware
DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RandomProxyMiddleware': 350,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}

# Повторные попытки при ошибках
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]

Ротация прокси: три рабочих метода

Случайный выбор прокси (как в примере выше) — самый простой, но не самый эффективный метод. Рассмотрим три стратегии ротации для разных сценариев.

Метод 1: Round-robin (последовательная ротация)

Прокси выбираются по кругу. Подходит для равномерного распределения нагрузки:

class RoundRobinProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        self.current_index = 0
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # Берём следующий прокси по кругу
        proxy = self.proxy_list[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxy_list)
        
        request.meta['proxy'] = proxy

Метод 2: Умная ротация с blacklist

Отслеживаем проблемные прокси и временно исключаем их из ротации:

import time
from collections import defaultdict

class SmartProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        self.proxy_errors = defaultdict(int)
        self.blacklist = set()
        self.blacklist_timeout = 300  # 5 минут
        self.blacklist_time = {}
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        return cls(proxy_list)
    
    def get_working_proxies(self):
        # Убираем из blacklist прокси, у которых истёк таймаут
        current_time = time.time()
        expired = [
            proxy for proxy, ban_time in self.blacklist_time.items()
            if current_time - ban_time > self.blacklist_timeout
        ]
        
        for proxy in expired:
            self.blacklist.discard(proxy)
            self.proxy_errors[proxy] = 0
        
        # Возвращаем рабочие прокси
        return [p for p in self.proxy_list if p not in self.blacklist]
    
    def process_request(self, request, spider):
        working_proxies = self.get_working_proxies()
        
        if not working_proxies:
            spider.logger.error('All proxies are blacklisted!')
            return
        
        proxy = random.choice(working_proxies)
        request.meta['proxy'] = proxy
    
    def process_response(self, request, response, spider):
        # Если получили блокировку — добавляем в blacklist
        if response.status in [403, 429, 503]:
            proxy = request.meta.get('proxy')
            self.proxy_errors[proxy] += 1
            
            if self.proxy_errors[proxy] >= 3:
                self.blacklist.add(proxy)
                self.blacklist_time[proxy] = time.time()
                spider.logger.warning(
                    f'Proxy {proxy} blacklisted for {self.blacklist_timeout}s'
                )
        
        return response

Метод 3: Ротация через API провайдера

Многие провайдеры прокси (включая резидентные прокси) предоставляют rotating endpoint — один URL, который автоматически меняет IP при каждом запросе:

# settings.py

# Единый endpoint с автоматической ротацией
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'

# Простой middleware
class RotatingProxyMiddleware:
    def __init__(self, proxy):
        self.proxy = proxy
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy = crawler.settings.get('ROTATING_PROXY')
        return cls(proxy)
    
    def process_request(self, request, spider):
        # Один URL, но каждый запрос идёт с нового IP
        request.meta['proxy'] = self.proxy

Это самый удобный метод для продакшена: не нужно управлять пулом прокси, провайдер сам следит за качеством IP и заменяет проблемные. Особенно эффективно работает с резидентными прокси, где пул IP может достигать миллионов адресов.

Аутентификация: логин/пароль vs IP whitelist

Прокси-провайдеры предлагают два метода аутентификации. Выбор влияет на скорость подключения и удобство настройки.

User:Pass аутентификация

Логин и пароль передаются в URL прокси. Scrapy автоматически конвертирует их в HTTP заголовок Proxy-Authorization:

proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy

# Scrapy автоматически добавит заголовок:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Плюсы: работает с любого IP, легко менять прокси в коде.
Минусы: небольшой overhead на каждый запрос (~50-100ms), учётные данные в открытом виде в коде.

IP Whitelist аутентификация

Вы добавляете IP вашего сервера в whitelist у провайдера, аутентификация не требуется:

proxy = 'http://proxy.example.com:8080'  # без логина/пароля
request.meta['proxy'] = proxy

Плюсы: быстрее на 50-100ms, безопаснее (нет credentials в коде).
Минусы: работает только с определённых IP, нужно обновлять whitelist при смене сервера.

Рекомендация для продакшена:

Используйте IP whitelist для парсинга с выделенных серверов (AWS, Google Cloud, Hetzner). Для разработки и тестирования с локальной машины — user:pass аутентификацию.

Обработка ошибок и автоматическая смена IP

Даже с качественными прокси будут ошибки: таймауты, отказы в соединении, блокировки. Правильная обработка ошибок критична для стабильной работы парсера.

Обработка HTTP статусов

class ProxyMiddleware:
    def process_response(self, request, response, spider):
        # Коды, при которых нужно сменить прокси и повторить
        ban_codes = [403, 407, 429, 503]
        
        if response.status in ban_codes:
            proxy = request.meta.get('proxy')
            spider.logger.warning(
                f'Got {response.status} from {proxy}, retrying...'
            )
            
            # Помечаем для retry с новым прокси
            request.meta['dont_retry'] = False
            request.meta['proxy'] = self.get_new_proxy()
            
            return request
        
        return response

Обработка исключений сети

from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest

class ProxyMiddleware:
    def process_exception(self, request, exception, spider):
        # Ошибки подключения к прокси
        proxy_errors = (
            TimeoutError,
            ConnectionRefusedError,
            ConnectionLost,
        )
        
        if isinstance(exception, proxy_errors):
            proxy = request.meta.get('proxy')
            spider.logger.error(
                f'Proxy {proxy} connection failed: {exception}'
            )
            
            # Меняем прокси и пробуем снова
            request.meta['proxy'] = self.get_new_proxy()
            return request
        
        # Для остальных ошибок используем стандартную обработку
        return None

Детектирование блокировок по контенту

Некоторые сайты возвращают HTTP 200, но показывают капчу или страницу блокировки:

class ProxyMiddleware:
    def process_response(self, request, response, spider):
        # Признаки блокировки в контенте
        ban_indicators = [
            'captcha',
            'access denied',
            'blocked',
            'unusual traffic',
            'robot check',
        ]
        
        body_text = response.text.lower()
        
        if any(indicator in body_text for indicator in ban_indicators):
            spider.logger.warning(
                f'Ban page detected from {request.meta.get("proxy")}'
            )
            
            # Меняем прокси и повторяем
            request.meta['proxy'] = self.get_new_proxy()
            return request
        
        return response

Какой тип прокси выбрать для Scrapy

Выбор типа прокси зависит от целевого сайта, бюджета и требуемой скорости парсинга. Вот сравнение основных вариантов:

Тип прокси Скорость Стоимость Когда использовать
Дата-центр прокси Высокая (50-200ms) Низкая ($1-3/IP) Простые сайты без защиты, API, внутренние инструменты
Резидентные прокси Средняя (300-800ms) Средняя ($5-15/GB) E-commerce, соцсети, сайты с Cloudflare, геотаргетинг
Мобильные прокси Низкая (500-1500ms) Высокая ($50-150/IP) Мобильные приложения, Instagram, TikTok, максимальная защита

Рекомендации по выбору

Для парсинга маркетплейсов (Amazon, Wildberries, Ozon, AliExpress) — только резидентные прокси. Эти сайты агрессивно банят дата-центры. Нужна ротация и геотаргетинг (например, российские IP для Wildberries).

Для парсинга новостных сайтов, блогов, форумов — подойдут дата-центр прокси. Защита минимальная, важна скорость и низкая стоимость трафика.

Для парсинга сайтов с Cloudflare — резидентные прокси обязательны. Дата-центры Cloudflare детектит почти мгновенно. Добавьте к Scrapy библиотеку cloudscraper для обхода JS-челленджей.

Для парсинга Google Search, SEO-инструментов — резидентные прокси с геотаргетингом. Google показывает разные результаты для разных стран и городов.

Совет: Начните с пула 10 резидентных прокси для тестирования. Если получаете блокировки — увеличивайте пул до 50-100 IP. Для высокоскоростного парсинга (1000+ запросов/минуту) используйте rotating endpoint с пулом 10,000+ IP.

Продвинутые техники: сессии и sticky IP

При парсинге некоторых сайтов нужно сохранять один IP на протяжении всей сессии (авторизация, корзина покупок, многошаговые формы). Вот как реализовать sticky sessions в Scrapy.

Sticky IP для одного домена

from urllib.parse import urlparse

class StickyProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list
        # Словарь: домен -> прокси
        self.domain_proxy_map = {}
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        return cls(proxy_list)
    
    def process_request(self, request, spider):
        # Извлекаем домен из URL
        domain = urlparse(request.url).netloc
        
        # Если для этого домена уже есть прокси — используем его
        if domain in self.domain_proxy_map:
            proxy = self.domain_proxy_map[domain]
        else:
            # Иначе выбираем новый и запоминаем
            proxy = random.choice(self.proxy_list)
            self.domain_proxy_map[domain] = proxy
            spider.logger.info(f'Assigned {proxy} to {domain}')
        
        request.meta['proxy'] = proxy

Sticky IP с таймаутом сессии

Более продвинутый вариант: прокси привязывается к домену на определённое время (например, 10 минут), затем меняется:

import time
from urllib.parse import urlparse

class SessionProxyMiddleware:
    def __init__(self, proxy_list, session_timeout=600):
        self.proxy_list = proxy_list
        self.session_timeout = session_timeout  # 10 минут
        # Словарь: домен -> (прокси, время создания)
        self.sessions = {}
    
    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.getlist('PROXY_LIST')
        timeout = crawler.settings.getint('PROXY_SESSION_TIMEOUT', 600)
        return cls(proxy_list, timeout)
    
    def get_proxy_for_domain(self, domain):
        current_time = time.time()
        
        # Проверяем, есть ли активная сессия
        if domain in self.sessions:
            proxy, created_at = self.sessions[domain]
            
            # Если сессия не истекла — используем тот же прокси
            if current_time - created_at < self.session_timeout:
                return proxy
        
        # Создаём новую сессию с новым прокси
        new_proxy = random.choice(self.proxy_list)
        self.sessions[domain] = (new_proxy, current_time)
        
        return new_proxy
    
    def process_request(self, request, spider):
        domain = urlparse(request.url).netloc
        proxy = self.get_proxy_for_domain(domain)
        request.meta['proxy'] = proxy

Интеграция с Cookie Middleware

Для полноценных сессий нужно синхронизировать прокси и cookies. Scrapy хранит cookies отдельно для каждого домена, но при смене прокси нужно очищать cookies:

# settings.py

# Включаем cookie middleware
COOKIES_ENABLED = True
COOKIES_DEBUG = False

# Middleware для синхронизации прокси и cookies
class ProxyCookieMiddleware:
    def process_request(self, request, spider):
        # Получаем текущий прокси
        current_proxy = request.meta.get('proxy')
        
        # Если прокси сменился — очищаем cookies
        previous_proxy = request.meta.get('previous_proxy')
        
        if previous_proxy and previous_proxy != current_proxy:
            # Очищаем cookies для этого домена
            jar = spider.crawler.engine.downloader.middleware.middlewares[0].jars
            domain = urlparse(request.url).netloc
            
            if domain in jar:
                jar[domain].clear()
                spider.logger.info(f'Cleared cookies for {domain}')
        
        request.meta['previous_proxy'] = current_proxy

Заключение

Правильная настройка прокси в Scrapy — это основа стабильного парсинга без блокировок. Мы рассмотрели все ключевые аспекты: от базовой интеграции до продвинутых техник ротации и управления сессиями.

Главные выводы:

  • Для продакшена используйте кастомный middleware с умной ротацией и blacklist проблемных IP
  • Обрабатывайте все типы ошибок: HTTP статусы, исключения сети, блокировки по контенту
  • Выбирайте тип прокси под задачу: дата-центры для простых сайтов, резидентные для защищённых
  • Для сайтов с авторизацией используйте sticky sessions с привязкой прокси к домену
  • Начинайте с пула 10-50 прокси, масштабируйте при росте нагрузки

Если вы планируете парсить защищённые сайты (маркетплейсы, соцсети, сайты с Cloudflare), рекомендую использовать резидентные прокси — они обеспечивают максимальную анонимность и минимальный риск блокировок. Для высокоскоростного парсинга выбирайте провайдеров с rotating endpoint и пулом от 10,000 IP-адресов.

Все примеры кода из этой статьи протестированы на Scrapy 2.x и готовы к использованию в продакшене. Адаптируйте их под свои задачи и масштабируйте по мере роста проекта.