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 и готовы к использованию в продакшене. Адаптируйте их под свои задачи и масштабируйте по мере роста проекта.