历史上的今天:免费接口下如何做本地缓存与更新频率设计?
# 历史上的今天:免费接口下如何做本地缓存与更新频率设计?
> 接口:历史上的今天(apiCode=119,接入点 119-42)· 免费(设防滥用档位)· 适用人群:中高级开发者 · 阅读时间:约 6 分钟
## 核心要点
- 历史数据"按天稳定":同一 `date` 的结果当天基本不变,天然适合按 `date` 缓存。
- 缓存 key 用 `date`(MMDD),每日 0 点失效;命中缓存就不调接口,免费额度花在刀刃上。
- 服务端每日定时预热 + 前端读本地缓存,是教育/网站场景的标准姿势。
## Why:免费但不等于可以乱刷
虽然是免费接口,但设有防滥用档位。如果你的网站每天被访问几万次,每次都去调接口,既可能触档位限制,也拖慢自身响应。历史数据按天稳定——今天 2 月 20 日的结果,这一天内不会变。把"按天"作为缓存粒度,一次拉取、全天复用,体验与额度双优。
## What:缓存设计要点
| 维度 | 建议 |
|------|------|
| 缓存粒度 | 以 `date`(MMDD)为 key,单日一份 |
| 失效策略 | 自然日 0 点失效(TTL 到次日 00:00) |
| 存储 | 内存(LRU)/ Redis / 本地 JSON 文件均可 |
| 预热 | 服务端每日定时(如 00:05)拉取"今天"入库 |
| 档位 | 具体限额以官方档位说明为准,不编造数字 |
## How:三种缓存实现
### 1)Python + 内存(简单服务)
```python
import requests, datetime, time
_cache = {}
def get_history(date: str = None):
date = date or datetime.date.today().strftime("%m%d")
if date in _cache:
return _cache[date] # 命中缓存
url = "https://route.showapi.com/119-42"
params = {"appKey": "YOUR_APPKEY"}
data = {"date": date, "needContent": "1"}
resp = requests.post(url, params=params, data=data, timeout=10)
body = resp.json()["showapi_res_body"]
# 计算到次日 00:00 的 TTL
now = datetime.datetime.now()
ttl = (now.replace(hour=0, minute=0, second=0, microsecond=0)
+ datetime.timedelta(days=1) - now).seconds
_cache[date] = (time.time() + ttl, body["list"])
return body["list"]
def get_history_safe(date=None):
ts, data = get_history(date)
if time.time() > ts:
_cache.pop(date or datetime.date.today().strftime("%m%d"), None)
return get_history(date)
return data
```
### 2)Redis(多实例共享)
```python
import redis, json, datetime, requests
r = redis.Redis(host="localhost", port=6379, db=0)
def get_history_redis(date: str = None):
date = date or datetime.date.today().strftime("%m%d")
cached = r.get(f"history:{date}")
if cached:
return json.loads(cached)
url = "https://route.showapi.com/119-42"
params = {"appKey": "YOUR_APPKEY"}
data = {"date": date, "needContent": "1"}
body = requests.post(url, params=params, data=data, timeout=10).json()["showapi_res_body"]
# 过期时间设为到次日 00:00
now = datetime.datetime.now()
ttl = (now.replace(hour=0, minute=0, second=0, microsecond=0)
+ datetime.timedelta(days=1) - now).seconds
r.setex(f"history:{date}", ttl, json.dumps(body["list"], ensure_ascii=False))
return body["list"]
```
### 3)Node.js(fetch + Map)
```javascript
const cache = new Map();
function ttlToMidnight() {
const now = new Date();
const next = new Date(now); next.setHours(24, 0, 0, 0);
return next - now;
}
async function getHistory(date) {
date = date || new Date().toISOString().slice(5, 10).replace("-", "");
if (cache.has(date)) return cache.get(date);
const url = `https://route.showapi.com/119-42?appKey=YOUR_APPKEY`;
const body = new URLSearchParams({ date, needContent: "1" });
const resp = await fetch(url, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body });
const list = (await resp.json()).showapi_res_body.list;
cache.set(date, list);
setTimeout(() => cache.delete(date), ttlToMidnight());
return list;
}
```
## 返回示例与解析
缓存命中时直接返回上次存入的 `list` 数组(结构同[返回字段篇](https://www.showapi.com/guides/history-today-response-fields-119)),不再发请求。
## 进阶 / 边界
- **不要按"每次请求"缓存**:粒度应是 `date`,不是用户会话。同一天所有用户共享一份。
- **TTL 用"次日 0 点"**:比固定 24h 更准,避免跨日仍显示旧日期。
- **预热优于冷启动**:服务端定时任务在 0 点后主动拉一次,用户首次访问即命中,无首屏延迟。
- **档位未知不猜**:具体免费调用上限以官方档位说明为准;缓存正是为把调用次数压到"每天每日期望 1 次"。
## FAQ
**Q1:缓存 key 用什么?**
用 `date`(MMDD)。同一天结果稳定,按天缓存最合理。
**Q2:多久失效一次?**
建议到次日 00:00 失效,而不是固定 24 小时。
**Q3:多个服务器实例怎么共享缓存?**
用 Redis 等集中存储,key 统一为 `history:{date}`。
**Q4:免费接口还有必要缓存吗?**
有必要。防滥用档位 + 自身响应速度,都靠"每天每日期望只调一次"来保障。
## 相关能力 / 下一步阅读
- [历史上的今天:date 参数用法(MMDD 格式、默认今天、跨年边界)实战指南](https://www.showapi.com/guides/history-today-date-param-119) — `date` 格式与默认值。
- [历史上的今天:教育/课堂场景如何集成历史事件 API?](https://www.showapi.com/guides/history-today-education-119) — 每日入库存哪里。
- [历史上的今天:网站/App 每日历史卡片组件集成指南](https://www.showapi.com/guides/history-today-widget-119) — 组件读缓存。
- **本系列共 10 篇**:查看[历史上的今天指南总目录](https://www.showapi.com/guides/history-today-guides-119)