前言:为什么所有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用好就七个要点:
- params= 传Query String,json= 传JSON,data= 传表单
- 用Session 复用连接和Cookie,不要每次请求new一个
- 永远设timeout,不然一个卡住的请求能拖死整个脚本
- 加自动重试(Retry + HTTPAdapter),网络波动不再影响你
- 用resp.raise_for_status() 统一处理HTTP错误
- try/except RequestException 兜底所有网络层异常
- stream=True + iter_content 下载大文件,不把内存吃光
requests可能是Python生态里设计得最好的库之一。简单到一行代码就能发请求,强大到能支撑百万级的API调用。把它吃透,能省下大量写HTTP请求代码的时间。
相关阅读: