# Прокси в Scrapy 2.19: авторизация, ротация, ошибки

Source: https://ipvolt.com/ru/guides/scrapy-proxy-setup
Markdown: https://ipvolt.com/ru/guides/scrapy-proxy-setup.md
Language: ru

[ipvolt — главная](https://ipvolt.com/ru.md) / [Руководства](https://ipvolt.com/ru/guides.md) / Прокси в Scrapy 2.19: авторизация, ротация, ошибки

Интеграция
Опубликовано: 2026-10-07
8 мин чтения
Автор: ipvolt

Настройка прокси в Scrapy 2.19: учётные данные в meta["proxy"], ротация по запросам, HTTP_PROXY и разбор TunnelError 407, пропущенного ответа 407 и ошибки socks5.

Укажите учётные данные в URL прокси и передайте его в `meta["proxy"]`:

```python
import scrapy

PROXY = "http://user:pass@proxy.example.com:8080"


class ExampleSpider(scrapy.Spider):
    name = "example"

    async def start(self):
        yield scrapy.Request("https://example.com/", meta={"proxy": PROXY})

    def parse(self, response):
        self.logger.info("%s via %s", response.status, response.meta["proxy"])
```

`HttpProxyMiddleware` включён по умолчанию (`HTTPPROXY_ENABLED = True`). Он переносит имя пользователя и пароль из URL в заголовок `Proxy-Authorization` ([документация](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.httpproxy)), поэтому в `response.meta["proxy"]` остаётся URL без учётных данных.

В нашей лаборатории так прошли авторизацию запросы к `https://` и `http://` через локальный прокси, который требует Basic-авторизацию. Всё, что описано ниже, записано 7 октября 2026 года на Scrapy 2.19.0, Twisted 26.4.0 и Python 3.14.4.

Три детали, прежде чем перейти к ошибкам:

- **Кодируйте пароль процентами.** `p@ss:w/rd` сработал в виде `p%40ss%3Aw%2Frd`. Без кодирования запрос завершился ошибкой `ValueError: Port could not be cast to integer value as 'w'`.
- **Запросы, созданные в callback, не наследуют прокси.** Запрос, созданный через `response.follow()`, ушёл напрямую. При редиректе прокси сохранился. Задавайте `meta["proxy"]` в каждом запросе или в downloader middleware, как показано ниже.
- **Пишите `async def start()`.** Паук, в котором был только `start_requests()`, открылся, не отправил ни одного запроса и закрылся с `finish_reason: finished` без предупреждений. В Scrapy 2.16.0 метод удалён: он «is removed and no longer called» ([примечания к версии](https://docs.scrapy.org/en/latest/news.html#release-2-16-0)). Его заменяет [`Spider.start()`](https://docs.scrapy.org/en/latest/topics/spiders.html#scrapy.Spider.start). Миграция, включая spider middleware, описана в руководстве [Scrapy: start_requests() не вызывается](/ru/guides/scrapy-start-requests-not-called).

## Ротация прокси: задавайте meta["proxy"] для каждого запроса

Ротация — это downloader middleware, который выполняется раньше `HttpProxyMiddleware` (порядок 750) и задаёт URL прокси вместе с учётными данными:

```python
from itertools import cycle

PROXIES = cycle([
    "http://user:pass@proxy-a.example.com:8080",
    "http://user:pass@proxy-b.example.com:8080",
])


class RotateProxyMiddleware:
    def process_request(self, request):
        request.meta["proxy"] = next(PROXIES)
```

```python
DOWNLOADER_MIDDLEWARES = {"myproject.middlewares.RotateProxyMiddleware": 740}
```

В лаборатории запрос с редиректом прошёл через этот middleware дважды, попал на два прокси и авторизовался на обоих.

Не работает вариант, в котором URL задан без учётных данных, а заголовок выставляется вручную:

```python
request.meta["proxy"] = next(PROXIES)  # "http://proxy-a.example.com:8080"
request.headers["Proxy-Authorization"] = "Basic dXNlcjpwYXNz"
```

Первый запрос пришёл на прокси A с заголовком. Целевой сервер ответил 301, и запрос после редиректа снова прошёл через все middleware. Ротация выбрала прокси B и снова выставила заголовок, но прокси B получил запрос без `Proxy-Authorization` и ответил 407. Повторённый запрос к `https://` потерял заголовок точно так же.

`HttpProxyMiddleware` запоминает, с каким URL прокси был отправлен выставленный вручную заголовок. Если при следующем проходе того же запроса URL прокси другой и без учётных данных, заголовок удаляется. Причина описана в [примечаниях к версии 2.6.2](https://docs.scrapy.org/en/latest/news.html#release-2-6-2): middleware для ротации меняли прокси и оставляли старый заголовок, так что учётные данные одного прокси уходили другому. Начиная с [2.7.1](https://docs.scrapy.org/en/latest/news.html#release-2-7-1) выставленный вручную заголовок сохраняется только «for as long as that proxy URL remains the same». Учётные данные из URL всегда его заменяют.

Ещё два результата того же кода:

- На первом проходе заголовок ещё не привязан к прокси. Middleware, который сменил `meta["proxy"]` с A на B до запуска `HttpProxyMiddleware`, отправил выставленный вручную заголовок на прокси B.
- Когда прокси не было вовсе, заголовок был удалён до того, как запрос дошёл до целевого сервера.

## Переменные окружения: HTTP_PROXY действует по умолчанию

Scrapy читает переменные прокси при запуске обхода, и включать это настройкой не нужно. С `HTTP_PROXY=http://user:pass@127.0.0.1:8888` и без `meta["proxy"]` запрос в лаборатории прошёл через прокси и авторизовался.

- `HTTP_PROXY` и `http_proxy` в нижнем регистре применялись к URL `http://`, а `HTTPS_PROXY` — к URL `https://`. Когда была задана только `HTTP_PROXY`, запрос к `https://` ушёл напрямую. `ALL_PROXY` игнорировалась.
- `NO_PROXY=127.0.0.1` отправила запрос напрямую. `meta["proxy"]` имел приоритет над переменными и не учитывал `NO_PROXY`, как и сказано в документации.
- `meta={"proxy": None}` отправил один запрос напрямую при заданной переменной.
- `HTTPPROXY_ENABLED = False` отключил чтение переменных. Вместе с этим отключилась и обработка учётных данных: `meta["proxy"] = "http://user:pass@127.0.0.1:8888"` после этого вызвал `ValueError: invalid hostname: user:pass@127.0.0.1` для URL `http://` и `TunnelError` с кодом 407 для `https://`.

Чтобы игнорировать окружение и сохранить работу `meta["proxy"]`, замените middleware подклассом, который отбрасывает прочитанное:

```python
from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware


class ExplicitProxyMiddleware(HttpProxyMiddleware):
    def __init__(self, auth_encoding="latin-1"):
        super().__init__(auth_encoding)
        self.proxies = {}
```

```python
DOWNLOADER_MIDDLEWARES = {
    "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": None,
    "myproject.middlewares.ExplicitProxyMiddleware": 750,
}
```

`proxies` — недокументированный атрибут, поэтому после обновления проверьте это ещё раз. aiohttp ведёт себя наоборот: он игнорирует переменные, пока вы не передадите `trust_env=True` ([Прокси в aiohttp 3.14](/ru/guides/aiohttp-proxy-setup)). Руководство [Переменные окружения прокси](/ru/guides/proxy-environment-variables) сравнивает другие клиенты.

## TunnelError: Could not open CONNECT tunnel with proxy

```text
scrapy.core.downloader.handlers.http11.TunnelError: Could not open CONNECT tunnel with proxy 127.0.0.1:8888 [{'status': 407, 'reason': b'Proxy Authentication Required'}]
```

Прокси отклонил запрос `CONNECT` для URL `https://`. Сообщение было одинаковым и без учётных данных, и с неверным паролем, и самих учётных данных в нём нет, поэтому по нему нельзя понять, какой из двух случаев у вас. Проверьте URL в `meta["proxy"]` у запроса, который завершился ошибкой, а затем пройдите порядок проверок из руководства [Ошибка прокси 407](/ru/guides/fix-proxy-error-407).

- `TunnelError` входит в `RETRY_EXCEPTIONS`. С настройками по умолчанию прокси в лаборатории получил отклонённый `CONNECT` три раза. В журнале были `Retrying <GET https://127.0.0.1:8443/> (failed 1 times)`, затем `(failed 2 times)`, затем `Gave up retrying`. С `"dont_retry": True` в `meta` прокси получил один запрос.
- `handle_httpstatus_list` здесь ничего не меняет. Ответа нет, есть только исключение, поэтому обрабатывайте его в `errback`.

## Ignoring response 407: HTTP status code is not handled or not allowed

```text
Ignoring response <407 http://127.0.0.1:8080/>: HTTP status code is not handled or not allowed
```

Для URL `http://` ответ 407 от прокси — обычный ответ, и `HttpErrorMiddleware` его отбрасывает. Callback не был вызван ни разу, единственным признаком была эта строка уровня INFO, а обход закрылся с `finish_reason: finished`. Если задан `errback`, строка в журнал не пишется, а errback получает `scrapy.spidermiddlewares.httperror.HttpError: Ignoring non-200 response`. Повторять запрос Scrapy не стал.

Чтобы получить ответ в callback, разрешите этот статус для запроса:

```python
yield scrapy.Request(url, meta={"proxy": PROXY, "handle_httpstatus_list": [407]})
```

Атрибут паука `handle_httpstatus_list = [407]` и настройка `HTTPERROR_ALLOWED_CODES = [407]` дали тот же результат ([HttpErrorMiddleware](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#module-scrapy.spidermiddlewares.httperror)). Callback после этого получил статус 407 с `Proxy-Authenticate: Basic realm="lab"`. Без них отброшенные ответы учитываются в статистике обхода под ключом `httperror/response_ignored_status_count/407`.

## UnsupportedURLSchemeError: Unsupported scheme: b'socks5'

```text
scrapy.exceptions.UnsupportedURLSchemeError: Unsupported scheme: b'socks5'
```

Стандартный обработчик загрузки Scrapy не поддерживает SOCKS. С `meta["proxy"] = "socks5://127.0.0.1:1080"` запрос к `http://` вызвал эту ошибку один раз, без повторов. `socks5h://` вызвал `Unsupported scheme: b'socks5h'`, и URL SOCKS в `HTTP_PROXY` вызвал ту же ошибку.

Запрос к `https://` её не вызвал. Scrapy отправил `CONNECT 127.0.0.1:8443` обычным HTTP на порт SOCKS, SOCKS-сервер лаборатории закрыл соединение, и Scrapy трижды ждал истечения таймаута загрузки, прежде чем сообщил `DownloadTimeoutError: Getting https://127.0.0.1:8443/ took longer than 15.0 seconds.` В лаборатории `DOWNLOAD_TIMEOUT` был равен 15. По умолчанию он равен 180.

Выходов два:

- Используйте HTTP-прокси вашего провайдера, если он есть. Тогда применимо всё сказанное выше.
- Переключитесь на `HttpxDownloadHandler`. Он входит в Scrapy, а [документация](https://docs.scrapy.org/en/latest/topics/download-handlers.html#httpx-handler) указывает для него поддержку SOCKS5 и помечает его как экспериментальный и пока не рекомендованный для продакшена.

```sh
python -m pip install "scrapy[httpx]"
```

```python
DOWNLOAD_HANDLERS = {
    "http": "scrapy.core.downloader.handlers._httpx.HttpxDownloadHandler",
    "https": "scrapy.core.downloader.handlers._httpx.HttpxDownloadHandler",
}
```

С этим обработчиком `socks5://` вернул 200 для обеих схем. Для `http://localhost:8080/` SOCKS-сервер записал доменное имя, то есть обработчик передаёт прокси имя хоста. `socks5h://` тоже сработал. Статья [socks5 и socks5h](/ru/blog/socks5-vs-socks5h) показывает, что делают с этими двумя схемами другие клиенты. Ошибки у этого обработчика выглядят иначе: отклонённый `CONNECT` проявился как `scrapy.exceptions.DownloadConnectionRefusedError: 407 Proxy Authentication Required`.

## Повторы: RetryMiddleware повторяет запросы POST

`RetryMiddleware` повторил `POST` так же, как повторяет `GET`. В лаборатории `POST` прошёл через прокси к серверу, который прочитал запрос и закрыл соединение, не ответив. Scrapy записал `Retrying <POST http://127.0.0.1:8081/jobs> (failed 1 times)` и отправил запрос снова. Сервер получил одно и то же тело дважды, а callback увидел один ответ 201.

Для запроса, который небезопасно повторять, задайте `"dont_retry": True` в `meta` ([RetryMiddleware](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.retry)). Тогда сервер получил один `POST`, а errback получил `scrapy.exceptions.DownloadFailedError`. [Повторы через прокси: один потерянный ответ, две созданные задачи](/ru/blog/proxy-retries-duplicate-jobs) показывает, что меняет ключ идемпотентности.

Каждый повтор — это ещё один запрос через прокси. В статье [Сколько ГБ прокси вам нужно](/ru/blog/how-many-gb-of-proxies-do-i-need) измерено, сколько трафика уходит на загрузку одного только HTML. Для помощи с диагностикой внутри агента для написания кода [Proxy Toolkit MCP](/mcp) принимает очищенные описания ошибок. Не передавайте в его аргументах учётные данные.

## Как это проверялось

На Ubuntu 26.04.1 с Python 3.14.4, Scrapy 2.19.0, Twisted 26.4.0 и httpx2 2.13.1, всё на `127.0.0.1`: `python -m http.server` в роли HTTP- и HTTPS-целей, три прокси, которые требуют Basic-авторизацию и записывают каждый запрос с именами его заголовков, SOCKS5-сервер, который записывает каждое место назначения, и цель, которая теряет ответ на первый `POST`. Каждый из 62 случаев запускал `scrapy runspider` в новом интерпретаторе с чистым окружением. Аккаунт прокси не использовался. На Python 3.15.0 `pip install scrapy` без компилятора C завершается ошибкой, потому что у brotli на 14 октября 2026 года не было колеса cp315; проверенные решения — в статье [Python 3.15 для веб-скрапинга](/ru/blog/python-3-15-web-scraping).

[Архив лаборатории](https://ipvolt.com/downloads/scrapy-proxy-setup/scrapy-proxy-lab.zip) содержит [скрипт запуска](https://ipvolt.com/downloads/scrapy-proxy-setup/run_lab.py), [случаи](https://ipvolt.com/downloads/scrapy-proxy-setup/cases.py), [прокси](https://ipvolt.com/downloads/scrapy-proxy-setup/auth_proxy.py), [SOCKS5-сервер](https://ipvolt.com/downloads/scrapy-proxy-setup/socks5_server.py), [README](https://ipvolt.com/downloads/scrapy-proxy-setup/README.md) и [записанные результаты](https://ipvolt.com/downloads/scrapy-proxy-setup/results.json).

Не проверялось: macOS и Windows, авторизация прокси по Digest и NTLM, SOCKS-прокси с авторизацией, прокси, до которых нужно подключаться по TLS, HTTP/2 и обработчик загрузки Scrapy на основе aiohttp.

ipvolt — прокси-сервис для разработчиков, который пока не открыт; [запишитесь в список раннего доступа](https://ipvolt.com/#waitlist-closing), и вы получите одно письмо, когда он откроется.

## Источники и дополнительное чтение

- [Scrapy documentation: HttpProxyMiddleware and HTTPPROXY_ENABLED](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.httpproxy)
- [Scrapy documentation: Request.meta special keys (proxy, dont_retry, handle_httpstatus_list)](https://docs.scrapy.org/en/latest/topics/request-response.html#topics-request-meta)
- [Scrapy documentation: HttpErrorMiddleware and HTTPERROR_ALLOWED_CODES](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#module-scrapy.spidermiddlewares.httperror)
- [Scrapy documentation: RetryMiddleware](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.retry)
- [Scrapy documentation: Spider.start()](https://docs.scrapy.org/en/latest/topics/spiders.html#scrapy.Spider.start)
- [Scrapy documentation: HttpxDownloadHandler](https://docs.scrapy.org/en/latest/topics/download-handlers.html#httpx-handler)
- [Scrapy release notes: 2.16.0, start_requests() removed](https://docs.scrapy.org/en/latest/news.html#release-2-16-0)
- [Scrapy release notes: 2.6.2, Proxy-Authorization security fix](https://docs.scrapy.org/en/latest/news.html#release-2-6-2)
- [Scrapy release notes: 2.7.1, explicit Proxy-Authorization header](https://docs.scrapy.org/en/latest/news.html#release-2-7-1)

## Связанные руководства

- [Как исправить ошибку прокси 407, не гадая](https://ipvolt.com/ru/guides/fix-proxy-error-407.md)
- [Scrapy: start_requests() не вызывается, Crawled 0 pages](https://ipvolt.com/ru/guides/scrapy-start-requests-not-called.md)
- [Переменные окружения прокси: HTTP_PROXY и NO_PROXY](https://ipvolt.com/ru/guides/proxy-environment-variables.md)

## О ipvolt

Примеры используют обобщённые настройки прокси со ссылками на оригинальную техническую документацию. Поведение конкретного продукта уточняйте у своего провайдера. ipvolt пока в разработке.

[Читать оригинал на английском](https://ipvolt.com/guides/scrapy-proxy-setup.md)

## Узнайте, когда откроется доступ.

ipvolt · В разработке

Мы строим прокси-инфраструктуру для разработчиков и команд, работающих с данными. Оставьте email, чтобы получить уведомление, когда ipvolt будет готов.

Одно письмо, когда откроется доступ. Больше ничего.

[Получить ранний доступ](https://ipvolt.com/ru/guides/scrapy-proxy-setup#waitlist-closing)

[Конфиденциальность](https://ipvolt.com/privacy)

