概览
操作流程
检查首个响应
先查看状态和 Location,再决定是否跟随重定向链。
跟随后检查最终请求
使用 -L 并限制跳转次数,核对最终 URL、方法和正文。
使用 curl --location URL,或简写 curl -L URL,可以跟随 HTTP 重定向。在把返回正文当成预期页面之前,应增加重定向次数上限,并检查最终 URL。默认情况下,curl 只返回初始响应,不会跟随其中的 Location 响应头。
对于采集和地区测试,最终状态成功只说明了一部分情况。重定向链可能结束于登录页、地区首页,或其他主机上一个并非解析器预期的页面。
跟随重定向链,并保留检查依据
将 TARGET_URL 替换为起始地址。下面的 macOS/Linux 命令分别保存响应头和正文,并输出最终目标:
curl --location --max-redirs 5 --max-time 30 \
--silent --show-error --dump-header redirect-headers.txt \
--output response.html \
--write-out 'final=%{url_effective} status=%{http_code} redirects=%{num_redirects}\n' \
'TARGET_URL'响应头文件包含重定向链中遇到的响应。将每个状态与对应的 Location 值一起检查,然后查看最终正文。响应头文件可能包含 Cookie,分享前应移除敏感字段。Windows 请使用 curl.exe,并将命令写在一行。
如果只想检查初始响应而不跟随重定向,可以省略 --location。--head 会发起 HEAD 请求,并不只是隐藏 GET 请求的正文。如果服务器对 HEAD 的处理不同,应使用 GET 并丢弃其正文。详见 curl 重定向指南。
检查 POST 重定向后的请求方法

素材图记录了 curl 8.7.1 在本地运行 -L --data demo=1 的结果:302 后使用 GET;307 保留 POST 和请求正文。
响应 | 对使用 |
|---|---|
301 或 302 | 默认将后续请求改为 GET |
303 | 后续请求通常改为 GET |
307 或 308 | 保留请求方法和正文 |
不要仅仅为了发送数据而添加 -X POST。显式指定的自定义方法,可能产生与 curl 正常自动选择方法时不同的重定向行为。只有目标 API 的约定要求时,才使用保留请求方法的选项,并在接收端验证结果。
重定向到其他主机时,应把它当成新的目标。默认情况下,curl 会限制标准鉴权凭据跨主机转发;--location-trusted 会放宽这项保护。X-API-Key 等自定义敏感请求头尤其需要谨慎。请检查目标;如果凭据只属于一个源站,就不要盲目跟随重定向。
在本地比较不跟随、跟随和循环重定向
启动随附的 network_demo.py。它的路由返回可控响应,不会发起外部请求。
curl --noproxy '*' --silent --output /dev/null \
--write-out '%{http_code}\n' 'http://127.0.0.1:8766/redirect'
curl --noproxy '*' --silent --location --max-redirs 5 \
'http://127.0.0.1:8766/redirect'
curl --noproxy '*' --location --max-redirs 2 \
'http://127.0.0.1:8766/loop'第一条命令输出 302。第二条到达 /headers 的 JSON 响应。第三条因为重定向次数超过上限而停止,curl 退出码为 47。这些本地结果可以帮助你区分循环重定向和连接超时。
同一演示服务还提供 /redirect302 和 /redirect307,用于比较 POST 行为。配合 --location 发送 --data 'demo=1':前者的最终响应报告 GET,后者报告 POST。此测试只使用演示数据。
保持重定向链中的路由条件一致
通过 MIYAIP 测试时,在相同命令中添加分配的代理字段。确认适用的会话策略,并检查每个目标。代理端点字符串保持不变,本身不能证明轮换服务在每次请求中都维持同一出口。
比较直连和代理结果时,应使用相同的起始 URL、方法、请求头和重定向策略。差异可能来自语言、地区、鉴权或目标规则,不能自动认定为代理故障。浏览器中的 IP 与浏览器环境检查报告该浏览器观察到的结果,而 curl 是另一种客户端。
如果设置了明确的客户端标识,先设置并验证 cURL User-Agent,再在比较重定向行为时保持该值不变。
对于采集程序,导出前应验证最终内容类型和必需字段。状态为 200 的登录页不应被保存为成功获取的商品记录。如果重定向只能进入一组已知主机,应显式检查并跟随获准的步骤;数字形式的重定向次数限制并不等于主机白名单。
常见问题
常见问题
curl 会跟随 JavaScript 重定向吗?
不会。--location 跟随的是 HTTP 重定向,不执行 JavaScript,也不提供浏览器运行环境。任务需要执行页面代码时,应选择浏览器工作流程。
可以使用 --max-redirs 0 吗?
启用重定向跟随后,将上限设置为零会阻止跟随任何重定向。如果遇到意外重定向时就应停止请求,这项设置很有用。
为什么最终状态是 200,却没有想要的数据?
请检查最终 URL、响应类型和正文。重定向链可能到达了其他页面。除了传输成功,还需要验证内容。
为请求选择合适的工作流程
HTTP 请求可以参考代理连接示例;如果任务需要的能力超出重定向处理,应比较浏览器和 API 路线。
下一步
运行本地演示
下载 network_demo.py,或将以下完整代码保存为同名文件。使用 Python 3 运行 python network_demo.py。服务仅监听 127.0.0.1:8766,使用完按 Ctrl+C 退出。
"""Local HTTP demonstration. No external requests, credentials or MIYAIP service calls."""
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlsplit
import json
import time
class DemoHandler(BaseHTTPRequestHandler):
def log_message(self, *_):
pass
def reply(self, code, body=b"", headers=None):
self.send_response(code)
for key, value in (headers or {}).items():
self.send_header(key, value)
self.send_header("Content-Length", str(len(body)))
try:
self.end_headers()
self.wfile.write(body)
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
pass
def do_CONNECT(self):
self.reply(407, headers={"Proxy-Authenticate": 'Basic realm="local-demo"'})
def do_GET(self):
path = urlsplit(self.path).path
if path == "/redirect":
return self.reply(302, headers={"Location": "/headers"})
if path == "/loop":
return self.reply(302, headers={"Location": "/loop"})
if path == "/slow":
time.sleep(2)
if path in {"/headers", "/slow"}:
payload = {"method": self.command, "headers": dict(self.headers)}
return self.reply(200, json.dumps(payload, indent=2).encode(),
{"Content-Type": "application/json"})
self.reply(404, b"Use /headers, /redirect, /loop or /slow")
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", "0")))
if urlsplit(self.path).path == "/redirect307":
return self.reply(307, headers={"Location": "/headers"})
if urlsplit(self.path).path == "/redirect302":
return self.reply(302, headers={"Location": "/headers"})
payload = {"method": self.command, "headers": dict(self.headers),
"body": body.decode("utf-8", errors="replace")}
self.reply(200, json.dumps(payload, indent=2).encode(),
{"Content-Type": "application/json"})
if __name__ == "__main__":
print("Local demonstration: http://127.0.0.1:8766 (Ctrl+C to stop)", flush=True)
ThreadingHTTPServer(("127.0.0.1", 8766), DemoHandler).serve_forever()