Python Requests请求头教程指南:GET、POST与User-Agent
2026年7月22日 · 教程 · 18 分钟阅读
在Python Requests中,请求头以字典形式传给 requests.get()、requests.post() 等方法的 headers 参数即可。若多个请求需要复用同一组值,可以通过 Session.headers.update() 配置到会话中。本文会从一个简单的GET请求讲起,依次说明POST请求头、自定义User-Agent、Session默认请求头、请求头检查方式,以及403问题的排查思路。
文中的示例面向调用JSON API,或在获得许可后采集网页数据的开发者。读完后,你可以分别测试单次请求和Session级别的请求头,复现一个可控的403响应,并判断修改请求头是否真的与请求失败有关。
快速结论
要为单次请求添加请求头,把字典传给
headers即可,例如:requests.get(url, headers={"Accept": "application/json"})。当接口要求客户端表明身份时,可在该字典中加入"User-Agent"。需要长期复用的默认值应放在session.headers.update(...)中。
要核对实际结果,请查看response.request.headers,它表示发出的请求头;response.headers则是服务器返回的响应头。
Python Requests中的请求头是什么?
HTTP请求头是随请求或响应一起传输的键值对,用来承载不适合放进消息正文的信息。在Python Requests中,发出的请求头通常以字典形式,通过 headers= 传给 requests.get()、requests.post() 等方法;服务器返回的响应头可通过 response.headers 读取。
为什么要使用Python Requests请求头?
请求头能帮助服务器识别:客户端希望获得JSON还是HTML、请求体采用何种编码、请求携带了哪些凭据,以及请求来自哪个应用。常见字段包括 Accept、Content-Type、Authorization 和 User-Agent。填写准确的值不仅能让服务器正确理解请求,也更便于定位API调用失败的原因。
单次请求使用 headers=;多个请求需要共享默认值时,使用 requests.Session。如果想确认Requests最终准备并发出的内容,请检查 response.request.headers。在配置GET、User-Agent、POST和Session请求头前,先搭建一个可重复使用的本地测试环境。
准备Python Requests测试环境
安装依赖
创建虚拟环境,并在其中安装固定版本的依赖:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install requests==2.34.2 PySocks==1.7.1
PySocks 只在后文可选的SOCKS代理示例中需要;基础请求头示例只依赖Requests。
| 组件 | 已验证版本 | 用途 |
|---|---|---|
| Python | 3.14.0 | 运行示例 |
| Requests | 2.34.2 | 发送和检查HTTP请求 |
| PySocks | 1.7.1 | 支持 socks5h:// 代理URL |
| 测试目标 | 本地HTTP回显服务器 | 无需依赖第三方服务,也能得到可重复的测试结果 |
这个演示会在 127.0.0.1 启动一台服务器,接收每个请求并输出提交的值;测试结束后可直接在本机关闭。整个过程不会采集外部网站的数据。

启动本地测试服务器
下面所有示例都会访问你自己电脑上的可用URL。请将以下代码保存为 code/local_echo_server.py:
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class Handler(BaseHTTPRequestHandler):
def reply(self, status=200):
length = int(self.headers.get("Content-Length", "0"))
raw_body = self.rfile.read(length) if length else b""
parsed_json = None
body_text = None
if raw_body:
body_text = raw_body.decode("utf-8", errors="replace")
try:
parsed_json = json.loads(raw_body)
except json.JSONDecodeError:
pass
payload = {
"method": self.command,
"path": self.path,
"headers": dict(self.headers),
"json": parsed_json,
"body_text": body_text,
}
body = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
self.reply(403 if self.path == "/blocked" else 200)
def do_POST(self):
self.reply()
def log_message(self, format, *args):
return
server = ThreadingHTTPServer(("127.0.0.1", 8765), Handler)
print("Local test server: http://127.0.0.1:8765", flush=True)
print("Press Control-C to stop.", flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
这个回显服务器会在请求体是合法JSON时返回解析后的JSON;如果请求体不是JSON,则将其以文本形式保留。因此,你既可以用它测试 json=,也可以测试 data=。
在第一个终端窗口运行下面的命令,并保持窗口开启:
python3 code/local_echo_server.py

如何配置Python Requests请求头
创建请求头字典
HTTP请求头会在请求体之前传递元数据。它可以说明客户端可接收的数据格式、正在发送的请求体媒体类型、认证信息、缓存偏好,或发起请求的软件信息。
Requests通常使用普通字典表示这些值:
import requests
url = "http://127.0.0.1:8765/headers"
headers = {
"User-Agent": "inventory-client/2.1",
"Accept": "application/json",
}
response = requests.get(url, headers=headers, timeout=(2, 5))
response.raise_for_status()
print(response.status_code)
HTTP请求头名称不区分大小写,但统一采用首字母大写的写法,代码会更易读。请求头的值应为字符串。不要手动设置 Content-Length,Requests会根据编码后的请求体自动计算它。
还要注意,有些专用参数的优先级会高于 headers 中的同名值。例如 auth= 可能替换 Authorization;代理URL中携带的凭据也可能替换 Proxy-Authorization。
为GET请求添加请求头
以下是为单次Python Requests GET请求添加请求头的最小可运行示例:
import requests
url = "http://127.0.0.1:8765/headers"
headers = {
"User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)",
"Accept": "application/json",
"X-Trace-ID": "items-read-001",
}
response = requests.get(url, headers=headers, timeout=(2, 5))
response.raise_for_status()
received = response.json()
print("Status:", response.status_code)
print("Method:", received["method"])
print("User-Agent:", received["headers"]["User-Agent"])
print("X-Trace-ID:", received["headers"]["X-Trace-ID"])
这里的 Accept 只是告诉服务器:客户端希望获得JSON;它不能强制服务器一定返回JSON。调用 response.json() 前,应先确认请求成功。对于响应格式不稳定的接口,还应确认返回的 Content-Type 符合预期。
每次请求都应设置 timeout。元组中的第一个数值限制Requests建立连接时的等待时间;第二个数值限制它在两段响应数据之间最多等待多久。请注意,这一设置并不保证整个下载一定会在两个数值相加的秒数内完成。

在Python Requests中设置User-Agent
Requests默认会使用库自身的 User-Agent,例如 python-requests/2.34.2。当接口要求客户端标识自身时,建议为应用设置清晰、可识别的值:
import requests
url = "http://127.0.0.1:8765/headers"
response = requests.get(
url,
headers={"User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)"},
timeout=(2, 5),
)
response.raise_for_status()
print(response.json()["headers"]["User-Agent"])
对API运营方而言,包含产品名称、版本号以及联系或说明页面的User-Agent,通常比从其他客户端照搬的浏览器字符串更有用。若服务方规定了User-Agent格式,应按其要求填写。
修改Python Requests的User-Agent,并不会让客户端变成Chrome。Requests依旧不会执行JavaScript、复现浏览器的TLS行为、管理浏览器客户端提示(client hints),也不会自动完成认证挑战。照搬浏览器的值甚至可能与请求的其他部分相互矛盾。因此,应把User-Agent视为客户端标识,而不是解决403的通用办法。
可选:通过SOCKS5代理发送获授权请求
安装 PySocks 后,Requests可以使用 socks5h:// 代理URL。将代理地址和凭据保存到环境变量,而不是写入代码;socks5h 会让域名解析在代理一侧进行。以下示例仅适用于已获目标服务许可的API调用或数据访问任务:
import os
import requests
proxy_url = os.environ["ROLA_SOCKS5_PROXY"]
response = requests.get(
"https://api.example.com/v1/items",
headers={
"User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)",
"Accept": "application/json",
},
proxies={"http": proxy_url, "https": proxy_url},
timeout=(2, 5),
)
response.raise_for_status()
print(response.json())
例如,可在受控的本地环境中将 ROLA_SOCKS5_PROXY 配置为 socks5h://username:password@proxy.example:1080。代理只决定网络路径,不能授予访问权限,也不能替代接口认证、访问政策或速率限制;发送前应确认目标服务允许该工作流。
正确发送Python Requests POST请求头
添加POST请求头和JSON请求体
调用JSON API时,请通过 json= 传入Python对象:
import requests
url = "http://127.0.0.1:8765/items"
headers = {
"User-Agent": "inventory-client/2.1 (+https://rola-ip.co/)",
"Accept": "application/json",
"X-Trace-ID": "item-create-001",
}
payload = {"name": "monitor", "quantity": 2}
response = requests.post(
url,
headers=headers,
json=payload,
timeout=(2, 5),
)
response.raise_for_status()
received = response.json()
print("Status:", response.status_code)
print("Method:", received["method"])
print("User-Agent:", received["headers"]["User-Agent"])
print("Content-Type:", received["headers"]["Content-Type"])
print("JSON received:", received["json"])
使用 json=payload 时,Requests会序列化该对象,并添加恰当的JSON Content-Type。通常不要先用 json.dumps() 把对象转成字符串,再将该字符串传给 json=;那样编码的是一个JSON字符串,而不是原本的对象结构。
如果接口需要表单字段,或需要已编码的请求体,应使用 data=。正确的编码方式取决于接口契约,而不是单纯取决于HTTP方法。
| 服务器期望的数据 | Requests参数 | 常见的自动生成 Content-Type |
|---|---|---|
| JSON对象 | json={"key": "value"} |
application/json |
| HTML表单字段 | data={"key": "value"} |
application/x-www-form-urlencoded |
| 原始字节或文本 | data=encoded_body |
按API契约设置 |
| 文件上传 | files={...} |
multipart/form-data; boundary=... |

复用并检查Python Requests请求头
通过Session复用请求头
requests.Session 会在多次调用之间保存默认请求头和Cookie,并能复用到同一主机的连接。因此,它很适合小型API客户端,或经过授权的多页面采集任务:
import requests
with requests.Session() as session:
session.headers.update(
{
"User-Agent": "inventory-client/2.1",
"Accept": "application/json",
}
)
response = session.get(
"http://127.0.0.1:8765/headers",
headers={"X-Trace-ID": "session-001"},
timeout=(2, 5),
)
response.raise_for_status()
received = response.json()
print("Status:", response.status_code)
print("User-Agent:", received["headers"]["User-Agent"])
print("X-Trace-ID:", received["headers"]["X-Trace-ID"])
Requests会将单次调用的请求头字典与Session默认请求头合并。同一个键同时出现时,该次请求中传入的值会覆盖Session默认值。不要在并发线程间随意共享可变的Session,因为Cookie等状态会变得难以预测;应让一个Session只由一个工作单元使用,或做好并发访问保护。

检查请求头与响应头
不要默认认为原始字典会原样发出,而应检查已准备好的请求:
import requests
response = requests.get(
"http://127.0.0.1:8765/headers",
headers={"User-Agent": "inspection-client/1.0"},
timeout=(2, 5),
)
response.raise_for_status()
print("Method:", response.request.method)
print("URL:", response.request.url)
print("User-Agent sent:", response.request.headers["User-Agent"])
print("Response Content-Type:", response.headers["Content-Type"])
response.headers 与 response.request.headers 的含义不同:
| 表达式 | 包含的内容 |
|---|---|
response.request.headers |
客户端实际发出的请求头 |
response.headers |
服务器返回的响应头 |
session.headers |
Session中配置的默认值 |
response.history |
重定向过程中产生的早期响应 |
生产环境中不要不加筛选地打印所有请求头。Authorization、Cookie、Proxy-Authorization、API密钥和自定义令牌都可能泄漏到日志。只输出安全字段,或先对敏感值进行脱敏。

理解请求头的优先级
Requests会根据多个输入构建最终请求。以下三条规则可以解释许多看似反常的结果:
- 对于Requests支持的认证机制,优先使用
auth=,而不是手动拼接Authorization。从.netrc读取的凭据,或通过auth=提供的凭据,都可能覆盖headers中的Authorization值。 - 代理凭据应放在配置好的代理URL中,或按照代理服务商文档提供的认证流程配置。Requests可能会替换手动填写的
Proxy-Authorization。 - 让Requests自行计算
Content-Length。如果它能知道请求体长度,手写的值可能会被替换。
当重定向跳转到另一台主机时,Requests还会移除Authorization。这是安全措施,并不说明原始字典被忽略了。排查重定向问题时,应结合 response.history 和已准备好的请求头一起查看。
排查Python Requests请求头故障
修改请求头前,先诊断状态码
先检查状态码、响应正文、最终URL、重定向历史,以及已准备请求中安全的那部分请求头。给每个失败请求都盲目添加更多浏览器风格字段,往往只会掩盖真正的问题,而不能解决它。
| 现象 | 可能原因 | 如何验证 | 实用处理方法 |
|---|---|---|---|
400 Bad Request |
语法、请求头值或请求体结构无效 | 阅读API错误正文,并与接口schema对照 | 修正指定字段或编码方式 |
401 Unauthorized |
凭据缺失、过期或被覆盖 | 检查认证方案和可安全查看的令牌元数据 | 刷新凭据;支持时使用 auth= |
403 Forbidden |
权限、策略、限速、IP信誉或反自动化控制 | 对照账号权限、文档限制、响应正文及允许使用的网络 | 申请权限、降低频率,或使用获准的网络路径 |
406 Not Acceptable |
Accept 值不被支持 |
查看支持的响应格式 | 发送文档规定的媒体类型 |
415 Unsupported Media Type |
请求体编码与 Content-Type 不一致 |
同时检查已准备的请求体和请求头 | 正确使用 json=、data= 或 files= |
429 Too Many Requests |
超出速率限制 | 检查 Retry-After 和服务商配额请求头 |
加入随机抖动后退避,并降低并发 |
| 连接/读取超时 | 网络、代理或上游服务过慢 | 分别测试DNS、直连和代理路径 | 设置有上限的超时,只重试安全操作 |
遇到403时,先确认URL和认证信息,再查看网站的访问政策并降低请求频率。自定义User-Agent可能满足某些接口的客户端标识要求,但它不能赋予访问权限。在不了解来源的情况下加入 Referer、复制Cookie,或添加浏览器专用的 Sec-CH-UA-* 字段,反而可能让请求前后不一致。
重试同样需要谨慎。重复GET通常是安全的;自动重复POST则可能创建重复记录,除非API支持幂等键(idempotency key)。存在 Retry-After 时应遵从它,并为指数退避设置上限。
下面的脚本会复现一个已知403,但不会过早调用 raise_for_status(),从而保留已准备的请求,方便进一步诊断:
import requests
response = requests.get(
"http://127.0.0.1:8765/blocked",
timeout=(2, 5),
)
print("Status:", response.status_code)
print("URL:", response.request.url)
print("User-Agent sent:", response.request.headers["User-Agent"])
if response.status_code == 403:
print("Diagnosis: controlled 403 reproduced; inspect policy and permissions.")

使用Rola IP处理网络路由与IP限制
如果已获授权的请求在一个网络中可用,却从共享服务器IP反复失败,限制因素可能是IP信誉、地理位置或单IP限速规则,而不是Python Requests的请求头字典。经过认证的代理可以独立处理网络路径,而应用请求头仍负责描述请求本身。
Rola IP提供住宅代理、动态数据中心代理和移动代理网络。数据中心代理适合需要稳定性和速度的API调用及批量任务;住宅代理通过家庭网络出口转发;移动代理则适合确实需要移动网络出口的工作流。选择何种类型,应依据目标服务的访问规则、所需位置和工作负载规模决定。
在Rola IP控制台中打开Proxy Setup,然后选择地区、认证方式、协议和账户。页面会提供连接所需的主机、端口、用户名与密码。请把这些值保存在环境变量或密钥管理器中,不要把有效凭据直接写入源代码。控制台配置流程可参考官方的代理连接信息快速入门。

当多次请求必须保持同一个出口IP时,选择粘性会话;当独立请求应通过不同出口发出时,使用按请求轮换。位置参数用于将获准流量路由到工作流所需的国家或地区。
需要明确区分两个层面:请求头描述的是应用层请求,代理改变的是网络路径。新的IP无法修正无效认证或错误的 Content-Type;同样,换一个User-Agent也无法解决IP信誉或地理位置限制。
代理路由不能替代授权,也不能免除与robots指令、合同、隐私和速率限制相关的义务。它同样不能渲染JavaScript。只有当页面确实需要浏览器,且该工作流已获允许时,才应使用浏览器自动化。
Python Requests请求头最佳实践
- 当服务要求客户端表明身份时,使用具体且真实的User-Agent。
- 让
Accept和请求体编码与API文档保持一致。 - 发送JSON时优先使用
json=,不要手动拼接请求体和长度。 - 同时设置连接超时和读取超时。
- 在保留所需诊断响应后,再调用
raise_for_status()。 - 调试时检查
response.request.headers,并对敏感信息脱敏。 - 对相关的顺序请求复用同一个Session。
- 只重试适合重试的方法,限制退避时长,并遵从
Retry-After。 - 将令牌和代理凭据保存在环境变量或密钥管理器中。
- 遵守访问条款、隐私规则、适用的robots指令和公开的速率限制。
总结
单次请求使用 headers 字典,多个请求共享默认值时使用Session。让Requests负责JSON编码和与请求体相关的请求头计算;遇到意外结果时,检查已准备的请求。
如果经许可的工作流受网络信誉或地理位置限制,而非请求头限制,经过认证的Rola IP路由可以处理这一独立层面。同时仍应设置合理超时、控制请求频率、妥善保管凭据,并遵循目标服务的访问规则。