Начать пробный период
Назад

Ошибка 405 Method Not Allowed в веб-скрапинге: причины и решения

Обобщите эту статью с помощью предпочитаемого вами AI
Попробуйте наши премиум-прокси

Протестируйте наши премиум-прокси без ограничений по качеству.

  • Мобильные и резидентные прокси
  • Таргетинг на уровне ZIP
  • Статические и ротируемые IP
  • Встроенный фильтр качества
Попробовать сейчас

Ответ 405 Method Not Allowed означает, что сервер нашёл ваш URL, но отклонил отправленный вами HTTP-метод. В скрапинге на деле часто происходит не это. Некоторые серверы отвечают автоматизированным клиентам кодом 405, поэтому ваш скрипт падает на странице, которая без проблем загружается в Chrome.

Это руководство для разработчиков, которые отлаживают скрапер на Python. Из него вы узнаете, как примерно за две минуты отличить один случай от другого и что делать в каждом из них.

Что означает ошибка 405 Method Not Allowed

405 — это ошибка клиента. Сервер нашёл ресурс и понял запрос, но использованный вами метод отсутствует в списке методов, которые этот ресурс принимает.

RFC 9110, который в июне 2022 года заменил RFC 7231, требует, чтобы серверы отправляли заголовок Разрешить с каждым ответом 405, перечисляя в нём поддерживаемые методы. Почти никто при отладке скрапера его не читает. А ведь это самый быстрый способ отличить настоящую ошибку метода от отказа в обслуживании.

Один и тот же код состояния отображается по-разному в зависимости от серверного ПО:

  • 405 Method Not Allowed
  • HTTP Error 405 – Method Not Allowed
  • 405 Not Allowed
  • The requested method POST is not allowed for the URL
  • HTTP verb used to access this page is not allowed
  • This page isn’t working – HTTP ERROR 405

На уровне протокола все они означают одно и то же.

Почему ваш скрапер получает 405, а браузер нет

Ошибка 405 в скрапере возникает по одной из трёх причин. Каждая требует своего решения, поэтому угадывать — значит терять время.

ПричинаЧто вы видитеКак подтвердить
Метод действительно неверный405 на API или эндпоинте формы, каждый разРазрешить заголовок перечисляет метод, который вы не использовали
Сервер отклоняет ваш клиент405 на обычной странице, которая должна принимать GETТот же URL открывается в Chrome
Отклонение CORS preflight-запроса405 на запрос OPTIONS от инструментов автоматизации браузераТолько при внедрённых кросс-доменных запросах

Почему сервер возвращает 405 вместо 403

На обычной странице GET, очевидно, разрешён, поэтому 405 там на самом деле не про метод. Некоторые защитные слои отвечают не-браузерным клиентам 405 вместо 403, потому что это говорит вам меньше. 403 сообщает, что вас распознали и отказали. 405 отправляет вас переписывать методы запросов.

Обычно клиент на Python выдаёт себя не строкой User-Agent. Его выдаёт всё, что находится под ней.

  • TLS-рукопожатие. запросы согласовывает TLS через OpenSSL. Его список шифров, порядок расширений и поддерживаемые группы не совпадают с Chrome, и эта комбинация достаточно стабильна для фингерпринтинга. Хэши JA3 и JA4 описывают именно это.
  • Версия протокола. Chrome общается с большинством крупных сайтов по HTTP/2 или HTTP/3. запросы говорит только по HTTP/1.1. Это разделяет их ещё до того, как будет прочитан хоть один заголовок.
  • Настройки HTTP/2. Клиенты, которые всё же используют HTTP/2, отправляют собственные значения SETTINGS и размеры окон, и они различаются от библиотеки к библиотеке.
  • Порядок заголовков. Браузеры отправляют заголовки в постоянном порядке. запросы добавляет собственные заголовки по умолчанию в своём порядке, который не совпадает ни с одним браузером.

Поэтому подмена User-Agent на Chrome иногда ничего не меняет. Вы исправили один сигнал из четырёх.

Когда метод действительно неверный

Настоящая версия этой ошибки появляется, когда вы обращаетесь к эндпоинту, взятому из DevTools, а не к странице. Эндпоинты поиска, обработчики пагинации и формы входа обычно принимают только POST.

Откройте вкладку Network, найдите неудавшийся запрос и посмотрите на колонку Method. Если там указан POST, а ваш код отправляет GET, вопрос закрыт. Ничего из диагностики блокировок здесь не применимо.

Как диагностировать ошибку 405 за две минуты

Выполняйте эти шаги по порядку. Остановитесь, как только один из них даст вам ясный ответ.

1. Прочитайте заголовок Разрешить заголовок. Самая быстрая проверка, и обычно она сама снимает вопрос.

python

import requests

url = "https://example.com/api/search"

response = requests.options(url, timeout=10)
print(response.status_code)
print(response.headers.get("Allow"))

Если в ответе перечислен GET, а ваш GET был отклонён, проблема не в методе. Если указан только POST, проблема именно в нём.

2. Откройте URL в браузере. Работает в Chrome, не работает в вашем скрипте, сеть одна и та же: разница в вашем клиенте.

3. Проверьте, когда именно происходит сбой. Сбой на первом же запросе указывает на то, как сформирован запрос. Сбой после пятидесяти успешных запросов указывает на ограничение частоты запросов.

4. Посмотрите на заголовки ответа. cf-ray, server: cloudflare или заголовки Akamai означают, что ответил защитный слой, а не само приложение.

5. Подождите пятнадцать минут и отправьте тот же запрос ещё раз, ничего не меняя. Если он сработал, вы упёрлись во временное ограничение. Снизьте темп, а не переписывайте код.

ДиагностикаИди
Разрешить указывает метод, который вы не использовалиРешение 1
Открывается в Chrome, но в вашем скрипте не работает с первого же запросаРешение 2
Работает, затем перестаёт после N запросовРешение 3
Проходит само по себе после паузыРешение 3, плюс снизьте частоту запросов

Решение 1: отправляйте метод, который ожидает эндпоинт

Просто, когда заголовок Разрешить уже подсказал вам, что отправлять. Воспроизведите запрос, который показали DevTools, включая payload и тип содержимого.

python

import requests

url = "https://example.com/api/search"
payload = {"query": "laptops", "page": 1}

response = requests.post(url, json=payload, timeout=10)
print(response.status_code)

Здесь многих подводят редиректы. Если запрос проходит через 301 или 302, некоторые клиенты по пути превращают POST в GET, так что по URL, который вы написали, метод верный, а по URL, куда вы в итоге попали, нет. Выведите response.history и посмотрите, как выглядел итоговый запрос, прежде чем винить эндпоинт.

Решение 2: отправляйте полный запрос

Если эндпоинт принимает ваш метод, но отвергает ваш клиент, значит, сам запрос неполный. Браузер отправляет с десяток заголовков при каждом переходе. Голый requests.get() отправляет четыре.

python

import requests

headers = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
    ),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1",
}

session = requests.Session()
session.headers.update(headers)

response = session.get("https://example.com/", timeout=10)
print(response.status_code)

Использовать Сессия вместо отдельных вызовов. Она сохраняет файлы cookie между запросами и переиспользует соединение, как это делает браузер. Независимые запросы, каждый из которых открывает новое соединение и не несёт файлов cookie, выглядят странно на любом сайте с авторизацией.

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

Если полный набор заголовков ничего не меняет, остаётся разница в TLS и протоколе, описанная выше. curl_cffi и httpx с http2=True частично закрывают этот разрыв. А браузер для скрейпинга закрывает остальное, но стоит вам скорости, поскольку это настоящий браузерный движок.

Решение 3: распределите запросы между большим числом IP-адресов

Если 405 появляется только после серии успешных запросов или сам исчезает после паузы, причина в объёме запросов с одного адреса. Заголовки здесь ни при чём. Их переписывание не поможет.

Сначала замедлитесь. Добавьте задержку между запросами и не завышайте параллелизм. Сайты публикуют свои ограничения частоты запросов чаще, чем принято думать, и оставаться в их рамках дешевле, чем обходить их инженерными средствами.

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

python

proxies = {
    "http": "http://YOUR_USERNAME:YOUR_PASSWORD@PROXY_HOST:PROXY_PORT",
    "https": "http://YOUR_USERNAME:YOUR_PASSWORD@PROXY_HOST:PROXY_PORT",
}

response = session.get(url, proxies=proxies, timeout=15)
print(response.status_code)

Качество IP здесь важно по практической причине. Адреса, уже отмеченные где-то ещё, приходят вместе с этой репутацией, поэтому ротация через пул низкого качества может давать сбои чаще, чем один чистый адрес. NodeMaven фильтрует свой пул до того, как IP попадут к вам.

Логика повторных запросов, которая не расходует трафик впустую

Большинство скраперов повторяют любой ответ, отличный от 200, одинаково. Для 405 это неверное поведение по умолчанию. Реальное несовпадение метода будет падать одинаково всегда, а каждая попытка стоит трафика.

Классифицируйте ошибку один раз при первом сбое, а затем действуйте по результату.

python

import time
import requests


def fetch(session, url, proxy_pool, max_attempts=3):
    for attempt in range(max_attempts):
        proxies = proxy_pool[attempt % len(proxy_pool)]
        response = session.get(url, proxies=proxies, timeout=15)

        if response.status_code != 405:
            return response

        allowed = response.headers.get("Allow", "")
        if allowed and "GET" not in allowed:
            raise ValueError(f"GET not supported here. Allowed: {allowed}")

        time.sleep(2 ** attempt)

    return None

Никогда не повторяйте запрос при подтверждённом несовпадении метода. Меняйте IP между попытками, а не повторяйте с одного и того же адреса. Делайте паузу с нарастающей задержкой вместо того, чтобы долбить сервер, потому что временному ограничению нужно время, а не ещё один запрос.

405 против 403, 404, 401 и 415

Их постоянно путают, поскольку симптом один и тот же. Ваш запрос не прошёл.

КодРесурс существуетЗапрос корректно сформированМетод разрешёнДоступ разрешёнОбычное значение при скрапинге
400 Неверный запросn/aНетn/an/aНекорректное тело запроса или неверные параметры
401 Не авторизованоДаДаДаНужна авторизацияОтсутствует токен или истекла сессия
403 ЗапрещеноДаДаДаНетВас идентифицировали и отказали в доступе
404 Не найденоНетДаn/an/aНеверный URL или ресурс скрыт от вас
405 Method Not AllowedДаДаНетДаНеверный метод или замаскированный отказ
415 Unsupported Media TypeДаДаДаДаНеверно Content-Type в POST-запросе

Ошибки 403 и 405 на одном сайте на практике часто означают одно и то же. Просто 403 признаёт это открыто.

Как ошибка 405 проявляется в разных инструментах

ИнструментЧто вы видитеПроверьте сначала
запросыresponse.status_code == 405, исключение не выбрасываетсяПроверяете ли вы код статуса вообще
httpxТо же самое, если вы не вызываете raise_for_status()Включить http2=True
ScrapyОтвет отбрасывается по умолчанию, 405 отсутствует в handle_httpstatus_listDOWNLOAD_DELAY и CONCURRENT_REQUESTS_PER_DOMAIN
Playwrightresponse.status() в ответе на навигациюУпала ли сама страница или фоновый XHR-запрос
curlТолько тело ответа, если вы не передадите -i или -вБеги curl -X OPTIONS -i чтобы прочитать Разрешить заголовок

Отдельно стоит сказать о Scrapy. Он отбрасывает ответы с кодами, отличными от 2xx, ещё до запуска вашего парсера, поэтому паук (spider), который ничего не возвращает, может на самом деле получать 405, которых вы не видите. Добавьте 405 в handle_httpstatus_list на время отладки, и ответ дойдёт до вашего callback.

Когда правильный ответ: остановиться

Иногда 405 означает, что сайт не хочет видеть автоматизированный трафик, и правильная реакция в этом случае — оставить его в покое.

Проверить robots.txt и условия использования, прежде чем тратить на него больше времени. Поищите официальный API: он, как правило, быстрее и стабильнее, чем скрапинг фронтенда. Сбор данных должен оставаться в рамках закона и правил самой платформы, и всё описанное здесь предполагает работу с публично доступными данными на сайтах, где их сбор разрешён.

Эти два клиента различаются на уровне ниже заголовков. Chrome иначе согласует TLS, обычно использует HTTP/2 и отправляет полный набор заголовков в постоянном порядке. Стандартный вызов запросы не совпадает ни с одним из этих признаков, поэтому сервер, фильтрующий автоматизированный трафик, легко их различает.

Если причина в ограничении частоты запросов, обычно от десяти до двадцати минут. Подождите, отправьте точно такой же запрос ещё раз, и если он пройдёт, вы подтвердите, что это временное ограничение, а не проблема конфигурации. Повторение запроса во время паузы может её продлить.

Иногда, на слабо защищённых сайтах. Чаще это не срабатывает, потому что User-Agent — лишь один сигнал из нескольких, а остальные по-прежнему указывают на скрипт.

Только когда причина связана с вашим IP-адресом, например ограничение частоты запросов или адрес с плохой репутацией. Прокси не поможет при реальном несоответствии метода или неполном запросе, поэтому сначала проведите диагностику. Если вы уже работаете через прокси и всё равно получаете отказ, проверьте адрес с помощью нашего бесплатного IP-адрес , чтобы увидеть страну, ISP и репутацию, которые видит целевой сайт.

Он перечисляет методы, которые принимает ресурс, и по спецификации сервер обязан отправлять его с каждым ответом 405. Прочитать его — самый быстрый способ отличить настоящую ошибку метода от отказа, потому что сервер, отклоняющий вашего клиента, часто перечисляет в нём именно тот метод, который только что отверг.

Вам также могут понравиться эти статьи

Этот сайт использует Файлы cookie чтобы улучшить ваш опыт. Продолжая, вы соглашаетесь на использование файлов cookie.