1. 为什么每个Python开发者都需要掌握requests
十年前我刚入行时,用Python发送HTTP请求还得忍受urllib2的折磨。直到2012年requests库横空出世,这个仅用pip install requests就能获得的第三方库,彻底改变了Python开发者处理网络请求的方式。如今在PyPI的下载量统计中,requests常年稳居前三,GitHub星标数超过48k,这些数据足以证明它的江湖地位。
requests的核心价值在于:用人类可读的语法完成复杂的HTTP操作。比如获取一个JSON接口的数据,原生urllib需要十多行代码处理连接、异常和编码,而requests只需要:
import requests resp = requests.get('https://api.example.com/data') print(resp.json())这个设计哲学贯穿整个库——每个API调用都像在说人话。requests.get()对应HTTP GET方法,resp.json()自动解析JSON响应,连超时设置都是直观的timeout=3.0。这种极简主义让新手能快速上手,也让老手能专注于业务逻辑而非协议细节。
2. 核心功能全景解析
2.1 七大HTTP方法全覆盖
requests支持所有主流HTTP方法,每种都有对应的快捷函数:
requests.get(url) # GET requests.post(url) # POST requests.put(url) # PUT requests.delete(url) # DELETE requests.head(url) # HEAD requests.options(url)# OPTIONS requests.patch(url) # PATCH以POST请求为例,发送表单数据和JSON数据是两种最常见场景。表单提交时,使用data参数自动编码为application/x-www-form-urlencoded:
payload = {'key1': 'value1', 'key2': 'value2'} r = requests.post("https://httpbin.org/post", data=payload)而发送JSON数据时,既可以直接传递字典给json参数(推荐),也可以手动序列化:
# 方式1:自动序列化 r = requests.post(url, json={'key':'value'}) # 方式2:手动序列化 import json r = requests.post(url, data=json.dumps({'key':'value'}), headers={'Content-Type': 'application/json'})关键区别:
data参数发送原始字节流,需要手动处理编码和头信息;json参数自动处理序列化和Content-Type头
2.2 请求参数的高级控制
实际项目中,我们经常需要精细控制请求行为。requests通过统一的requests.request()方法暴露所有可配置项:
requests.request( method='GET', url='http://example.com', params={'key':'value'}, # URL查询参数 data={'form':'data'}, # 表单数据 json={'json':'data'}, # JSON数据 headers={'X-Header':'value'}, # 自定义头 cookies={'name':'value'}, # Cookie files={'file': open('report.xls', 'rb')}, # 文件上传 auth=('user', 'pass'), # 基础认证 timeout=3.14, # 超时秒数 allow_redirects=False, # 禁用重定向 proxies={'http': 'http://proxy:port'}, # 代理设置 verify='/path/to/cert.pem' # SSL证书验证 )几个特别实用的参数:
timeout:强烈建议始终设置,避免请求挂起阻塞程序。可以传入元组分别指定连接和读取超时:(3.05, 27)allow_redirects:默认True自动跟随重定向,调试时设为False可查看原始响应verify:设为False可跳过SSL验证(仅测试环境使用)
2.3 响应对象的深度挖掘
每个请求返回的Response对象都是信息宝库。除了最常用的resp.status_code和resp.text,这些属性也值得关注:
r = requests.get('https://api.github.com/events') r.encoding # 当前响应编码,如'utf-8' r.content # 原始字节数据(用于非文本内容) r.json() # 自动解析JSON(需响应头正确) r.raw # 原始socket响应(用于流式下载) r.headers # 响应头字典(不区分大小写) r.cookies # 获取的CookieJar对象 r.history # 重定向历史列表 r.url # 最终请求URL(考虑重定向后) r.elapsed # 请求耗时(timedelta对象)处理二进制内容(如图片下载)的正确姿势:
r = requests.get('https://example.com/image.jpg', stream=True) with open('local.jpg', 'wb') as f: for chunk in r.iter_content(chunk_size=8192): if chunk: # 过滤keep-alive空块 f.write(chunk)设置stream=True避免立即加载大文件到内存,iter_content()则实现分块写入。
3. 实战技巧与性能优化
3.1 会话保持与连接池
每次requests.get()都新建连接显然低效。使用Session对象可以:
- 自动保持Cookie(无需手动处理)
- 复用TCP连接(显著提升性能)
- 统一配置默认参数
s = requests.Session() s.headers.update({'X-Test': 'true'}) # 会话级头信息 s.auth = ('user', 'pass') # 统一认证 # 所有会话内请求自动携带上述配置 r1 = s.get('https://httpbin.org/cookies') r2 = s.get('https://httpbin.org/headers')连接池大小通过adapters配置。默认每个主机保持最多2个连接,可通过以下方式调整:
from requests.adapters import HTTPAdapter s = requests.Session() adapter = HTTPAdapter(pool_connections=10, pool_maxsize=100) s.mount('http://', adapter) s.mount('https://', adapter)3.2 超时与重试机制
生产环境必须考虑网络不可靠性。推荐使用urllib3.util.Retry结合会话:
from urllib3.util import Retry from requests.adapters import HTTPAdapter retry_strategy = Retry( total=3, backoff_factor=1, status_forcelist=[408, 429, 500, 502, 503, 504] ) s = requests.Session() adapter = HTTPAdapter(max_retries=retry_strategy) s.mount("https://", adapter) s.mount("http://", adapter) try: response = s.get("https://example.com", timeout=5) except requests.exceptions.RequestException as e: print(f"请求最终失败: {e}")这个配置会在以下情况自动重试:
- 连接/读取超时
- 指定状态码(如500错误)
- 最多重试3次,每次间隔按指数退避(1s, 2s, 4s)
3.3 文件上传与流式请求
多文件上传是常见需求,requests支持同时传文件和附加字段:
files = { 'document': ('report.pdf', open('report.pdf', 'rb'), 'application/pdf'), 'image': ('photo.jpg', open('photo.jpg', 'rb'), 'image/jpeg'), 'meta': ('metadata', json.dumps({'author': 'John'}), 'application/json') } r = requests.post('https://httpbin.org/post', files=files)对于大文件上传,应该使用流式处理避免内存爆炸:
def generate_large_file(): for _ in range(1000000): yield b"some binary data\n" requests.post('https://example.com/upload', data=generate_large_file())4. 安全防护与异常处理
4.1 证书验证与HTTPS最佳实践
虽然测试时可以用verify=False跳过SSL验证,但生产环境必须确保安全:
# 推荐做法:指定CA证书包路径 requests.get('https://example.com', verify='/path/to/cert.pem') # 或者使用系统默认证书 requests.get('https://example.com', verify=True) # 默认值客户端证书认证场景:
requests.get('https://example.com', cert=('/path/client.cert', '/path/client.key'))4.2 异常处理金字塔
requests的异常体系非常完善,应该分层捕获:
try: r = requests.get('https://example.com', timeout=5) r.raise_for_status() # 检查HTTP错误码 except requests.exceptions.Timeout: print("请求超时") except requests.exceptions.SSLError: print("SSL证书错误") except requests.exceptions.RequestException as e: print(f"请求失败: {e}") else: print("请求成功")特别提醒:永远不要裸奔try-except!以下写法会默默吞掉所有错误:
# 错误示范! try: r = requests.get(url) except: # 捕获所有异常包括KeyboardInterrupt pass4.3 防范常见安全风险
注入攻击防护:
# 危险:直接拼接参数 requests.get(f"https://api.com/search?q={user_input}") # 安全:使用params自动编码 requests.get("https://api.com/search", params={'q': user_input})敏感信息泄露:
# 错误:在日志打印完整响应 print(response.text) # 可能包含敏感数据 # 正确:只记录必要元数据 logger.info(f"Request to {response.url} took {response.elapsed}")速率限制处理:
if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) time.sleep(retry_after)
5. 调试技巧与高级用法
5.1 请求调试与流量分析
查看实际发出的请求信息对调试至关重要:
# 打印请求头、体等详细信息 print(r.request.headers) print(r.request.body) # 使用hooks记录时间点 def record_timing(response, *args, **kwargs): response.metrics = {'start': kwargs['start'], 'end': time.time()} return response r = requests.get('https://example.com', hooks={'response': record_timing}, start=time.time())对于复杂场景,建议配合mitmproxy等工具分析实际网络流量。
5.2 自定义适配器与中间件
requests的架构允许深度定制。例如实现一个记录所有请求的适配器:
from requests.adapters import HTTPAdapter class LoggingAdapter(HTTPAdapter): def send(self, request, **kwargs): print(f"Sending {request.method} to {request.url}") response = super().send(request, **kwargs) print(f"Got {response.status_code} from {response.url}") return response s = requests.Session() s.mount('https://', LoggingAdapter()) s.get('https://example.com')5.3 异步请求方案
虽然requests本身是同步的,但可以结合线程池实现并发:
from concurrent.futures import ThreadPoolExecutor urls = ['https://example.com/1', 'https://example.com/2'] with ThreadPoolExecutor(max_workers=5) as executor: futures = [executor.submit(requests.get, url) for url in urls] results = [f.result().text for f in futures]对于现代异步框架,推荐使用aiohttp或httpx等原生异步库。
6. 性能对比与生态工具
6.1 主流HTTP库基准测试
在Python生态中,除了requests还有多个HTTP客户端选择。以下是简单对比:
| 特性 | requests | urllib3 | httpx | aiohttp |
|---|---|---|---|---|
| API友好度 | ★★★★★ | ★★☆ | ★★★★☆ | ★★★☆ |
| 同步支持 | ✓ | ✓ | ✓ | ✗ |
| 异步支持 | ✗ | ✗ | ✓ | ✓ |
| HTTP/2支持 | ✗ | ✗ | ✓ | ✓ |
| 连接池 | ✓ | ✓ | ✓ | ✓ |
| 安装体积(MB) | 1.1 | 0.6 | 2.3 | 3.1 |
requests依然是同步场景下的最佳选择,除非你需要HTTP/2或原生异步支持。
6.2 常用配套工具推荐
requests-cache:为requests添加缓存支持
import requests_cache requests_cache.install_cache('demo_cache') # 后续请求自动缓存requests-html:内置HTML解析的增强版
from requests_html import HTMLSession session = HTMLSession() r = session.get('https://example.com') print(r.html.find('title', first=True).text)requests-toolbelt:提供多部分编码、流式上传等高级功能
from requests_toolbelt.multipart.encoder import MultipartEncoder m = MultipartEncoder({'field': 'value', 'file': ('f.txt', open('f.txt', 'rb'))}) requests.post('http://example.com', data=m, headers={'Content-Type': m.content_type})
7. 真实项目经验总结
在电商爬虫项目中,我们每天要处理数百万次API请求。经过多次优化,最终稳定运行的配置如下:
class ApiClient: def __init__(self): self.session = requests.Session() # 连接池配置 adapter = HTTPAdapter( pool_connections=20, pool_maxsize=100, max_retries=Retry( total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504] ) ) self.session.mount('http://', adapter) self.session.mount('https://', adapter) # 默认头信息 self.session.headers.update({ 'User-Agent': 'Mozilla/5.0', 'Accept-Encoding': 'gzip, deflate' }) def request(self, method, url, **kwargs): # 强制超时设置 kwargs.setdefault('timeout', (3.05, 30)) try: resp = self.session.request(method, url, **kwargs) resp.raise_for_status() return resp except requests.exceptions.RequestException as e: logger.error(f"Request failed: {e}") raise几个关键优化点:
- 全局Session减少TCP握手开销
- 合理的连接池大小(根据服务器限制调整)
- 必须设置超时避免僵尸请求
- 统一异常处理和日志记录
- 自动重试应对临时故障
在长期实践中,我们发现90%的请求问题源于:
- 未设置超时导致线程阻塞
- 未处理重定向丢失Cookies
- 未考虑代理环境差异
- 响应编码猜测错误(特别是非UTF-8页面)
requests的Response对象有个不太为人知的特性:自动处理编码的逻辑在resp.apparent_encoding属性中实现。当常规的resp.encoding失效时(比如服务器返回错误的Content-Type),可以这样兜底:
if not resp.encoding or 'ascii' in resp.encoding.lower(): resp.encoding = resp.apparent_encoding