Python requests库完全指南:从API调用到会话管理与自动重试(2026)

📝 835 字 · ☕ 3 分钟阅读

前言:为什么所有Python程序员都在用requests?

如果你写过Python,一定用过requests。Python标准库有urllib,但它用起来是这样的:

# urllib — 只是为了发一个带Header的GET请求
import urllib.request
req = urllib.request.Request(
    'https://api.example.com/data',
    headers={'Authorization': 'Bearer xxx'}
)
with urllib.request.urlopen(req) as resp:
    data = resp.read().decode('utf-8')

用requests呢?

import requests
resp = requests.get(
    'https://api.example.com/data',
    headers={'Authorization': 'Bearer xxx'}
)
data = resp.json()

少了5行代码,少了3个import。这就是requests——「HTTP for Humans」。

但大多数人的requests止步于.get().post()。今天从基础到生产级,一次讲透。

第一步:安装和第一个请求

pip install requests
import requests

# 最基础的GET
resp = requests.get('https://api.github.com')
print(resp.status_code)  # 200
print(resp.headers['Content-Type'])  # application/json; charset=utf-8
print(resp.json()['current_user_url'])  # https://api.github.com/user

resp.json() 是requests最常用的方法之一——直接把响应体解析为Python字典。不需要 import json,不需要 json.loads()

第二步:HTTP方法速查

# GET — 获取资源
requests.get('https://api.example.com/users')

# POST — 创建资源
requests.post('https://api.example.com/users', json={'name': '张三'})

# PUT — 替换资源(全量更新)
requests.put('https://api.example.com/users/1', json={'name': '张三'})

# PATCH — 修改资源(部分更新)
requests.patch('https://api.example.com/users/1', json={'name': '张三'})

# DELETE — 删除资源
requests.delete('https://api.example.com/users/1')

# HEAD — 只获取响应头(不要响应体)
requests.head('https://api.example.com/users')

第三步:传递数据的四种方式

1. Query String(URL参数)

# 自动URL编码
resp = requests.get(
    'https://api.example.com/search',
    params={'q': 'python教程', 'page': 1, 'size': 20}
)
# 实际请求: /search?q=python%E6%95%99%E7%A8%8B&page=1&size=20

2. JSON请求体(最常用)

resp = requests.post(
    'https://api.example.com/users',
    json={
        'name': '张三',
        'email': 'zhangsan@example.com',
        'role': 'admin'
    }
)
# 自动设置 Content-Type: application/json
# 自动 json.dumps()

3. Form表单(x-www-form-urlencoded)

resp = requests.post(
    'https://api.example.com/login',
    data={'username': 'admin', 'password': 'secret'}
)
# Content-Type: application/x-www-form-urlencoded

4. 文件上传

with open('/tmp/report.pdf', 'rb') as f:
    resp = requests.post(
        'https://api.example.com/upload',
        files={'file': ('report.pdf', f, 'application/pdf')}
    )

第四步:会话管理(Session)——生产环境必用

如果你对同一个API发了100个请求,每个请求都单独建立TCP连接+SSL握手——慢。用Session,复用连接:

# ❌ 低效 — 每次请求都新建连接
for user_id in range(1, 100):
    resp = requests.get(
        f'https://api.example.com/users/{user_id}',
        headers={'Authorization': 'Bearer xxx'}
    )

# ✅ 高效 — 连接复用
session = requests.Session()
session.headers.update({
    'Authorization': 'Bearer xxx',
    'User-Agent': 'MyApp/1.0'
})

for user_id in range(1, 100):
    resp = session.get(f'https://api.example.com/users/{user_id}')

session.close()  # 用完关闭

Session自动管理Cookie。比如登录后需要保持会话状态:

session = requests.Session()

# 登录(服务端返回Set-Cookie)
session.post('https://api.example.com/login', json={
    'username': 'admin', 'password': 'secret'
})

# 后续请求自动带上Cookie
resp = session.get('https://api.example.com/profile')
# 不需要手动处理Cookie!

第五步:超时与重试——生产环境必修课

永远设超时

# ❌ 危险 — 可能永远等下去
resp = requests.get('https://api.example.com/data')

# ✅ 必须设timeout
resp = requests.get(
    'https://api.example.com/data',
    timeout=10  # 10秒超时(连接+读取)
)

# 精确控制连接超时和读取超时
resp = requests.get(
    'https://api.example.com/data',
    timeout=(3, 30)  # 连接3秒超时,读取30秒超时
)

自动重试

requests本身不自动重试,但可以搭配urllib3的Retry:

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()

retry_strategy = Retry(
    total=3,                    # 最多重试3次
    backoff_factor=0.5,        # 重试间隔 0.5s, 1s, 2s...
    status_forcelist=[429, 500, 502, 503, 504],  # 这些状态码才重试
    allowed_methods=["GET", "POST"]  # 哪些方法允许重试
)

adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)

# 这之后的请求都会自动重试
resp = session.get('https://api.example.com/data', timeout=10)

注意:POST请求慎重重试——如果服务端已经处理了请求但只返回了错误状态码,重试可能导致重复创建资源。幂等的GET/PUT/DELETE可以放心重试。

第六步:错误处理——别让一个API挂了你的程序

import requests
from requests.exceptions import (
    RequestException, Timeout, ConnectionError,
    HTTPError, TooManyRedirects
)

def safe_get(url, **kwargs):
    """永不抛异常的GET请求"""
    try:
        resp = requests.get(url, timeout=10, **kwargs)
        resp.raise_for_status()  # 4xx/5xx → HTTPError
        return resp
    except Timeout:
        print(f"⏱️ {url} 超时了,可能服务器挂了")
    except ConnectionError:
        print(f"🔌 {url} 连不上,检查网络或DNS")
    except HTTPError as e:
        print(f"❌ {url} 返回了 {e.response.status_code}")
    except TooManyRedirects:
        print(f"🔄 {url} 重定向循环了")
    except RequestException as e:
        print(f"💥 {url} 未知错误: {e}")
    return None

# 使用
resp = safe_get('https://api.example.com/data')
if resp:
    process_data(resp.json())

第七步:实战——调用三个真实API

场景1:调用GitHub API获取仓库信息

import requests

def get_github_repo(owner, repo):
    url = f'https://api.github.com/repos/{owner}/{repo}'
    headers = {
        'Accept': 'application/vnd.github.v3+json',
        'User-Agent': 'MyApp/1.0'  # GitHub要求必须带User-Agent
    }
    resp = requests.get(url, headers=headers, timeout=10)
    resp.raise_for_status()
    data = resp.json()
    return {
        'name': data['full_name'],
        'stars': data['stargazers_count'],
        'language': data['language'],
        'description': data['description']
    }

# 调用
info = get_github_repo('psf', 'requests')
print(f"{info['name']}: ⭐{info['stars']:,} / {info['language']}")
# psf/requests: ⭐50,000+ / Python

场景2:带分页的API批量拉取

def fetch_all_pages(base_url, params=None):
    """自动处理分页,拉取所有数据"""
    if params is None:
        params = {}
    params['page'] = 1

    all_items = []
    session = requests.Session()
    session.headers['User-Agent'] = 'MyApp/1.0'

    while True:
        resp = session.get(base_url, params=params, timeout=15)
        resp.raise_for_status()
        data = resp.json()

        if not data:  # 空页 = 没了
            break

        all_items.extend(data)
        params['page'] += 1

    session.close()
    return all_items

场景3:下载大文件(流式)

def download_file(url, dest_path):
    """流式下载大文件,不爆内存"""
    with requests.get(url, stream=True, timeout=300) as resp:
        resp.raise_for_status()

        total_size = int(resp.headers.get('content-length', 0))
        downloaded = 0

        with open(dest_path, 'wb') as f:
            for chunk in resp.iter_content(chunk_size=8192):
                f.write(chunk)
                downloaded += len(chunk)
                if total_size:
                    pct = downloaded / total_size * 100
                    print(f'\r📥 {pct:.1f}%', end='', flush=True)

    print(f'\n✅ 下载完成: {dest_path}')

# download_file('https://example.com/large-file.zip', '/tmp/output.zip')

常见问题

Q: requests和httpx有什么区别?该用哪个?

httpx是requests的现代化替代品,支持async/await和HTTP/2。如果你的项目需要异步(如FastAPI项目)或HTTP/2,用httpx。如果只是写脚本、调API、做自动化——requests足够,生态更成熟,文档更多。简单说:同步用requests,异步用httpx。

Q: 怎么处理SSL证书问题(自签名证书/内网环境)?

verify=False跳过验证(仅限内网环境!公网绝对不要用):requests.get('https://192.168.1.1/api', verify=False)。更好的做法是指定你的自签名证书:requests.get(url, verify='/path/to/cert.pem')。或者用Session统一配置:session.verify = False

Q: POST的data=和json=到底有什么区别?

json=:自动设置Content-Type为application/json,自动json.dumps()序列化。适用于REST API。data=:Content-Type为application/x-www-form-urlencoded,适用于HTML表单。如果你传dict给data=,requests会自动编码。如果你传字符串/bytes,原样发送(需要手动设Content-Type)。记一个原则:调API用json=,模拟登录用data=。

总结

把requests用好就七个要点:

  1. params= 传Query String,json= 传JSON,data= 传表单
  2. 用Session 复用连接和Cookie,不要每次请求new一个
  3. 永远设timeout,不然一个卡住的请求能拖死整个脚本
  4. 加自动重试(Retry + HTTPAdapter),网络波动不再影响你
  5. 用resp.raise_for_status() 统一处理HTTP错误
  6. try/except RequestException 兜底所有网络层异常
  7. stream=True + iter_content 下载大文件,不把内存吃光

requests可能是Python生态里设计得最好的库之一。简单到一行代码就能发请求,强大到能支撑百万级的API调用。把它吃透,能省下大量写HTTP请求代码的时间。

相关阅读:

📤 分享这篇文章