Menu

Python HTTP 请求:使用 requests 库(GET、POST、JSON)

如何用 requests 库在 Python 中发送 HTTP 请求:GET、POST、查询参数、请求头、JSON 请求体,以及错误处理。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

几行代码从网上获取数据

大多数真实的 Python 程序迟早都要通过网络与别的服务通信:REST API、天气服务、GitHub 接口、文件下载。标准库可以做到这些(通过 urllib),但 Python 世界里事实上的标准工具是一个叫 requests 的第三方库。它的 API 友好太多了,值得你执行一次 pip install。

pip install requests

如果你还没有在虚拟环境中工作,先创建一个,这样安装的库只作用于你的项目。

第一个 GET 请求

三行代码:调用 get,检查状态码,查看响应体。response.text 是字符串形式的响应体。这里做了截断,因为完整的 JSON 很长。

值得像速查表一样记住的状态码:

  • 200:OK,一切正常。
  • 201:已创建(常见的 POST 响应)。
  • 301 / 302:重定向;requests 默认会自动跟随。
  • 400:错误请求;你发送的内容有问题。
  • 401 / 403:未认证 / 无权限。
  • 404:资源不存在。
  • 429:请求过于频繁,被限流了,请放慢速度。
  • 500:服务器错误。

解析 JSON 响应

当接口返回 JSON 时,对响应调用 .json(),它会解析响应体并返回一个字典(或列表):

在底层,.json() 和 json.loads(response.text) 是一样的,只是针对常见情况的快捷方式。

发送查询参数

不要手动把 ?key=value&... 拼接到 URL 上。把一个字典传给 params=:

requests 会替你处理 URL 编码,空格、特殊字符、Unicode 都能安全处理。

实际发出的 URL 可以通过 response.url 查看,调试时很方便。

带 JSON 请求体的 POST 请求

要发送数据,比如创建资源、提交表单、调用会修改数据的接口,就用 requests.post:

import requests

payload = {
    "title": "Docs update",
    "body": "Added HTTP requests page.",
    "labels": ["docs"],
}

response = requests.post(
    "https://api.example.com/issues",
    json=payload,
    headers={"Authorization": "Bearer YOUR_TOKEN"},
)

print(response.status_code)
print(response.json())

json= 参数做了两件事:把 payload 序列化为 JSON,并设置 Content-Type: application/json。你也可以用 data=json.dumps(payload) 加上显式的请求头手动完成,但 json= 是惯用的快捷写法。

如果要发送表单编码的数据(传统 HTML 表单发送的那种),改用 data=:

requests.post("https://example.com/login", data={"user": "rosa", "password": "..."})

请求头

以字典的形式传入任意自定义请求头:

import requests

response = requests.get(
    "https://api.example.com/profile",
    headers={
        "Authorization": "Bearer abc123",
        "User-Agent": "my-tool/1.0",
    },
)

大多数 API 都要求用 Authorization 请求头进行认证。具体的认证方案(Bearer、Basic、Token)请查看它们的文档。

超时设置不是可选项

默认情况下,requests 会一直等待响应。在真实的程序中,这会让一个不稳定的服务器把你的脚本卡死。一定要传入 timeout:

import requests

try:
    response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
    print("Server took too long.")

这个数字的单位是秒。timeout=5 表示“5 秒内没收到响应就放弃”。想要更精细的控制,可以传入一个元组 (connect_timeout, read_timeout)。

错误处理

可能出现两类问题:

  1. HTTP 层面的错误(4xx、5xx):服务器响应了,但响应是一个错误。response.status_code 会告诉你。
  2. 网络层面的问题:超时、DNS 解析失败、主机不可达。这些会抛出异常。

惯用的写法把两者结合起来:

对于 2xx 响应,raise_for_status() 什么也不做,否则就抛出异常。RequestException 是 requests 抛出的所有错误的基类,捕获它就能一次覆盖“与这个接口通信时出的任何问题”。

下载文件

对于大型二进制文件的下载,以流的方式读取响应,避免它全部留在内存中:

import requests

url = "https://example.com/large.zip"

with requests.get(url, stream=True, timeout=30) as r:
    r.raise_for_status()
    with open("large.zip", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)

stream=True 告诉 requests 不要预先加载响应体。iter_content(chunk_size=...) 一次产出一块响应体,你直接把它写到磁盘上。

会话:复用连接和默认设置

如果你要向同一个服务发送多个请求,就用 Session。它会复用底层的 TCP 连接(更快),还能让你一次性设置默认值:

import requests

session = requests.Session()
session.headers.update({"Authorization": "Bearer abc123"})

# Every request through this session carries the header.
a = session.get("https://api.example.com/users/1")
b = session.get("https://api.example.com/users/2")
c = session.post("https://api.example.com/users", json={"name": "Rosa"})

对于要几十次调用同一个 API 的脚本,使用会话能明显提速。

一个贴近实际的例子:简易 GitHub 客户端

获取某个仓库的最新发布版本:

import requests

def latest_release(owner, repo):
    url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    data = response.json()
    return {
        "tag": data["tag_name"],
        "name": data["name"],
        "published": data["published_at"],
        "url": data["html_url"],
    }

release = latest_release("python", "cpython")
print(release)

不到十五行代码:构建 URL、发送请求、检查错误、提取你关心的字段。你将来写的大多数 API 客户端都是这个样子。

那 urllib 呢?

标准库的 urllib.request 能做到 requests 能做的一切,只是代码更多、用起来更别扭。如果你确实无法添加依赖,可以用它:

import json
import urllib.request

with urllib.request.urlopen("https://api.github.com/repos/python/cpython") as r:
    data = json.loads(r.read().decode("utf-8"))

print(data["name"])

只要不是临时写的小脚本,requests(需要异步时用 httpx)都值得安装。

几个好习惯

  • 始终设置超时。 没有例外。
  • 在预期请求会成功的脚本中使用 raise_for_status(),它会把错误的响应变成一个醒目的异常。
  • 把字典传给 params= 和 json=,不要手动拼接字符串。
  • 频繁调用同一个 API 时,把相关调用放进一个 Session。
  • 调试时记录 response.status_code 和 response.text,响应体通常会准确告诉你服务器不满意的地方。

下一篇:日期和时间

有了 requests 这件工具,你就可以与任何现代 Web API 通信、下载文件,并在服务之间构建小型集成。结合前面的 JSON 和 CSV 页面,你现在已经掌握了完整的“获取数据、读取数据、处理数据、写回数据”流程,大量真实的 Python 脚本都是这个结构。不过,这些数据大多带有时间戳,下一页将介绍 Python 如何表示日期和时间,以及值得避开的时区陷阱。

常见问题

Python 中怎么发送 HTTP 请求?

用 pip install requests 安装 requests 库,然后调用 requests.get(url) 发送 GET 请求,或调用 requests.post(url, json=...) 发送 POST 请求。响应对象有 .status_code、.text、.json() 和 .headers。例如:r = requests.get('https://api.example.com/users/1')。

应该用 requests 还是 urllib?

凡是要自己手写的请求都用 requests,它的 API 友好得多。urllib 是内置的,在无法添加依赖时也够用,但处理 JSON 请求体和会话等功能需要写更多代码。在生产环境中,很多团队还会使用 httpx(一个兼容 requests、支持异步的库)。

Python 中怎么在 POST 请求里发送 JSON?

把 json={'key': 'value'} 传给 requests.post(...)。requests 会把字典序列化成 JSON,并替你设置 Content-Type: application/json。不要同时传 data= 和 json=,二选一。

用 requests 库时怎么处理错误?

检查 response.status_code(200 表示成功),或者调用 response.raise_for_status(),在遇到 4xx/5xx 时抛出异常。网络层面的问题(超时、DNS 解析失败)会抛出 requests.RequestException 的子类,捕获它就能同时覆盖这两种情况。

Coddy 编程语言插图

和 Coddy 一起学编程

开始