Scrapy в 2026 году всё ещё рабочая лошадка для боевых краулеров — и всё ещё фреймворк, в котором настройка прокси сбивает людей с толку сильнее всего, потому что прокси можно воткнуть в четыре разных места, и три из них для большинства проектов неправильные. Этот гайд даёт правильный вариант: небольшой кастомный middleware с маршрутизацией на уровне запроса, sticky-сессиями, детекцией банов и вменяемой логикой повторов.
Если вы на обычных requests/httpx/aiohttp, смотрите Как ротировать прокси в Python. Эта статья — именно про Scrapy.
Для ротируемого резидентного шлюза минимально рабочая настройка — одна строка на запрос, без всякого middleware:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:[email protected]:913"},
)
Встроенный в Scrapy HttpProxyMiddleware читает request.meta["proxy"] и берёт аутентификацию из URL. Шлюз сам ротирует исходящий IP за вас. Если больше ничего не нужно — на этом можно остановиться. Остальная часть гайда — для случаев, когда вам нужен контроль: sticky-сессии, маршрутизация по стране, ротация с учётом банов и тюнинг конкурентности.
Положите это в middlewares.py. Он назначает sticky-сессию на каждый домен, ротирует при банах и тегирует каждый запрос, чтобы вы могли отлаживать, какая сессия что забрала:
import random
import string
GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME" # в реальных проектах вынесите в settings.py / env
PASSWORD = "PASSWORD"
def _new_session(n=8):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
class JibaoProxyMiddleware:
"""Sticky-сессия на домен; ротация сессии при бане."""
def __init__(self):
self.sessions = {} # домен -> id сессии
def _proxy_url(self, session_id):
user = f"{USERNAME}-session-{session_id}"
return f"http://{user}:{PASSWORD}@{GATEWAY}"
def process_request(self, request, spider):
domain = request.url.split("/")[2]
session = self.sessions.setdefault(domain, _new_session())
request.meta["proxy"] = self._proxy_url(session)
request.meta["proxy_session"] = session
def rotate(self, domain):
"""Вызывать, когда сессия сожжена."""
self.sessions[domain] = _new_session()
И парный downloader-middleware, который детектит баны и повторяет запрос на свежей сессии:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")
class BanAwareRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
banned = (
response.status in BAN_CODES
or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
)
if banned:
domain = request.url.split("/")[2]
proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
for mw in proxy_mw:
if hasattr(mw, "rotate"):
mw.rotate(domain) # сжечь сессию
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return super().process_response(request, response, spider)
Подключите оба в settings.py:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # заменяем штатный retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
Приоритет важен: прокси-middleware должен отрабатывать раньше scrapy'шного HttpProxyMiddleware (750), так что любое значение ниже 750 годится; 350 держит его рано и предсказуемо.
| Тип краулинга | Режим | Реализация |
|---|---|---|
| Сбор страниц без состояния | Ротация | Голый username, шлюз ротирует на каждый запрос |
| Логин + краулинг под авторизацией | Sticky на аккаунт | -session-{account_id}, никогда не ротировать в середине логина |
| Листинги с тяжёлой пагинацией | Sticky на домен, ротация при бане | Middleware выше |
| Гео-специфичные цены | Ротация + привязка к стране | Параметры вида USERNAME-country-de |
Подробнее об этом компромиссе: Sticky или ротируемые прокси-сессии.
Дефолты Scrapy заточены под вежливый краулинг с одного IP. За ротируемым пулом можно давить куда сильнее — но лимиты на домен всё равно важны, потому что цель видит совокупное поведение:
# settings.py - вменяемая отправная точка за резидентным пулом
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # то, что ощущает цель
DOWNLOAD_DELAY = 0.25 # джиттер применяется на каждый слот
RANDOMIZE_DOWNLOAD_DELAY = True # 0.5x-1.5x от задержки
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True
Повышайте CONCURRENT_REQUESTS_PER_DOMAIN только после того, как понаблюдаете за долей 403 на текущем уровне на протяжении пары тысяч запросов. Прыжок 8 → 32 по логике «прокси всё равно ротируются» — именно так люди сжигают гигабайты на повторах.
IMAGES_STORE на загрузку через прокси дата-центра или прямое соединение, если CDN всё равно — медиа съедает большую часть гигабайт и редко защищено.HTTPCACHE_ENABLED = True во время доработки парсеров проигрывает ответы с диска вместо повторной загрузки через пул. Эта одна настройка обычно вдвое сокращает счёт за трафик проекта.COMPRESSION_ENABLED = True (по умолчанию) — gzip-сжатый HTML в 5–10 раз меньше на проводе.407 Proxy Authentication RequiredУчётные данные не дошли до прокси. Положите их в URL (http://user:pass@host:port) внутри meta["proxy"] — Scrapy распарсит и сам выставит Proxy-Authorization. Если выставить заголовок вручную и при этом использовать креды в URL, начинается двойная аутентификация и странности; выберите что-то одно.
TunnelError: Could not open CONNECT tunnelПочти всегда опечатка в хосте/порте либо HTTPS-цель через эндпоинт, который не разрешает CONNECT на этом порту. Сначала проверьте через curl -x вне Scrapy.
Ваша sticky-сессия пережила свою полезность, либо ваш темп на домен слишком горячий. Первый случай решает ban-aware middleware выше; для второго понизьте CONCURRENT_REQUESTS_PER_DOMAIN. Если цель проверяет JA4, то выдавать вас может сам TLS-стек Scrapy — см. разбор JA3/JA4 о том, почему это не лечится никаким прокси.
meta["proxy"] с URL шлюза — и есть вся настройка.Ротируемые и sticky резидентные сессии на одном шлюзе. $5 бесплатного баланса, чтобы начать краулить.
Начать бесплатноНовым пользователям — 5U при регистрации, бонус к первому пополнению. Акция ограничена по времени.