开发者指南

Python Requests 请求头的设置与验证

用 Requests 发送自定义请求头,区分请求与响应字段,正确处理 JSON Content-Type 和代理鉴权,并验证服务器接收值。

发出请求头、服务器观测的 User-Agent 与收到的响应头

Python Requests 请求头的设置与验证

操作流程

  1. 显式发送请求头

    配置受控请求,检查准备发送的请求头。

  2. 在接收端验证

    对比回显结果和响应头,区分目标服务与代理凭据。

通过 headers= 传入字典,即可使用 Python Requests 发送请求头。用 response.request.headers 检查发出的请求,用 response.headers 检查收到的响应。这两个对象对应一次 HTTP 交互的不同方向。

请求头可以声明期望的响应格式、标识应用或向 API 提供鉴权信息。它们不会改变来源 IP、执行 JavaScript,也不能保证网站接受自动化请求。

显式设置并发送请求头

使用 python -m pip install requests 安装 Requests。启动随附的 network_demo.py,再运行下面的本地示例。演示端点会回显它收到的请求头。

import requests

headers = {
    "User-Agent": "MiyaIP-Tutorial/1.0",
    "Accept": "application/json",
}
with requests.Session() as session:
    session.trust_env = False  # Isolate this loopback demonstration.
    response = session.get(
        "http://127.0.0.1:8766/headers", headers=headers, timeout=(1, 3)
    )
    response.raise_for_status()
    print("sent_user_agent:", response.request.headers.get("User-Agent"))
    print("received_type:", response.headers.get("Content-Type"))
    observed = response.json()["headers"]
    print("server_saw:", observed.get("User-Agent"))

本地输出中,发出的 User-Agent 为 MiyaIP-Tutorial/1.0,响应类型为 application/json,服务器观察到的 User-Agent 也是同一教程标识。请只对比用于诊断的非敏感字段,不要将已鉴权的生产请求的全部请求头打印出来。

发出请求头、服务器观测的 User-Agent 与收到的响应头
本地回显示例区分发出字段、服务器观测结果与响应头。

根据接收服务选择请求头

请求头

表达的含义

常见误用

Accept

希望收到的响应格式

认为客户端请求 JSON 后,服务器就一定返回 JSON

Content-Type

正在发送的请求体格式

实际发送表单编码内容,却声明为 JSON

User-Agent

由客户端提供的应用标识

把看起来像浏览器的字符串视为完整浏览器环境

Authorization

目标服务的鉴权凭据

填入代理密码,或将 Token 发给错误的主机

Accept-Language

语言偏好

认为它会改变网络出口所在国家

HTTP 字段名不区分大小写,但使用约定俗成的拼写更便于阅读。字段值仍需遵循各自的语法。Requests 快速入门说明了自定义请求头及其优先级。

让请求体格式与 Content-Type 一致

发送 JSON 请求体时使用 json=。Requests 会序列化对象,并设置对应的 JSON Content-Type,除非你自行覆盖该请求头。以下本地示例仅发送演示数据:

import requests

with requests.Session() as session:
    session.trust_env = False
    session.headers.update({"User-Agent": "MiyaIP-Tutorial/1.0"})
    response = session.post(
        "http://127.0.0.1:8766/headers",
        json={"demo": 1},
        headers={"Accept": "application/json"},
        timeout=(1, 3),
    )
    response.raise_for_status()
    print(response.request.headers.get("Content-Type"))
    print(response.json()["method"])

该示例会输出 application/json 和 POST。相比之下,通过 data= 传入字典通常会创建表单请求体。只有确定 Content-Type 与实际发送内容一致时,才手动设置它。

Session 可以为多个请求设置通用请求头,单次请求中的值可以覆盖这些默认值。鉴权信息应限定于预期的目标服务;不要将密钥放入被广泛复用的 Session,再把它发往无关服务。

将目标请求头与代理凭据分开

连接 MIYAIP 代理时,请按连接指南通过 proxies 参数配置代理 URL 和凭据。不要把代理密码作为普通的 Authorization 请求头发给目标网站。MCP 密钥和 Crawler API Token 同样只属于各自的服务及其鉴权方式。

异常结果

检查内容

请求 JSON 却收到 HTML

响应状态、实际 Content-Type、最终 URL,以及端点是否支持 JSON

请求头值与字典不同

Session 默认值、鉴权配置、准备后的请求及重定向

HTTP 401

目标服务的鉴权要求

HTTP 407

代理鉴权,需与目标请求头独立排查

HTTP 403

响应中的说明和允许的访问方式;单改 User-Agent 并不是通用修复方法

连接或 TLS 错误

先诊断连接,再调整内容协商请求头

发生重定向时,response.request 描述最终请求。若需检查更早的请求,请查看 response.history 中的记录。只检查诊断必需的字段,不要将 Cookie 和 Bearer Token 写入共享日志。

常见问题与下一步

常见问题

response.headers 是客户端发出的请求头吗?

不是。它包含的是响应头。请用 response.request.headers 检查关联的发出请求,或使用自己控制的回显端点查看接收服务器实际观察到的字段。

Accept-Language 会选择代理国家吗?

不会。它声明语言偏好。代理位置应通过产品支持的连接配置选择。

请求头可以让 Requests 执行 JavaScript 吗?

不可以。任务需要执行 JavaScript 或进行界面交互时,应选择浏览器工作流。

分别验证请求配置与网络路由

先确认响应符合预期。只有任务需要代理的路由或出口行为时,再配置代理。

下一步

运行本地演示

下载 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()

来源