开始试用
返回

网络爬虫中的 405 Method Not Allowed:原因与解决方法

用你偏好的 AI 总结本文
试用我们的高级代理

无限量测试我们的优质代理,畅享卓越质量。

  • 移动代理和住宅代理
  • ZIP 级别定位
  • 静态 IP 和轮换 IP
  • 内置质量过滤器
立即试用

405 Method Not Allowed 响应表示服务器找到了您的 URL,但拒绝了您发送的 HTTP 方法。在爬虫场景中,实际情况往往并非如此。有些服务器会用 405 来回应自动化客户端,这就是为什么您的脚本在一个 Chrome 中能正常加载的页面上会失败。

本指南面向正在用 Python 调试爬虫的开发者。它介绍如何在大约两分钟内区分这两种情况,以及针对每种情况该怎么做。

405 Method Not Allowed 错误是什么意思

405 是一种客户端错误。服务器找到了资源并理解了请求,但您使用的方法不在该资源接受的方法列表中。

RFC 9110(于 2022 年 6 月取代了 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 错误来自三种情况之一。每种情况需要不同的修复方式,因此靠猜测只会浪费您的时间。

原因你看到的现象确认方法
请求方法确实有误在 API 或表单端点上每次都返回 405允许 响应头列出的方法与您所使用的不同
服务器正在拒绝您的客户端本应接受 GET 的普通页面返回 405同一 URL 在 Chrome 中可以正常加载
CORS 预检请求被拒绝浏览器自动化发出的 OPTIONS 请求返回 405仅在注入跨域请求时出现

服务器为何返回 405 而不是 403

在普通页面上,GET 显然是被允许的,因此那里出现的 405 其实与请求方法无关。一些防护层会用 405 而非 403 来回应非浏览器客户端,因为 405 透露的信息更少。403 表明您已被识别并遭到拒绝,而 405 则会让您去白白折腾改写请求方法。

暴露 Python 客户端身份的通常不是 User-Agent 字符串,而是它底下的一切。

  • TLS 握手。 requests 库 通过 OpenSSL 协商 TLS。它的密码套件列表、扩展顺序和支持的密钥组与 Chrome 的并不一致,而这一组合足够稳定,可以用来生成指纹。JA3 和 JA4 哈希描述的正是这一点。
  • 协议版本。 Chrome 与大多数大型网站通信时使用 HTTP/2 或 HTTP/3。 requests 库 只支持 HTTP/1.1。这在读取任何一个请求头之前就已将二者区分开来。
  • HTTP/2 设置。 确实使用 HTTP/2 的客户端会发送自己的 SETTINGS 值和窗口大小,而这些会因库的不同而有所差异。
  • 标头顺序。 浏览器发送标头的顺序是固定一致的。 requests 库 则按自己的顺序添加自己的默认标头,与任何浏览器都不匹配。

因此,仅仅换上一个 Chrome 的 User-Agent 有时毫无效果。四个信号里您只修好了一个。

当方法确实用错了的时候

这个错误的“真实版本”通常出现在您请求的是从 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 向您展示的那个请求,包括负载和内容类型。

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 质量在这里之所以重要,有一个很实际的原因。在其他地方被标记过的地址会带着这份信誉记录一起到达,因此轮换一个低质量 IP 池可能比使用一个干净 IP 失败得更频繁。NodeMaven 会在 IP 到达您手中之前先对 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,而不是从同一个 IP 重复请求。采用退避策略而不是连续猛发请求,因为临时限制需要的是时间,而不是又一个请求。

405 与 403、404、401 和 415 的区别

这些状态码经常被混淆,因为症状是一样的。您的请求没有通过。

代码资源存在请求格式正确方法被允许允许访问在数据采集中的常见含义
400 Bad Requestn/a否n/an/a请求载荷格式错误或参数无效
401 未授权是是是需要身份验证缺少令牌或会话已过期
403 Forbidden是是是否您已被识别并遭到拒绝
404 未找到否是n/an/aURL 错误,或该资源对您隐藏
405 Method Not Allowed是是否是方法错误,或是变相的拒绝
415 Unsupported Media Type是是是是错误 Content-Type 在 POST 请求中

同一网站上的 403 和 405 在实践中往往意味着同一件事,只是 403 直接承认了而已。

405 在不同工具中的表现形式

工具你看到的现象首先检查
requests 库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 或 -v运行 curl -X OPTIONS -i 阅读 允许 标头

Scrapy 值得一提。它会在您的解析器运行之前丢弃非 2xx 响应,因此一个什么都不返回的爬虫 (spider) 可能正在收到您从未看到的 405。在调试期间,将 405 添加到 handle_httpstatus_list ,响应就会到达您的回调函数。

什么时候应该停止

有时,405 是网站在表明它不欢迎自动化流量,此时正确的做法是不再打扰它。

检查 robots.txt 和服务条款,再决定是否继续投入时间。寻找官方 API,它通常比抓取前端更快也更稳定。数据采集必须遵守法律以及平台自身的条款,本文所有内容都以在允许采集的网站上获取公开数据为前提。

这两种客户端在请求头之下的层面就已不同。Chrome 的 TLS 协商方式不同,通常使用 HTTP/2,并以一致的顺序发送完整的请求头集合。默认的 requests 库 调用与这些特征全都不匹配,因此过滤自动化流量的服务器可以轻松将二者区分开。

如果是由速率限制引起的,通常为十到二十分钟。请稍等,然后再次发送完全相同的请求;如果成功,就说明这是临时限制,而非配置问题。在冷却期内重复请求可能会延长封锁时间。

在防护较弱的网站上偶尔可以。但多数情况下会失败,因为 User-Agent 只是多个信号之一,其他信号仍然指向脚本。

只有当原因与您的 IP 地址相关时才有效,例如速率限制或信誉不佳的地址。对于真正的方法不匹配或不完整的请求,代理无济于事,所以请先进行诊断。如果您已经在使用代理但仍被拒绝,请将该地址放入我们免费的 IP 查询 中运行,查看目标网站所看到的国家、ISP 和信誉。

它列出了资源所接受的方法,并且规范要求服务器在每次返回 405 时都必须附带该头。读取它是区分真正的方法错误与拒绝访问的最快方式,因为拒绝您客户端的服务器往往会把刚刚拒绝的那个方法也列在其中。

您可能还喜欢 这些文章

本网站使用 Cookie 文件 来提升您的使用体验。继续访问即表示您同意我们使用 Cookie。