1. 为什么选择 requests 作为你的 HTTP 客户端
在 Python 生态中,处理 HTTP 请求的库并不少,但 requests 能成为事实上的标准库,绝非偶然。作为一个从 2011 年就开始使用 requests 的老用户,我可以负责任地说,这个库改变了 Python 开发者与网络交互的方式。
requests 的核心优势在于它完美体现了 Python 的哲学——"简单胜于复杂"。它把原本需要几十行 urllib 代码才能完成的工作,简化成了几行直观的语句。举个例子,在 urllib 时代,发送一个带参数的 GET 请求需要这样:
python复制from urllib.parse import urlencode
from urllib.request import urlopen
params = urlencode({'q': 'python', 'page': 1})
response = urlopen(f"https://httpbin.org/get?{params}")
data = response.read().decode('utf-8')
而用 requests,同样的功能只需要:
python复制import requests
response = requests.get("https://httpbin.org/get", params={'q': 'python', 'page': 1})
data = response.json()
这种简洁性不是通过牺牲功能换来的。requests 提供了完整的 HTTP 功能支持:
- 各种 HTTP 方法(GET/POST/PUT/DELETE 等)
- 自动内容解码
- 连接池管理
- 会话保持
- 文件上传下载
- 完善的错误处理
在企业级应用中,requests 的稳定性经过了大规模验证。根据 PyPI 的统计数据,requests 的月下载量超过 1.5 亿次,被包括 Google、Amazon、Spotify 等众多知名公司采用。这种广泛的采用意味着它的 API 设计经受住了实践的考验,也意味着你在使用中遇到问题时,更容易找到解决方案。
提示:虽然 Python 标准库中有 urllib,但在实际项目中,99% 的情况下 requests 都是更好的选择。除非你有特殊需求(如需要完全避免第三方依赖),否则建议直接使用 requests。
2. 环境准备与安装
2.1 安装 requests
安装 requests 非常简单,使用 pip 即可:
bash复制pip install requests
对于企业级项目,我建议将 requests 与其他依赖一起记录在 requirements.txt 文件中:
text复制requests>=2.31.0
这里指定了最低版本 2.31.0,因为这个版本修复了一些重要的安全漏洞。在实际项目中,固定版本号可以避免因依赖更新导致的意外问题。
2.2 验证安装
安装完成后,可以通过以下方式验证 requests 是否正常工作:
python复制import requests
print(requests.__version__) # 应该输出类似 2.31.0 的版本号
2.3 虚拟环境建议
对于任何 Python 项目,我都强烈建议使用虚拟环境。这样可以隔离不同项目的依赖,避免版本冲突。创建和使用虚拟环境的步骤如下:
bash复制# 创建虚拟环境
python -m venv myenv
# 激活虚拟环境 (Linux/macOS)
source myenv/bin/activate
# 激活虚拟环境 (Windows)
myenv\Scripts\activate
# 在虚拟环境中安装 requests
pip install requests
3. 基础请求方法详解
3.1 GET 请求:获取数据
GET 是最常用的 HTTP 方法,用于从服务器获取数据。requests 让发送 GET 请求变得非常简单:
python复制import requests
# 基本 GET 请求
response = requests.get('https://api.github.com/events')
# 带查询参数的 GET 请求
params = {'key1': 'value1', 'key2': 'value2'}
response = requests.get('https://httpbin.org/get', params=params)
GET 请求的几个关键点:
-
参数传递:使用
params参数传递查询字符串,requests 会自动将其编码并附加到 URL 上。 -
响应处理:
response对象包含了服务器返回的所有信息:response.status_code:HTTP 状态码response.text:响应内容(字符串形式)response.json():如果响应是 JSON,自动解析为 Python 对象response.headers:响应头信息
-
超时设置:生产环境中必须设置 timeout,避免请求长时间阻塞:
python复制# 设置 3 秒连接超时和 5 秒读取超时
response = requests.get('https://api.github.com/events', timeout=(3, 5))
3.2 POST 请求:提交数据
POST 请求用于向服务器提交数据,常见于表单提交和 API 调用。requests 支持多种数据提交方式:
表单形式提交
python复制data = {'username': 'admin', 'password': 'secret'}
response = requests.post('https://httpbin.org/post', data=data)
这种方式会使用 application/x-www-form-urlencoded 作为 Content-Type,适合传统的 HTML 表单提交。
JSON 形式提交
python复制payload = {'name': 'John Doe', 'age': 30}
response = requests.post('https://httpbin.org/post', json=payload)
使用 json 参数时,requests 会自动:
- 将 Python 对象序列化为 JSON 字符串
- 设置 Content-Type 为
application/json
文件上传
python复制files = {'file': open('report.xls', 'rb')}
response = requests.post('https://httpbin.org/post', files=files)
对于大文件,建议使用流式上传以避免内存问题:
python复制with open('large_file.bin', 'rb') as f:
requests.post('https://httpbin.org/post', data=f)
3.3 其他 HTTP 方法
requests 支持所有常见的 HTTP 方法:
python复制# PUT 请求
response = requests.put('https://httpbin.org/put', data={'key': 'value'})
# DELETE 请求
response = requests.delete('https://httpbin.org/delete')
# HEAD 请求
response = requests.head('https://httpbin.org/get')
# OPTIONS 请求
response = requests.options('https://httpbin.org/get')
4. 请求配置进阶
4.1 自定义请求头
很多 API 需要特定的请求头,如认证信息、内容类型等:
python复制headers = {
'User-Agent': 'MyApp/1.0',
'Authorization': 'Bearer xxxxxxxx',
'Accept': 'application/json'
}
response = requests.get('https://api.example.com/data', headers=headers)
常见的请求头包括:
User-Agent:标识客户端类型Authorization:认证凭证Accept:指定客户端能处理的内容类型Content-Type:请求体的媒体类型(如application/json)
4.2 Cookie 管理
requests 可以自动处理 Cookie,也可以手动指定:
python复制# 从响应中获取 Cookie
response = requests.get('https://httpbin.org/cookies/set/sessionid/123456')
print(response.cookies['sessionid'])
# 发送请求时带上 Cookie
cookies = {'sessionid': '123456'}
response = requests.get('https://httpbin.org/cookies', cookies=cookies)
对于需要保持会话的场景,建议使用 Session 对象(后面会详细介绍)。
4.3 超时设置
生产环境中,必须设置合理的超时时间:
python复制# 总超时 5 秒(包括连接和读取)
response = requests.get('https://api.example.com', timeout=5)
# 分别设置连接和读取超时
response = requests.get('https://api.example.com', timeout=(3.05, 27))
超时设置需要考虑:
- 网络延迟:内网请求可以设置较短超时,公网请求需要更长
- 服务器响应时间:复杂查询可能需要更长时间
- 用户体验:前端请求通常不应超过 10 秒
4.4 SSL 证书验证
默认情况下,requests 会验证 SSL 证书。在开发环境中,有时需要禁用验证:
python复制# 不推荐:禁用 SSL 验证(仅用于测试)
response = requests.get('https://example.com', verify=False)
生产环境中应该正确处理证书:
python复制# 指定自定义 CA 证书包
response = requests.get('https://example.com', verify='/path/to/certfile.pem')
5. 响应处理技巧
5.1 响应内容解析
requests 提供了多种方式访问响应内容:
python复制response = requests.get('https://api.github.com/events')
# 文本内容(自动解码)
print(response.text)
# 二进制内容
print(response.content)
# JSON 内容(自动解析)
print(response.json())
# 原始响应(用于流式读取)
print(response.raw)
5.2 编码处理
当遇到编码问题时,可以手动指定:
python复制response.encoding = 'utf-8' # 强制使用 UTF-8 解码
print(response.text)
对于未知编码的响应,可以使用 chardet 自动检测:
python复制import chardet
encoding = chardet.detect(response.content)['encoding']
response.encoding = encoding
5.3 流式处理大响应
对于大文件下载,应该使用流式处理:
python复制response = requests.get('https://example.com/large-file', stream=True)
with open('large-file', 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
关键点:
stream=True不会立即下载整个响应iter_content按块迭代内容chunk_size控制每次读取的字节数(通常 8KB 左右)
6. 会话(Session)管理
6.1 为什么使用 Session
Session 对象可以跨请求保持某些参数,如:
- Cookies
- 连接池
- 认证信息
- 请求头
这带来了两个主要好处:
- 性能提升:复用 TCP 连接,减少握手开销
- 代码简洁:无需重复设置相同参数
6.2 基本用法
python复制s = requests.Session()
# 设置会话级参数
s.headers.update({'User-Agent': 'MyApp/1.0'})
s.auth = ('username', 'password')
# 所有请求都会带上这些参数
response = s.get('https://api.example.com/data')
6.3 连接池管理
Session 默认使用 urllib3 的连接池,可以配置:
python复制from requests.adapters import HTTPAdapter
s = requests.Session()
# 创建适配器并设置连接池大小
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=100)
s.mount('http://', adapter)
s.mount('https://', adapter)
配置建议:
pool_connections:每个主机的连接数(默认 10)pool_maxsize:连接池最大连接数(默认 10)- 对于高并发应用,可以适当增大这些值
7. 错误处理与重试
7.1 基本错误处理
requests 可能抛出多种异常:
python复制try:
response = requests.get('https://api.example.com', timeout=5)
response.raise_for_status() # 检查 HTTP 错误
except requests.exceptions.Timeout:
print("请求超时")
except requests.exceptions.HTTPError as err:
print(f"HTTP 错误: {err}")
except requests.exceptions.RequestException as err:
print(f"请求异常: {err}")
7.2 自动重试机制
对于不稳定的网络或服务,可以实现重试逻辑:
python复制from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
s = requests.Session()
retries = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
s.mount('http://', HTTPAdapter(max_retries=retries))
s.mount('https://', HTTPAdapter(max_retries=retries))
配置说明:
total:最大重试次数backoff_factor:重试间隔(秒)status_forcelist:遇到这些状态码会重试
7.3 自定义重试策略
更复杂的场景可能需要自定义重试逻辑:
python复制import time
from requests.exceptions import RequestException
def request_with_retry(url, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response
except RequestException as err:
if attempt == max_retries - 1:
raise
wait_time = 2 ** attempt # 指数退避
time.sleep(wait_time)
8. 企业级最佳实践
8.1 封装 HTTP 客户端
在企业项目中,建议封装自己的 HTTP 客户端:
python复制import logging
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class APIClient:
def __init__(self, base_url):
self.base_url = base_url
self.session = requests.Session()
# 配置重试策略
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
# 默认请求头
self.session.headers.update({
"User-Agent": "MyApp/1.0",
"Accept": "application/json"
})
# 配置日志
self.logger = logging.getLogger(__name__)
def request(self, method, endpoint, **kwargs):
url = f"{self.base_url}{endpoint}"
# 确保有超时设置
if 'timeout' not in kwargs:
kwargs['timeout'] = (3.05, 30)
try:
self.logger.debug(f"Sending {method} request to {url}")
response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
self.logger.error(f"Request failed: {e}")
raise
def get(self, endpoint, params=None):
return self.request("GET", endpoint, params=params)
def post(self, endpoint, data=None, json=None):
return self.request("POST", endpoint, data=data, json=json)
8.2 日志记录
完善的日志记录对调试和监控至关重要:
python复制import logging
# 配置 requests 的日志记录
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('urllib3').setLevel(logging.WARNING)
# 在请求前后记录日志
def log_request(response, *args, **kwargs):
logging.info(f"Request to {response.url} took {response.elapsed.total_seconds()}s")
return response
s = requests.Session()
s.hooks['response'] = [log_request]
8.3 性能优化
-
连接池调优:
- 根据并发需求调整
pool_connections和pool_maxsize - 考虑使用
requests-futures实现异步请求
- 根据并发需求调整
-
压缩传输:
- 启用 gzip 压缩减少传输量:
python复制headers = {'Accept-Encoding': 'gzip, deflate'}
- 启用 gzip 压缩减少传输量:
-
DNS 缓存:
- 对于频繁访问的域名,可以考虑使用
dnspython缓存 DNS 查询
- 对于频繁访问的域名,可以考虑使用
8.4 安全实践
-
凭证管理:
- 不要将敏感信息硬编码在代码中
- 使用环境变量或专门的 secrets 管理工具
-
输入验证:
- 对所有来自外部的 URL 和参数进行验证
- 避免 SSRF(服务器端请求伪造)攻击
-
TLS 安全:
- 使用现代 TLS 版本(1.2+)
- 定期更新证书
9. 常见问题与解决方案
9.1 连接问题
问题:ConnectionError 或 Timeout 错误
解决方案:
- 检查网络连接
- 增加超时时间
- 实现重试逻辑
- 检查代理设置
9.2 编码问题
问题:响应内容乱码
解决方案:
- 手动设置正确的编码:
response.encoding = 'utf-8' - 使用
response.content获取二进制数据后手动解码 - 安装
chardet自动检测编码
9.3 内存问题
问题:下载大文件时内存不足
解决方案:
- 使用流式下载:
stream=True - 使用
iter_content分块处理 - 设置合理的
chunk_size
9.4 性能问题
问题:请求速度慢
优化建议:
- 使用 Session 复用连接
- 启用 HTTP keep-alive
- 考虑使用连接池
- 对多个请求使用并行处理
9.5 认证问题
问题:401 Unauthorized 错误
检查步骤:
- 确认认证信息正确
- 检查认证头格式
- 确认 token 未过期
- 检查 API 文档确认认证方式
10. 实际案例:GitHub API 客户端
让我们通过一个完整的 GitHub API 客户端示例,展示如何应用上述知识:
python复制import logging
from typing import Optional, Dict, Any
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class GitHubClient:
BASE_URL = "https://api.github.com"
def __init__(self, token: str):
self.token = token
self.session = requests.Session()
# 配置重试
retry = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry)
self.session.mount("https://", adapter)
# 设置默认请求头
self.session.headers.update({
"Accept": "application/vnd.github.v3+json",
"Authorization": f"token {self.token}",
"User-Agent": "GitHub-API-Client/1.0"
})
# 配置日志
self.logger = logging.getLogger(__name__)
def _request(self, method: str, endpoint: str, **kwargs) -> Optional[Dict[str, Any]]:
url = f"{self.BASE_URL}{endpoint}"
try:
response = self.session.request(
method,
url,
timeout=(3.05, 30),
**kwargs
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
self.logger.error(f"HTTP error: {e.response.status_code} - {e.response.text}")
except requests.exceptions.RequestException as e:
self.logger.error(f"Request failed: {e}")
return None
def get_user(self, username: str) -> Optional[Dict[str, Any]]:
return self._request("GET", f"/users/{username}")
def list_repos(self, username: str) -> Optional[Dict[str, Any]]:
return self._request("GET", f"/users/{username}/repos")
def create_repo(self, name: str, private: bool = False) -> Optional[Dict[str, Any]]:
data = {
"name": name,
"private": private,
"auto_init": True
}
return self._request("POST", "/user/repos", json=data)
# 使用示例
if __name__ == "__main__":
import os
token = os.getenv("GITHUB_TOKEN")
if not token:
raise ValueError("请设置 GITHUB_TOKEN 环境变量")
client = GitHubClient(token)
# 获取用户信息
user = client.get_user("octocat")
print(user)
# 创建仓库
repo = client.create_repo("my-new-repo")
print(repo)
这个客户端展示了:
- 认证处理(使用 GitHub token)
- 请求封装
- 错误处理
- 日志记录
- 重试机制
- 类型提示(Python 3.5+)
11. 进阶话题
11.1 流式 API 处理
对于流式 API(如 Twitter 的流式 API),可以使用 requests 的流式功能:
python复制response = requests.get('https://stream.example.com/data', stream=True)
for line in response.iter_lines():
if line:
print(line.decode('utf-8'))
11.2 异步请求
虽然 requests 是同步库,但可以通过以下方式实现并发:
- 使用
concurrent.futures:
python复制from concurrent.futures import ThreadPoolExecutor
import requests
urls = ['url1', 'url2', 'url3']
def fetch(url):
return requests.get(url).text
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(fetch, urls))
- 使用
requests-futures:
python复制from requests_futures.sessions import FuturesSession
session = FuturesSession(max_workers=10)
future = session.get('https://example.com')
response = future.result()
11.3 测试与模拟
在测试中,可以使用 responses 库模拟 requests:
python复制import responses
import requests
@responses.activate
def test_my_api():
responses.add(
responses.GET,
'https://example.com/api',
json={'key': 'value'},
status=200
)
response = requests.get('https://example.com/api')
assert response.json() == {'key': 'value'}
11.4 性能监控
使用 requests 的钩子实现性能监控:
python复制def record_timing(response, *args, **kwargs):
print(f"Request to {response.url} took {response.elapsed.total_seconds()}s")
return response
s = requests.Session()
s.hooks['response'] = [record_timing]
s.get('https://example.com')
12. 总结与个人经验分享
在多年的 Python 开发中,requests 一直是我最信赖的 HTTP 客户端库。以下是我总结的一些经验教训:
-
超时设置是必须的:我曾经因为忘记设置 timeout,导致一个生产服务因为外部 API 挂掉而整个卡死。现在我会在所有请求中明确设置 timeout。
-
Session 是性能关键:在一个高并发的爬虫项目中,使用 Session 使请求速度提升了 3 倍以上,因为避免了重复的 TCP 握手。
-
错误处理要细致:早期我经常只捕获
RequestException,后来发现不同的异常需要不同的处理方式(如超时需要重试,认证错误需要立即失败)。 -
日志记录要全面:完善的日志记录可以节省大量调试时间。我现在会记录请求 URL、参数、耗时和响应状态。
-
保持库的更新:requests 及其依赖 urllib3 会定期发布安全更新。使用过时的版本可能会存在安全风险。
最后,requests 的简洁性可能会让你低估它的复杂性。在实际项目中,特别是企业级应用中,合理配置和使用 requests 可以避免很多问题。希望这篇指南能帮助你更好地掌握这个强大的工具。