bigywater.tech
爬虫工程 · 6

重定向:301、302、307、308 对 POST 的影响与跳转上限

重定向看起来是客户端库替你处理的小事,但它决定了 POST 请求体会不会丢、一个死循环会让服务器多挨多少下、你的令牌会不会被带到别的主机。这篇在本机用 301、302、303、307、308 逐个试。

0 对 7
301/302/303 与 307/308 之后,目标地址收到的请求体字节数(请求体原本 7 字节)
31 个
一个一直 302 到自己的地址,服务器在客户端放弃前收到的请求数
X-Token
自定义请求头,跳到另一个主机时被原样带过去

状态码决定方法和请求体

实测 · 本机执行输出
  POST /r301 → 目标 /final:方法 GET,请求体 0 字节,Content-Type=None,Content-Length=None;服务器共收到 2 个请求;history=[301]
  POST /r302 → 目标 /final:方法 GET,请求体 0 字节,Content-Type=None,Content-Length=None;服务器共收到 2 个请求;history=[302]
  POST /r303 → 目标 /final:方法 GET,请求体 0 字节,Content-Type=None,Content-Length=None;服务器共收到 2 个请求;history=[303]
  POST /r307 → 目标 /final:方法 POST,请求体 7 字节,Content-Type=None,Content-Length=7;服务器共收到 2 个请求;history=[307]
  POST /r308 → 目标 /final:方法 POST,请求体 7 字节,Content-Type=None,Content-Length=7;服务器共收到 2 个请求;history=[308]

301、302、303 之后 requests 把 POST 改成 GET 并丢掉请求体;307、308 保持方法和请求体。服务器想让客户端重发表单,要用 307/308,否则 POST 的数据会悄悄消失,目标地址只会收到一个没有 Content-Length 的 GET。

history 与 allow_redirects

实测 · 本机执行输出
  allow_redirects=False:状态 302,Location=/final,is_redirect=True,r.next.url=http://127.0.0.1:9961/final
  /chain/3 逐跳:[(302, '/chain/3'), (302, '/chain/2'), (302, '/chain/1')] → 最终 200 /chain/0
  /rel 回 301 Location: sub/page(相对路径)→ 最终 URL /sub/page,状态 200

allow_redirects=False 可以拿到原始的 302 响应和 Location(r.next 是拼好的下一个请求)。默认跟随时,r.history 按顺序保存每一跳的响应;相对路径的 Location 会被解析成完整 URL。爬虫要记录“最终 URL”时看 r.url,要记录来路看 r.history。

跳转上限

实测 · 本机执行输出
  Session.max_redirects 默认值 = 30
  /loop(一直 302 到自己):TooManyRedirects: Exceeded 30 redirects.;服务器收到 31 个请求
  max_redirects=5:TooManyRedirects;服务器收到 6 个请求
  链长 29 跳:成功,history 29 跳;服务器收到 30 个请求
  链长 30 跳:成功,history 30 跳;服务器收到 31 个请求
  链长 31 跳:TooManyRedirects;服务器收到 31 个请求

max_redirects 默认 30。服务器在客户端放弃前会收到 31 个请求(第一次加 30 次跳转);链长 30 跳的重定向还能成功,31 跳抛 TooManyRedirects。爬虫遇到重定向环时,上限就是它给目标服务器多添的请求数,可以调小。

跨主机时的请求头

实测 · 本机执行输出
  headers= 手写 Authorization、X-Token、Cookie:A 收到的头 ['Host', 'User-Agent', 'Accept-Encoding', 'Accept', 'Connection', 'Authorization', 'X-Token', 'Cookie']
  headers= 手写 Authorization、X-Token、Cookie:B 收到的头 ['Host', 'User-Agent', 'Accept-Encoding', 'Accept', 'Connection', 'X-Token']
  auth=(user, pass):A 收到的头 ['Host', 'User-Agent', 'Accept-Encoding', 'Accept', 'Connection', 'Authorization']
  auth=(user, pass):B 收到的头 ['Host', 'User-Agent', 'Accept-Encoding', 'Accept', 'Connection']
  requests 的 should_strip_auth(决定跳转时要不要去掉 Authorization)对几组 URL 的判断:
    http://example.test/a → http://example.test/b:去掉 Authorization = False
    http://example.test:8000/a → http://example.test:9000/a:去掉 Authorization = True
    http://example.test/a → https://example.test/a:去掉 Authorization = False
    http://example.test:8080/a → https://example.test:8443/a:去掉 Authorization = True
    https://example.test/a → http://example.test/a:去掉 Authorization = True
    http://example.test/a → http://other.test/a:去掉 Authorization = True

跳到另一个主机(这里是不同端口)时,requests 会去掉 Authorization 和手写的 Cookie 头,但自定义的 X-Token 之类的头原样转发。如果你的令牌放在自定义头里,跳转到第三方主机就会一起带过去——这是实测结果,需要自己在跳转前处理(例如 allow_redirects=False 手动判断)。

分步演示

重定向
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。服务器按路径返回指定状态码的重定向、无限循环重定向、定长重定向链、相对 Location 的重定向,并记录目标地址收到的方法、请求体和全部请求头(按收到的顺序);跨主机用两个本机端口模拟;should_strip_auth 是直接调用 requests 的函数,没有发请求
    • 没有验证:浏览器对同样状态码的处理(这里只测了 requests);HEAD、PUT、DELETE 等其它方法遇到重定向的表现;Referer 头在跳转时的处理;代理环境下的重定向;aiohttp 等库的默认行为(它们的默认值不同,这里没有测)
    京ICP备2026067502号-1京公网安备11010502063487号