每天一张宇宙壁纸:Cloudflare Workers + NASA APOD 实战
每天一张宇宙壁纸:Cloudflare Workers + NASA APOD 实战
zhangchangsheng每天打开电脑时,与其继续面对那张看腻的系统壁纸,不如让一团星云、一轮日食或者一张深空照片来接班。
这篇文章会做一个很小但完整的服务:Cloudflare Workers 负责对外提供 HTTP 接口,Cron Trigger(定时触发器)每天向 NASA 拉取一次 APOD,Workers KV(边缘键值存储)保存最近一次成功结果。访问 / 可以得到结构化 JSON,访问 /image 会跳转到当天的高清图片;如果当天内容是视频或没有可确认的图片地址,接口会明确返回降级信息,而不是把一个网页地址伪装成图片。
最终数据流如下:
Cron Trigger ──每天触发──> Worker ──HTTPS──> NASA APOD API |
本文资料与代码按 2026-09-17 的官方状态核对。APOD 正处在一次重要迁移中,因此先把最容易踩坑的地方说清楚。
先说结论:APOD 正在迁移,但旧入口目前仍可用
APOD 是 Astronomy Picture of the Day,即“每日天文图片”。NASA Open APIs 目前仍列出这个入口:
GET https://api.nasa.gov/planetary/apod |
不过,NASA 官方 nasa/apod-api 仓库 在 2026-09-10 更新说明:后端已经改为转接新的 WordPress Endpoint(WordPress 接口),仓库计划在 2026-12-01 归档并指向新端点;现阶段调用方不需要立刻修改请求地址。仓库文档已经展示 permalink、post_id、basic_html_url 等新返回字段。
与此同时,本文在 2026-09-17 实际核对 api.nasa.gov/planetary/apod 时,实时响应仍是大家熟悉的结构:包含 date、title、explanation、media_type、url、hdurl 和 service_version,其中 url 仍可能是图片地址。这说明迁移期间“官方仓库描述的新结构”和“线上某次实际响应”可能暂时不同。
因此,稳妥做法不是赌某一套结构永远不变,而是:
- 请求地址继续使用当前 NASA Open APIs 公布的
api.nasa.gov/planetary/apod; - 图片优先使用官方定义为高清图片的
hdurl; - 仅当
media_type为image,且旧式url明显是图片文件时,才把url当作图片后备地址; - 将
permalink或非图片型url视为 APOD 详情页; - 遇到
video、iframe或无法确认的结构时保留元数据并降级,不抓取 HTML 猜图片。
后面的代码已经实现这套防御式解析。将来 NASA 完成切换时,大概率只需调整解析函数,而不必推翻整个 Worker。
1. 成品会提供什么
部署完成后,Worker 有三个行为:
GET /:返回最新 APOD JSON,包括标题、日期、说明、署名、媒体类型、图片地址、详情页和抓取时间;GET /image:图片可用时返回307跳转,可直接交给壁纸脚本;当天不是图片时返回409和详情页地址;- Cron Trigger:每天更新一次 KV。NASA 暂时不可用时,写入不会发生,KV 中上一份成功数据仍然保留。
它不会把 NASA 图片复制进 Cloudflare,也不会公开 NASA API Key。Worker 只保存元数据并把客户端重定向到原始图片地址,结构简单,也便于后续改成自己的前端页面。
2. 前置条件
你需要:
- 一个 Cloudflare 账号,并已启用
workers.dev子域名; - 本机安装 Node.js 与 npm;Cloudflare 当前按 Node.js 官方生命周期支持 Current、Active LTS 和 Maintenance LTS 版本,建议直接使用最新 LTS,详见 Wrangler 系统要求;
- 一个自己的 NASA API Key;
- 能使用终端编辑 TypeScript 文件。
本文使用 ES Modules(ES 模块)形式的 Worker,并选择 wrangler.jsonc。Cloudflare 当前明确推荐新项目使用 wrangler.jsonc,而不是旧教程里常见的 wrangler.toml;JSONC 是允许注释的 JSON。
3. 申请 NASA API Key
打开 NASA API Key 注册页,填写姓名和邮箱后获取密钥。测试文档时可以使用 DEMO_KEY,正式部署不要这样做。
NASA 的官方认证说明给出的默认限制是:普通 API Key 每小时 1,000 次,请求量在所有 api.nasa.gov API 之间合并计算;DEMO_KEY 则是每个 IP 每小时 30 次、每天 50 次。响应头中的 X-RateLimit-Limit 和 X-RateLimit-Remaining 可用于观察当前额度。
我们的设计通常每天只由 Cron 请求一次 NASA,KV 首次为空时才会由 HTTP 请求补拉一次,因此不会让每一位访客都消耗 NASA 额度。
4. 初始化 Worker 项目
使用 Cloudflare 官方的 C3(create-cloudflare 命令行脚手架)创建项目:
npm create cloudflare@latest -- nasa-apod-wallpaper |
交互选项选择:
Hello World example;Worker only;TypeScript;- 是否初始化 Git 按个人需要选择;
- 暂时不要部署。
完成后主要会用到 src/index.ts、wrangler.jsonc 和 package.json。
5. 配置 wrangler.jsonc
将 wrangler.jsonc 改为:
{ |
这里没有漏写 KV 的 id。Wrangler 当前支持自动配置资源:在绑定中只声明 APOD_CACHE,首次通过 Wrangler 部署时会自动创建 KV namespace(KV 命名空间),并把真实资源 ID 回写到配置文件。这个能力目前标记为 Beta,所以请把部署后回写的 wrangler.jsonc 一并保存。
如果你的账号或 Wrangler 版本没有启用自动配置,按 Workers KV 官方入门执行 npx wrangler kv namespace create APOD_CACHE,再把命令实际返回的 ID 加到这个绑定中,不要照抄别人的 ID。
Cron 的 UTC 时区
Cloudflare Cron Triggers 的表达式固定使用 UTC:
5 13 * * * |
表示每天 13:05 UTC 运行,对应中国标准时间(UTC+8)的 21:05。它不是“服务器本地时间”,也不会自动按你的浏览器时区换算。如果想在中国标准时间早上 08:30 更新,应写成 30 0 * * *。
APOD 的实际上线时间由 NASA 决定,本文不假设它一定在某个整点发布。这里选择晚间更新只是一个实用默认值;如果你观察到更新偏晚,可以调整 Cron,但不要高频轮询。
6. 配置 Secrets
Secret(密钥绑定)是 Cloudflare 为 API Key 等敏感值提供的加密配置。不要把 NASA Key 写进 wrangler.jsonc 的 vars,也不要提交到 Git。
在项目根目录创建 .dev.vars,仅写入 NASA_API_KEY 这一项,并把等号右侧填写为你从 NASA 邮件复制的真实密钥。它使用 dotenv 格式。确认 .gitignore 包含:
.dev.vars* |
.dev.vars 只供本地开发使用。Cloudflare 的Secrets 官方文档也明确要求不要提交 .dev.vars 或 .env。
第一次部署时,我们会用 --secrets-file .dev.vars 同时上传代码和 Secret。以后需要轮换密钥,可以运行:
npx wrangler secret put NASA_API_KEY |
命令会交互式读取真实密钥,不会把值留在 shell 历史中。当前 Wrangler 执行 secret put 会创建并立即部署一个新 Worker 版本,操作前要知道它不是纯粹的本地配置命令。
7. 完整 Worker TypeScript 代码
用下面内容替换 src/index.ts:
interface Env { |
这段代码同时导出了 fetch() 和 scheduled():前者处理 HTTP,后者由 Cron 调用。两条路径复用同一个 fetchAndStoreApod(),避免“手动访问能用、定时任务却走另一套逻辑”。
还有三个值得保留的细节:
- NASA API Key 只用于 Worker 到 NASA 的请求,不出现在响应和日志中;
- NASA 返回非
2xx或结构缺少关键字段时先抛错,不用坏数据覆盖 KV; explanation和credit在迁移后的结构中可能含 HTML。本文只把它们作为 JSON 字符串返回;如果做网页,请先清洗,或只用textContent渲染,不能直接塞进innerHTML。
8. 为什么使用 KV,而不是只用 Cache API
Cloudflare Cache API(缓存接口)看起来也能 put() 和 match(),但它的缓存内容不会在各数据中心之间复制。Cron 在某个数据中心写入 caches.default,并不能保证另一地区的访客命中同一份缓存。因此“定时任务预热一次,全球都能读到”是错误假设。
Workers KV 更适合这个场景:一天写一两次、读取很多次。它有中心存储并在各地缓存。不过,KV 是 eventual consistency(最终一致性),官方说明其他地区可能需要 60 秒或更久才能看到新值,负查询也会被缓存。详见 KV 工作原理。壁纸晚一分钟更新通常可以接受;若业务要求严格的读写一致性,应改用 Durable Objects,而不是假装 KV 是强一致数据库。
HTTP 响应里的 Cache-Control 则是另一层:它允许浏览器或按你规则配置的 CDN 缓存 JSON,减少重复读取。它不等于 KV,也不能保证 workers.dev 上的 Worker 永远不会执行。若以后给自定义域名配置 Cache Rule,还要确保缓存键不包含任何 Secret,并接受最多一小时的响应陈旧窗口。
9. 本地测试
先生成与当前配置、兼容日期匹配的类型:
npx wrangler types |
再启动本地开发服务器:
npx wrangler dev |
Wrangler 会加载 .dev.vars,并在本地模拟 KV。Cloudflare 的本地开发说明指出,本地绑定默认与生产资源隔离,因此第一次访问会发现本地 KV 为空,随后请求 NASA 并写入本地数据,这是正常现象。
另开一个终端依次检查:
curl --fail --silent --show-error localhost:8787/ |
预期行为不是某个固定标题,而是:
/返回合法 JSON,且source_date、title、media_type、fetched_at存在;- 图片日访问
/image得到307和Location;非图片日得到409JSON; - 本地 scheduled 测试返回的
outcome为ok,终端出现刷新日志。
不要把某一天的标题或图片 URL 写进自动化断言,因为 APOD 本来就会变化。若只想检查 TypeScript,可使用项目生成的类型检查脚本;若脚手架没有提供,再运行:
npx tsc --noEmit |
10. 部署与验收
第一次部署时,让 Wrangler 从 .dev.vars 同时上传 Secret:
npx wrangler deploy --secrets-file .dev.vars |
Wrangler 会登录 Cloudflare、部署 Worker、自动配置 APOD_CACHE,并输出真实的 HTTPS 访问地址。自动配置成功后,检查 wrangler.jsonc,其中应已写入 KV 的真实 id。以后的普通代码部署运行:
npx wrangler deploy |
已经配置的 Secret 会保留,不需要每次重新输入。
把 Wrangler 输出的真实地址保存到当前 shell 的 WORKER_URL 变量后验收:
curl --fail --silent --show-error "$WORKER_URL/" |
检查 JSON 中的 source_date 与 fetched_at,不要只看 HTTP 200。/image 在图片日应跳转到 HTTPS 图片地址;非图片日的 409 是设计行为,不是故障。
Cron 配置变更在 Cloudflare 全球网络传播可能需要数分钟,官方文档给出的上限是 15 分钟。可在 Cloudflare Dashboard 的 Worker 页面进入 Settings → Trigger Events → View events 查看最近的 Cron 调用,也可以运行:
npx wrangler tail |
等待下一次计划触发时观察 APOD cache refreshed 日志。不要为了“验收”把生产 Cron 改成每分钟运行后忘记改回来。
11. 常见错误与排查
NASA 返回 401、403 或 Worker 返回 502
先确认 .dev.vars 和生产 Secret 的变量名都精确写成 NASA_API_KEY。如果刚轮换密钥,重新执行 npx wrangler secret put NASA_API_KEY。不要把 Key 放在公开 URL、前端 JavaScript 或 Git 仓库里。
NASA 返回 429
这是限流。停止高频刷新,检查 X-RateLimit-Remaining,并确认没有在生产使用共享的 DEMO_KEY。本文的 / 会优先读 KV;如果你自行删除 KV 读取逻辑,就会让每位访客都直连 NASA。
/image 返回 409
先看响应里的 media_type 和 page_url。APOD 不保证每天都是静态图片,也可能是视频或 iframe;迁移中的新结构也可能只提供文章页。正确做法是打开详情页,而不是把页面 HTML 当图片下载。
本地能读,线上 KV 却是空的
wrangler dev 默认使用本地模拟 KV,生产 KV 是另一份数据。查看线上数据时给 Wrangler 命令加 --remote。反过来,本地开发不要随意启用远程绑定,否则测试写入会改动生产数据。
Cron 没有按北京时间执行
Cron 永远按 UTC。把中国标准时间减去 8 小时再写表达式,并等待最长 15 分钟的配置传播时间。Dashboard 手动创建和 wrangler.jsonc 同时管理也容易互相覆盖;使用 Wrangler 的项目应以配置文件为唯一来源。
更新后短时间仍读到旧数据
先看 fetched_at。KV 在全球是最终一致的,新值传播可能超过 60 秒;浏览器还可能在 max-age=300 内复用响应。这类短暂陈旧是当前缓存策略的预期代价。
NASA 改了字段,日志出现缺少 date 或 title
先查看 NASA 官方 APOD API 仓库的最新迁移说明和实际响应,再只修改 normalizeApod()。不要直接删除校验让未知结构进入 KV,也不要依赖非官方代理来“修复”字段。
12. 成本与免费额度边界
截至 2026-09-17,Cloudflare 官方公布的 Workers 限制中,Free 计划包括每天 100,000 次请求、每次 HTTP/Cron 调用 10 ms CPU 时间,以及每个账号 5 个 Cron Triggers。等待 NASA 网络响应的时间不计入 CPU 时间,但 JSON 解析和代码执行会计入。
Workers KV 免费额度包括每天 100,000 次读取、1,000 次写入以及账号总计 1 GB 存储;同一个 key 的写入上限为每秒一次。本文默认每天约一次 NASA 请求、一次 KV 写入,每个未被客户端缓存的 HTTP 请求约一次 KV 读取,个人使用通常很轻。
不过,“通常在免费额度内”不是无限免费承诺:
- Worker 的公开 URL 被大量访问时,请求和 KV 读取都会增长;
- Cache 命中也可能计入 Workers 请求数,具体以 Workers Pricing和账号仪表盘为准;
- Cloudflare 与 NASA 都可能调整限制;
- 自定义域名、日志、其他 Worker 和 KV 项目会共享账号级额度。
当前 Workers Paid 计划的账号最低费用为每月 5 美元,超出包含量后再按请求和 CPU 用量计费;这个个人项目没有必要为了“更稳”而预先升级,先根据真实流量观察即可。
上线后至少偶尔查看 Workers Analytics、KV 用量和 NASA 限流响应头。对个人壁纸服务来说,控制访问范围比盲目增加缓存层更有效。
13. 图片版权、署名与“能看不等于能随便用”
APOD 是精选栏目,不是“全部图片都归 NASA 所有”的版权池。APOD 官方版权说明明确指出:每张图片都会标注所有者或机构;部分图片受版权保护,公开或商业使用时需要联系权利人,NASA 和 APOD 都不能替第三方授权。
NASA 自有内容通常可用于教育或信息用途,但仍应注明 NASA 为来源,不能暗示 NASA 为产品背书;含 NASA 标识、可识别人物或第三方素材时还有额外限制,详见 NASA Images and Media Usage Guidelines。
实际使用时建议:
- 壁纸客户端同时保留
credit、page_url和source_date; - 在公开网页展示图片时,把署名放在图片附近并链接回 APOD 详情页;
credit或copyright缺失不代表自动进入公有领域;- 商业展示、再分发或印刷前,逐张确认 APOD 页面上的权利人信息;
- 不要移除图片中已有的水印和署名。
这也是代码没有代理、裁切或永久复制图片的原因。技术上能下载,不等于法律上拥有再次传播的权利。
14. 还能怎样扩展
基础版本跑稳后,可以继续做三件事:
- 增加一个静态前端,以
textContent安全展示标题与说明,并在图片旁显示署名; - 以日期作为额外 KV key 保存有限历史,但要设置保留策略并计算写入、读取与存储额度;
- 给自己的壁纸脚本增加“非图片日不更换”和“下载失败保留旧壁纸”逻辑。
不建议马上做的事情是高频轮询、批量镜像全部历史图片,或者解析 APOD 页面 HTML 猜测资源地址。这个小服务的价值恰恰在于边界清楚:NASA 提供内容,Worker 负责定时获取与稳定分发,KV 保存最近一次成功状态,异常时宁可保留昨夜星空,也不制造一张坏壁纸。
参考资料
- NASA Open APIs
- NASA API Authentication and Rate Limits
- NASA APOD API 官方仓库
- NASA Science:About Astronomy Picture of the Day
- NASA Images and Media Usage Guidelines
- Cloudflare Workers:Get started - CLI
- Cloudflare Workers:Wrangler Configuration
- Cloudflare Workers:Secrets
- Cloudflare Workers:Cron Triggers
- Cloudflare Workers:Local development
- Cloudflare Workers KV:Getting started
- Cloudflare Workers KV:How KV works
- Cloudflare Workers KV:Limits
- Cloudflare Workers:Cache API
- Cloudflare Workers:Limits
- Cloudflare Workers:Pricing
