
咱们做开发的,肯定都遇到过这种糟心事儿:同一个 API 调用好几次,每次都等半天,还白白浪费流量 —— 要是调用的是按次收费的 API,那更是心疼钱!其实解决这问题特简单,给 API 加个 “缓存” 就行。说白了,就是第一次请求 API 的时候,把返回的数据存到本地文件里;下次再要同样的数据,直接读本地文件,不用再发网络请求了。
这篇文章就手把手教你怎么实现 API 缓存,从方案选择到实战代码,再到常见坑和面试考点,全给你讲透。哪怕你是刚接触 API 的新手,跟着做也能搞定。
在写代码之前,得先明白 “为什么” 和 “选什么”,不然写出来的东西可能不适用自己的场景。
不是所有缓存都一样,不同方案适合不同场景。咱们用表格对比一下,新手优先选 “JSON 文件缓存”,简单易上手,不用额外装软件。
缓存方案 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
JSON 文件缓存 | 把 API 数据存成本地 JSON 文件 |
|
| 个人项目、小脚本、数据更新不频繁的场景 |
Redis 缓存 | 把数据存在内存数据库里 |
|
| 企业项目、高并发场景、多服务共享数据 |
Pickle 文件缓存 | 把 Python 对象直接序列化存文件 |
|
| 只在 Python 内部用、存复杂对象的场景 |
新手建议:先从 “JSON 文件缓存” 入手,本文也以这个方案为核心实战 —— 学会了这个,再学 Redis 也会更容易。
咱们拿免费的 dummyjson.com 做例子(这个 API 专门用来测试,不用申请密钥,直接就能用)。目标是实现:
咱们需要 requests 库来发 API 请求(Python 自带的库不好用),直接用 pip 装:
打开命令行,输入下面的命令,按回车:
pip install requests先写个最基础的代码,不做任何缓存,看看重复调用的耗时 —— 这样才能对比出缓存的好处。
import requests
import time
def get_api_data_no_cache(url):
"""没缓存的API请求函数"""
# 记录开始时间(用来算耗时)
start_time = time.time()
try:
# 发GET请求
response = requests.get(url)
# 检查请求是否成功(状态码200表示成功)
response.raise_for_status() # 要是状态码不对,会抛出错误
# 把响应转成JSON格式(API返回的通常是JSON)
data = response.json()
# 算耗时(保留3位小数)
cost_time = round(time.time() - start_time, 3)
print(f"没缓存:请求API成功!耗时 {cost_time} 秒")
return data
except requests.exceptions.RequestException as e:
print(f"没缓存:请求API失败!错误:{e}")
return None
# 测试:调用dummyjson的“产品列表”API(返回10个产品数据)
api_url = "https://dummyjson.com/products?limit=10"
# 第一次调用
print("=== 第一次没缓存调用 ===")
data1 = get_api_data_no_cache(api_url)
# 等1秒,再调用一次(模拟重复请求)
time.sleep(1)
print("n=== 第二次没缓存调用 ===")
data2 = get_api_data_no_cache(api_url)运行结果大概是这样:
两次调用都要发网络请求,每次耗时 1 秒左右(具体看你网速)。
=== 第一次没缓存调用 ===
没缓存:请求API成功!耗时 0.862 秒
=== 第二次没缓存调用 ===
没缓存:请求API成功!耗时 0.795 秒这就是痛点:明明要的是同样的数据,却要重复等 1 秒,完全没必要!
现在给上面的代码加缓存功能,核心逻辑就 4 步:
① 确定缓存文件存在哪里、叫什么名;
② 检查缓存文件是否存在,以及过没过期;
③ 没过期就读缓存,过期 / 不存在就请求 API;
④ 请求到新数据后,把数据存成缓存文件。
完整代码如下,每一行都加了注释,保证你能看懂:
import requests
import os
import json
import time
import hashlib
def get_api_data_with_cache(
url,
cache_dir="api_cache", # 缓存文件存在哪个文件夹里
expire_seconds=3600 # 缓存过期时间(秒),这里设1小时
):
"""带缓存的API请求函数"""
start_time = time.time()
# --------------------------
# 步骤1:生成唯一的缓存文件名
# --------------------------
# 问题:URL可能很长,直接当文件名会有问题(比如有特殊字符)
# 解决:把URL转成MD5哈希值(固定32位字符串,安全又短)
url_encoded = url.encode("utf-8") # 转成字节串(哈希需要)
url_hash = hashlib.md5(url_encoded).hexdigest() # 生成MD5哈希
cache_file = os.path.join(cache_dir, f"{url_hash}.json") # 缓存文件路径:api_cache/xxx.json
# --------------------------
# 步骤2:检查缓存是否可用
# --------------------------
# 先判断缓存文件夹是否存在,不存在就创建
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
print(f"创建缓存文件夹:{cache_dir}")
# 检查缓存文件是否存在 + 没过期
if os.path.exists(cache_file):
# 获取缓存文件的修改时间(最后一次保存的时间)
cache_modify_time = os.path.getmtime(cache_file)
# 计算当前时间和修改时间的差(秒)
time_diff = time.time() - cache_modify_time
# 判断是否没过期
if time_diff < expire_seconds:
try:
# 读缓存文件
with open(cache_file, "r", encoding="utf-8") as f:
data = json.load(f)
cost_time = round(time.time() - start_time, 3)
print(f"✅ 命中缓存!耗时 {cost_time} 秒(缓存文件:{cache_file})")
return data
except json.JSONDecodeError:
print(f"❌ 缓存文件损坏,重新请求API")
except Exception as e:
print(f"❌ 读取缓存出错:{e},重新请求API")
# --------------------------
# 步骤3:缓存不可用,请求API
# --------------------------
try:
print(f"🔄 缓存不存在/已过期,请求API...")
response = requests.get(url)
response.raise_for_status() # 检查请求是否成功
data = response.json()
# --------------------------
# 步骤4:保存新数据到缓存文件
# --------------------------
with open(cache_file, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2) # indent=2:格式化显示,方便人看
cost_time = round(time.time() - start_time, 3)
print(f"✅ API请求成功!耗时 {cost_time} 秒,已保存到缓存")
return data
except requests.exceptions.RequestException as e:
print(f"❌ API请求失败!错误:{e}")
return None
# --------------------------
# 测试:对比有缓存和没缓存的区别
# --------------------------
api_url = "https://dummyjson.com/products?limit=10"
# 第一次调用:没缓存,会请求API并创建缓存
print("=== 第一次带缓存调用 ===")
data1 = get_api_data_with_cache(api_url)
# 等1秒,第二次调用:有缓存,直接读
time.sleep(1)
print("n=== 第二次带缓存调用 ===")
data2 = get_api_data_with_cache(api_url)
# 等3601秒(超过1小时),第三次调用:缓存过期,重新请求(可选测试,不用等这么久)
# time.sleep(3601)
# print("n=== 第三次带缓存调用(缓存过期) ===")
# data3 = get_api_data_with_cache(api_url)运行结果一定会让你惊喜:
第一次调用要 1 秒左右,第二次调用耗时只有 0.001 秒(因为读的是本地文件)!
=== 第一次带缓存调用 ===
创建缓存文件夹:api_cache
🔄 缓存不存在/已过期,请求API...
✅ API请求成功!耗时 0.912 秒,已保存到缓存
=== 第二次带缓存调用 ===
✅ 命中缓存!耗时 0.001 秒(缓存文件:api_cache/xxx.json)
同时你会发现,项目文件夹里多了个 api_cache 文件夹,里面有个 .json 文件 —— 这就是咱们的缓存文件,打开能直接看到 API 返回的数据,特别直观。
很多 API 会带参数(比如 https://dummyjson.com/products?id=1 查单个产品),如果只按 URL 哈希,不同参数的请求会被当成同一个,导致缓存错乱。
咱们优化一下代码,让参数也参与缓存文件名的生成。修改后的核心代码(只改步骤 1 的文件名生成逻辑):
def get_api_data_with_cache(
url,
params=None, # 新增:API参数(比如{"id":1, "limit":10})
cache_dir="api_cache",
expire_seconds=3600
):
start_time = time.time()
# --------------------------
# 优化:参数也参与哈希,避免缓存错乱
# --------------------------
# 把URL和参数拼在一起,再转哈希
if params is None:
params = {}
# 把参数转成有序的字符串(保证不同顺序的相同参数哈希一致,比如?id=1&limit=10和?limit=10&id=1)
params_str = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
# 完整的请求标识:URL + 参数
request_key = f"{url}?{params_str}" if params_str else url
# 生成哈希文件名
request_key_encoded = request_key.encode("utf-8")
url_hash = hashlib.md5(request_key_encoded).hexdigest()
cache_file = os.path.join(cache_dir, f"{url_hash}.json")
# 后面的逻辑和之前一样...(检查缓存、请求API、保存缓存)
# --------------------------
# 步骤3:请求API时带上参数
# --------------------------
response = requests.get(url, params=params) # 这里加了params=params
# 测试带参数的API
print("=== 调用带参数的API(id=1) ===")
data_id1 = get_api_data_with_cache("https://dummyjson.com/products", params={"id":1})
print("n=== 调用带参数的API(id=2) ===")
data_id2 = get_api_data_with_cache("https://dummyjson.com/products", params={"id":2})运行结果:
两个不同参数的请求会生成两个不同的缓存文件,不会错乱。比如查 id=1 的产品用一个缓存,查 id=2 的用另一个,完美解决参数问题。
写缓存的时候,很容易踩坑,我把自己遇到过的问题整理好了,照着解决就行。
常见问题 | 现象描述 | 原因分析 | 解决办法 |
|---|---|---|---|
缓存文件乱码 | 打开 JSON 缓存文件,中文显示成 “u4e2du6587” | 保存 JSON 时没加 | 写文件时用 |
不同参数请求拿到相同缓存 | 查?id=1 和?id=2,返回的都是同一个产品数据 | 缓存文件名没包含参数,只哈希了基础 URL | 把参数拼到 URL 里再哈希(参考进阶优化部分) |
缓存文件越积越多 | api_cache 文件夹里有几百个 JSON 文件 | 只创建缓存,没清理过期 / 无用的缓存 |
|
API 返回数据变了,缓存没更 | 服务器数据更新了,但程序还读旧缓存 | 缓存过期时间设太长,或者没主动更新缓存 |
|
缓存文件损坏导致程序崩溃 | 运行时报错 “json.decoder.JSONDecodeError” | 保存缓存时程序意外退出(比如断电、强制关闭) | 读缓存时加 |
缓存文件夹权限不够 | 报错 “PermissionError: Errno 13 Permission denied” | 程序没有创建 / 写入文件夹的权限 |
|
如果你面试 Python 开发岗,涉及 API 优化的话,这些问题很可能会被问到。答案我都按 “大白话 + 实战经验” 的风格写好了,直接背也能用。
回答:
适合加缓存的场景:
不适合加缓存的场景:
回答:
遇到过 3 个主要问题,都是实战中踩过的坑:
回答:
最核心的区别是 “存储位置” 和 “性能”:
怎么选:
回答:
“缓存一致性” 就是说:缓存里的数据和 API 服务器上的数据要保持一致,不能服务器数据更新了,缓存还是旧的,导致用户看到错的信息。
我常用两种解决办法:
回答:
主要注意两点,避免安全和数据错乱问题:
咱们这篇文章从 “痛点” 出发,讲了缓存的价值、方案选择,然后手把手写了 JSON 缓存的实战代码,还整理了避坑指南和面试考点。其实 API 缓存的核心逻辑很简单:“能读本地就不发网络请求”,但细节上要考虑参数、过期时间、异常处理这些问题。
如果你是刚开始学,可以先把文中的代码复制到本地,改改 API 地址和参数,跑一遍看看效果;如果是做项目,根据场景选 JSON 或 Redis 缓存,遇到问题查避坑指南就行。
后续你还可以进阶学习:比如用 functools.lru_cache 做内存缓存(适合短期频繁调用的小数据),或者用 Redis 的 “发布订阅” 功能实现缓存主动更新 —— 但这些都是基于今天学的 “缓存思想”,基础打好了,学进阶内容会特别快。
最后,为了帮你更快落地,我可以帮你整理一份 《API 缓存实战代码模板》,包含带参数缓存、缓存清理、异常处理的完整代码,你以后做项目直接复制修改就能用。要不要我帮你整理一下?
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。