Ошибка 403 при работе через прокси: почему сайт отказывает и что менять
Код 403 приходит от целевого сайта, который запрос принял, прочитал и отклонил. Узел-посредник тут ни при чём: он честно донёс обращение до площадки, и площадка ответила отказом по содержанию запроса. Значит разбирать нужно сам запрос: темп обращений, набор и порядок заголовков, cookies, отпечаток шифрованного рукопожатия, доступность конкретного раздела.
Ниже отказ разложен по шести слоям от самого частого к самому редкому, по каждому дан признак, который отличает его от остальных, и правка, которая его снимает. Отдельно разобрано, как снять реальный ответ вместе с телом страницы отказа, чем 403 отличается от 429 и 503 и как выстроить прогон, чтобы отказы перестали появляться.
Почему 403 приходит от площадки и что это меняет
Проверка источника отказа занимает одну команду. Тот же запрос отправляется на нейтральный эхо-сервис через тот же выход. Ответ 200 оттуда означает, что канал рабочий: соединение поднялось, туннель встал, ответ вернулся. После этого все дальнейшие правки касаются только запроса.
# канал живой?
curl -s -o /dev/null -w '%{http_code}\n' -x http://185.24.87.14:8000 https://ifconfig.me
# 200
# а целевая площадка?
curl -s -o /dev/null -w '%{http_code}\n' -x http://185.24.87.14:8000 https://example.com/catalog
# 403
Два разных кода на двух доменах через один выход дают однозначный вывод: узел пропустил оба обращения, отказ вынесла площадка. Мы начинаем любой разбор именно с этой пары команд, потому что она за пять секунд отсекает подозрения на настройки доступа.
Отсюда второе наблюдение, менее очевидное. Площадка видит запрос целиком: строку запроса, все заголовки в том порядке, в котором они пришли, набор cookies, параметры шифрованного рукопожатия, темп обращений с одного адреса. Отказ выносится по совокупности признаков, и один и тот же адрес получает 200 в одном сценарии и 403 в другом. Смена адреса без правки запроса переносит проблему на новый адрес.
Отказ приходит быстро. Обычно за первые сотни миллисекунд, потому что вердикт выносит слой перед приложением.
Шесть слоёв отказа: от частого к редкому
Слои идут по убыванию частоты обращений, которые мы разбираем. У каждого свой признак, отличающий его от соседних, и своя правка. Порядок стоит соблюдать: верхние проверяются парой команд, нижние требуют перестройки прогона.
Слой первый: частота обращений выше принятой
Самый частый источник отказа. Прогон идёт в тридцать потоков с одного адреса, площадка держит счётчик обращений в минуту, порог перейден, дальше идёт отказ на всё подряд. Признак узнаётся по картине во времени: первые сотни запросов проходят нормально, потом начинается сплошная полоса отказов, и она держится, пока темп не упадёт.
Второй признак: отказ приходит на все адреса пула одновременно, если прогон разложен на несколько выходов и темп на каждом одинаково высокий. Третий признак: пауза в несколько минут снимает отказ без единой правки в запросе.
Правка сводится к трём цифрам. Пауза между обращениями с одного выхода, число одновременных потоков, размер пула, по которому раскладывается нагрузка. Мы считаем так: берём допустимый темп с одного адреса, умножаем на число выходов и получаем общую скорость прогона. Пул держится в районе 12 000 активных адресов, ротация внутри пула автоматическая, поэтому общая скорость набирается шириной, при спокойном темпе на каждом отдельном выходе.
| Величина | Как задаётся | Что происходит при завышении |
|---|---|---|
| Пауза между запросами | Задержка в настройках прогона | Счётчик площадки переполняется |
| Одновременные потоки | Лимит в софте | Всплеск обращений в первую секунду |
| Ширина ротации | Размер пула под прогон | Нагрузка садится на узкую группу выходов |
| Повторы после отказа | Политика ретраев | Отказы множатся, счётчик растёт дальше |
Отдельно про повторы. Скрипт получил 403 и тут же отправил тот же запрос заново, потом ещё раз, потом с задержкой в секунду. Счётчик площадки при этом продолжает расти, и вместо восстановления прогон загоняет себя глубже. Правильный порядок: отказ фиксируется, темп снижается, запрос уходит в отложенную очередь. Пакеты дают до 1000 потоков на стандартных вариантах и до 3000 на корпоративном, при этом сама цифра лимита к порогу площадки отношения не имеет, её задаёт сайт. Рабочие связки по темпу и ширине пула описаны там, где берутся прокси для парсинга и сбора данных.
Слой второй: заголовки не похожи на браузер
Второй по частоте источник. Библиотека запросов отправляет минимальный набор полей, площадка ждёт полный набор, разница видна с первого обращения. Признак отличается от первого слоя ровно одним свойством: отказ приходит на самом первом запросе, до всякого темпа, и повторяется стабильно с любого выхода.
Библиотека по умолчанию шлёт три-четыре поля. Браузер шлёт полтора десятка, в фиксированном порядке, с осмысленными значениями. Разница читается автоматом.
# то, что уходит из библиотеки без настройки
GET /catalog HTTP/1.1
Host: example.com
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate
Accept: */*
# то, что уходит из браузера
GET /catalog HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate, br
Sec-Ch-Ua: "Chromium";v="124", "Not:A-Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
Connection: keep-alive
Порядок полей значит не меньше состава. Браузеры отправляют заголовки в устойчивой последовательности, и площадка сверяет её вместе с содержимым User-Agent. Набор, собранный вручную по алфавиту или в случайном порядке, выдаёт себя даже при полном составе полей.
| Поле | Что показывает площадке | Частая ошибка |
|---|---|---|
User-Agent | Программа и версия | Оставлено значение библиотеки |
Accept-Language | Ожидаемый язык ответа | Отсутствует целиком |
Accept-Encoding | Поддерживаемое сжатие | Нет br, хотя заявлен свежий Chrome |
Sec-Fetch-* | Контекст перехода | Пропущены при заявленном Chromium |
Sec-Ch-Ua | Версия движка | Версия расходится с User-Agent |
Referer | Откуда пришёл переход | Стоит на первом же обращении к сайту |
Правка занимает один проход по коду. Мы снимаем настоящий набор из браузера через панель разработчика, копируем его как команду curl и переносим в прогон целиком, вместе с порядком. Дальше набор держится согласованным: версия в Sec-Ch-Ua совпадает с версией в User-Agent, язык в Accept-Language совпадает с ожидаемым, Referer появляется только со второго перехода.
Слой третий: нет привычного набора cookies
Третий слой узнаётся по характерной картине: главная страница отдаётся кодом 200, внутренний раздел отвечает 403. Площадка ставит служебные cookies на первом заходе и ждёт их на всех последующих обращениях. Прогон, который ходит сразу на карточки, эти поля не получает и не отправляет.
Признак, отличающий слой от предыдущего: заголовки в порядке, отказ приходит выборочно по разделам. Проверяется парой запросов подряд с сохранением банки cookies.
# первый заход: забираем cookies
curl -s -c jar.txt -x http://185.24.87.14:8000 https://example.com/ -o /dev/null
# второй заход в раздел: отдаём их обратно
curl -s -b jar.txt -c jar.txt -x http://185.24.87.14:8000 https://example.com/catalog -o page.html -w '%{http_code}\n'
# что вообще положила площадка
cat jar.txt
Код 200 на втором запросе при отказе на прямом обращении в раздел подтверждает слой однозначно. Правка простая: прогон начинается с главной страницы, банка cookies живёт на протяжении сессии и привязывается к тому же выходу, с которого была получена. Смена выхода посреди сессии обнуляет доверие: площадка видит, что её cookies пришли с другого адреса.
Отсюда практический вывод про ротацию. Сессия и выход держатся вместе от начала до конца, а новая сессия берёт новый выход. Ротация внутри пула автоматическая, поэтому схема укладывается в один параметр прогона: сессия открывается, отрабатывает свои страницы и закрывается вместе с банкой cookies.
Слой четвёртый: отпечаток рукопожатия расходится с User-Agent
Четвёртый слой встречается там, где перед сайтом стоит защита с разбором шифрованного рукопожатия. Клиент заявляет себя браузером в заголовке, а параметры согласования шифрования у него совсем другие: свой набор наборов шифров, свой порядок расширений, своя поддержка версий протокола. Несовпадение читается до отправки первого прикладного заголовка.
Признак отличается от третьего слоя тем, что отказ приходит на любом разделе, включая главную, и не снимается ни паузой, ни банкой cookies, ни полным набором заголовков. Второй признак: тот же запрос из настоящего браузера через тот же выход проходит нормально.
| Что сравнивает площадка | Браузер | Библиотека по умолчанию |
|---|---|---|
| Набор шифров и его порядок | Устойчивый для версии | Порядок берётся из системной библиотеки |
| Расширения рукопожатия | Полный набор, включая перемешивание | Сокращённый набор |
| Заявленные версии протокола | Свежая версия с откатом | Часто только одна версия |
| Согласование прикладного протокола | h2 и запасной вариант | Нередко отсутствует |
| Порядок прикладных заголовков | Фиксированный для движка | Порядок словаря в коде |
Правка идёт в сторону настоящего браузерного стека. Прогон переносится в браузер под управлением, либо берётся клиент, который повторяет рукопожатие движка целиком. Антидетект-браузеры делают ровно это: движок настоящий, поэтому рукопожатие совпадает с заявленным заголовком автоматически. Профили при этом мы разводим по разным выходам, и связка профиля с адресом держится постоянной на всю сессию. Настройки под такие браузеры собраны на странице про прокси для антидетект-браузеров.
# посмотреть, какую версию протокола и какой набор согласовал клиент
curl -v -x http://185.24.87.14:8000 https://example.com -o /dev/null 2>&1 | grep -E 'SSL connection|ALPN|TLSv'
Слой пятый: раздел закрыт для всех
Пятый слой самый простой и самый обидный, потому что правки в прогоне тут ничего не меняют. Площадка отдаёт 403 на конкретный путь всем подряд, включая обычный браузер без посредника. Каталог убрали под учётную запись, файл лежит вне доступного дерева, служебный путь закрыт настройками веб-сервера.
Проверяется за десять секунд: тот же адрес открывается в браузере напрямую. Отказ там означает, что вопрос в самом разделе. Тело ответа в таком случае обычно короткое и типовое, с формулировкой веб-сервера.
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
Такая страница означает отказ на уровне веб-сервера. Здесь помогает пересборка маршрута: смотрим, откуда на нужный раздел ведут живые переходы, и повторяем путь пользователя. Часть площадок отдаёт те же данные другим путём, через открытый интерфейс выдачи или через версию страницы для печати, и это дешевле любой борьбы с закрытым путём.
Слой шестой: сработала защита перед сайтом
Шестой слой отличается от пятого содержимым тела. Страница отказа приходит фирменная: заголовок защиты, идентификатор обращения, иногда проверка в браузере. Заголовки ответа тоже говорящие, там появляются служебные поля защитного слоя.
# смотрим ответ целиком: заголовки и тело страницы отказа
curl -s -D headers.txt -x http://185.24.87.14:8000 https://example.com/catalog -o body.html
head -20 headers.txt
grep -iE 'ray|request-id|challenge|blocked' body.html | head
Признаки, по которым слой опознаётся: код 403 приходит вместе со служебным идентификатором обращения в заголовках, тело весит несколько килобайт и содержит скрипт проверки, ответ отдаётся быстрее, чем обычные страницы сайта. При этом главная страница может открываться нормально, потому что защита включается на отдельных путях.
| Что в теле ответа | Что это означает | Куда смотреть дальше |
|---|---|---|
| Короткая страница веб-сервера | Путь закрыт настройками | Маршрут перехода и доступность раздела |
| Страница с идентификатором обращения | Отработала защита перед сайтом | Отпечаток рукопожатия и полнота заголовков |
| Страница с проверкой в браузере | Ожидается исполнение скрипта | Прогон переносится в браузерный движок |
| Форма ввода символов с картинки | Поведение сочтено автоматическим | Темп, ширина ротации, набор cookies |
| Обычная страница сайта с текстом отказа | Отказ вынесло само приложение | Учётная запись и права на разделе |
Правка идёт по совокупности: браузерный движок для исполнения скриптов, полный и согласованный набор заголовков, живая банка cookies, спокойный темп и широкая ротация выходов. Каждый пункт по отдельности отказ не снимает, вместе они дают рабочий прогон.
Как снять реальный ответ и прочитать страницу отказа
Отладка начинается с того, чтобы увидеть ответ целиком. Код без тела говорит мало, тело без заголовков тоже. Мы снимаем обе части одной командой и раскладываем их по файлам.
curl -s -D headers.txt -o body.html -w 'code=%{http_code} size=%{size_download} time=%{time_total}\n' \
-x http://185.24.87.14:8000 \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \
-H 'Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8' \
https://example.com/catalog
Три цифры в выводе дают первую подсказку. Размер тела в несколько сотен байт указывает на страницу веб-сервера, несколько килобайт указывают на защитный слой, размер обычной страницы сайта указывает на отказ самого приложения. Время ответа меньше сотни миллисекунд говорит о том, что запрос до приложения не дошёл.
Дальше читаем заголовки ответа. Служебные поля защитного слоя, поля кеша, поле Retry-After, поле Set-Cookie с новой служебной строкой. Каждое из них сужает круг.
# коды по серии запросов, чтобы увидеть картину во времени
for i in $(seq 1 40); do
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' -x http://185.24.87.14:8000 https://example.com/catalog)"
sleep 1
done
echo
# 200 200 200 200 200 403 403 403 403 403 403 ...
Полоса двухсоток, которая обрывается на отказ, указывает на темп. Отказ с первого же запроса указывает на запрос сам по себе. Чередование кодов указывает на ротацию: часть выходов уже под ограничением, часть ещё нет. Как правильно замерять серию и не путать кеш с реальным ответом, разобрано в материале про замер скорости и времени отклика.
Чем 403 отличается от 429 и 503
Три кода приходят в похожих обстоятельствах и требуют разных действий. Различить их помогает таблица и одно наблюдение: только 429 прямо называет причину, остальные два оставляют её на догадку.
| Код | Что говорит площадка | Тело ответа | Что делать |
|---|---|---|---|
| 403 | Запрос прочитан и отклонён | Страница отказа или защиты | Править запрос: темп, заголовки, cookies, отпечаток |
| 429 | Частота обращений превышена | Короткое, часто с полем Retry-After | Ждать указанное время, снижать темп |
| 503 | Приём запросов временно закрыт | Страница обслуживания или защиты | Отложить прогон, проверить сайт глазами |
| 401 | Раздел требует подтверждения права | Пустое, с полем WWW-Authenticate | Учётная запись на самой площадке |
| 407 | Право доступа к пулу не подтверждено | Пустое, с полем Proxy-Authenticate | Привязка адреса и пара логина с паролем |
| 404 | Путь отсутствует | Страница сайта | Сверить адрес и параметры запроса |
Код 407 в этом ряду стоит особняком: его выписывает узел-посредник до целевого сайта, и на нейтральном эхо-сервисе он воспроизводится точно так же. Полный разбор этого случая вынесен в соседнюю статью раздела диагностики.
Поле Retry-After в ответе означает, что площадка сама называет паузу. Его стоит читать и соблюдать: игнорирование поля быстро переводит ограничение в постоянный отказ. При коде 503 полезно открыть сайт в браузере: страница обслуживания видна сразу, и тогда прогон просто откладывается.
Почему повтор того же запроса ничего не даёт
Отказ 403 вынесен по содержанию запроса. Содержание не изменилось, значит и ответ будет прежним. Повтор при этом добавляет обращение в счётчик площадки и приближает переход ограничения в длительное. Мы видим это в журналах регулярно: скрипт с агрессивной политикой повторов превращает разовый отказ в сплошную полосу за пару минут.
Работающий порядок выглядит иначе. Отказ фиксируется вместе с кодом, размером тела и временем ответа. Запрос уходит в отложенную очередь. Прогон продолжается на других задачах, темп снижается, и отложенная очередь перебирается позже, уже с изменённым запросом или с другого выхода. Повтор без правки допустим ровно в одном случае: когда предыдущий отказ пришёл на пике темпа и пауза заведомо превышает окно счётчика.
# порядок обработки отказа: без слепых повторов
import time, collections
deferred = collections.deque()
def handle(url, code, body_len):
if code == 200:
return "ok"
if code == 403:
deferred.append((url, time.time() + 900)) # разбираем позже
return "отложено, темп снижен"
if code == 429:
return "пауза по полю Retry-After"
return "разбор по коду " + str(code)
Число 900 в примере это пятнадцать минут ожидания. Цифра подбирается под площадку: где-то хватает трёх минут, где-то нужен час. Замер делается один раз на небольшой серии и дальше остаётся в настройках прогона.
Как выстроить прогон, чтобы 403 не появлялись
Устойчивый прогон держится на четырёх настройках, и все они задаются до запуска. Пауза между обращениями с одного выхода, разумное число потоков, ротация внутри пула, согласованный набор заголовков. Порядок важен: сначала считается допустимый темп, потом под него подбирается ширина ротации, и только потом настраивается сам запрос.
| Настройка | Как подобрать | Признак того, что цифра завышена |
|---|---|---|
| Пауза между запросами | Замер серией по сорок обращений | Полоса отказов после первых успехов |
| Потоки в софте | Скорость умножить на время отклика | Всплеск обращений и отказ в первую минуту |
| Ширина ротации | Общая скорость делить на темп с выхода | Отказы возвращаются на те же выходы |
| Набор заголовков | Снимок из панели разработчика | Отказ на самом первом запросе |
| Длина сессии | Число страниц до смены выхода | Отказ появляется в середине сессии |
Пауза замеряется серией. Запускаем сорок обращений с шагом в секунду, смотрим, на каком номере появляется первый отказ, увеличиваем шаг и повторяем. Цифра, при которой сорок обращений проходят подряд, берётся с запасом примерно в треть. Такой замер занимает четверть часа и экономит дни разбора.
Ширина ротации считается из общей скорости. Нужно 5 000 страниц в час, безопасный темп с одного выхода 20 обращений в минуту: 5 000 делим на 60 и на 20, выходит примерно 4 выхода в постоянной работе плюс запас на отдых каждого. Пул около 12 000 адресов такую ширину закрывает с большим запасом, ротация внутри него автоматическая, поэтому раскладка не требует ручного управления списком. Трафик безлимитный, так что объём выкачанных страниц на расчёт не влияет вовсе.
Сессии выстраиваются по площадке. Одна сессия это вход через главную, набор служебных cookies, серия страниц и закрытие. Выход держится один на всю сессию. Длина сессии подбирается тем же замером: увеличиваем число страниц, пока отказ не появится в середине, и берём цифру ниже границы. Готовые связки под парсеры и антидетект-браузеры собраны на странице, где берутся адреса под массовый сбор данных.
Заголовки собираются один раз и живут в конфиге прогона. Пересобирать их приходится редко, а вот сверять на согласованность полезно при каждом обновлении версии в User-Agent: версия движка стоит сразу в трёх полях, и расхождение между ними даёт отказ на любом темпе. Отдельно проверяется, что запросы к службе имён идут через выход, иначе картина по адресу выходит неполной. Как это устроено, разобрано в материале про запросы к службе имён и их утечку.
Перед крупным запуском список адресов перечитывается из кабинета. Он обновляется в режиме реального времени, и сохранённый когда-то файл постепенно устаревает: часть строк перестаёт отвечать, прогон получает лишние ошибки, и они смешиваются с настоящими отказами площадки. Список забирается ссылкой или файлом в форматах IP:PORT и IP:PORT:LOGIN:PASS. Откуда берутся сами узлы, показано на странице про пул на собственных серверных мощностях, а сокетный вариант подключения разобран там, где берётся доступ по протоколу SOCKS5.
Что проверить, когда 403 появился на рабочем прогоне
Прогон работал неделю и начал отдавать отказы. Разбор идёт по цепочке от внешних причин к внутренним, и первые три шага занимают несколько минут.
| Шаг | Вопрос | Чем проверяем | Вывод |
|---|---|---|---|
| 1 | Канал живой? | Запрос на эхо-сервис через тот же выход | Код 200 означает, что отказ от площадки |
| 2 | Раздел открыт вообще? | Тот же путь в браузере напрямую | Отказ там означает закрытый путь |
| 3 | Отказ от темпа? | Серия с паузой в несколько секунд | Проходит означает превышение темпа |
| 4 | Заголовки согласованы? | Снимок из панели разработчика и сверка | Расхождение версий даёт стабильный отказ |
| 5 | Cookies живые? | Заход через главную с сохранением банки | Код 200 после этого подтверждает слой |
| 6 | Отпечаток совпадает? | Тот же запрос из настоящего браузера | Проходит означает расхождение рукопожатия |
Отдельная частая история это правка на стороне площадки. Сайт обновил защиту, порог темпа опустился, набор ожидаемых полей вырос. Прогон при этом не менялся ни строчкой. Здесь помогает тот же замер серией, который делался при настройке: он покажет новую границу за четверть часа, и прогон вернётся в работу с обновлёнными цифрами.
Мы советуем держать результаты замеров в конфиге прогона рядом с настройками потоков: темп с одного выхода, длина сессии, набор заголовков, дата последней сверки. Тогда возврат к площадке через месяц начинается с готовых цифр, и вся настройка сводится к одной серии из сорока обращений. Состав пакета по потокам, привязкам и трафику описан там, где берётся пул IPv4 с безлимитным трафиком.
Частые вопросы
Подойдут ли прокси под мою задачу, если площадка отвечает отказом?
Заранее предугадать поведение каждой площадки нельзя, поэтому перед покупкой доступен бесплатный тест длительностью до 2 часов под ваш запрос. Тест проходит на вашем софте и на ваших целевых доменах, и за два часа видно, какой темп площадка принимает и какой набор заголовков её устраивает.
Можно ли отобрать прокси по стране, чтобы обойти отказ?
Нет, пул это микс со всего мира, выборка по отдельной стране не делается. Отказ 403 выносится по содержанию запроса, поэтому снимается правкой темпа, заголовков, cookies и отпечатка. Разнообразие подсетей в общем пуле работает на ширину ротации, и её обычно хватает с запасом.
Есть ли лимиты по потокам и как они связаны с отказами?
У каждого пакета свой лимит: стандартные варианты дают до 1000 потоков, корпоративный до 3000. Пакеты по потокам не складываются, при двух привязанных адресах общее число делится пополам. Порог площадки задаётся ей самой и с лимитом пакета не связан, поэтому число потоков в софте подбирается под темп, который принимает конкретный сайт.
Чем проверять прокси перед прогоном?
Рекомендуется чекер от Zennolab, у него есть демонстрационная версия. Он проходит список пачкой и показывает отвечающие строки и время отклика. Перед крупным запуском список полезно перечитать из кабинета: он обновляется в режиме реального времени.
Остальную диагностику удобно смотреть по порядку: сначала проверка работы прокси командами и в браузере, затем какие заголовки уходят на сервер при разборе анонимности, отдельно ошибка 407 от узла-посредника со всеми причинами по списку. Когда прогон настроен и отказы ушли, дальше пригодится материал про сбор прайсов и остатков поставщиков.