资讯动态

用Python调用NASA公开API构建真实科学数据清洗与分析项目

发布时间:2026/10/15 20:11:32 来源:尧图企业网站定制
经常有读者问我想练 Python 数据分析但电商、金融类的数据集要么付费要么清洗起来特别费劲有没有数据免费、接口稳定、还自带科学感的练手项目我的答案通常是 NASA 公开 API。没错就是那个做航天探索的研究机构它把每日天文影像、近地小行星轨道、火星车拍摄的照片、地球高清影像等数据都做成了公开接口。你只要会用 Python 的requests发起 HTTP 请求再用pandas做解析清洗就能拿到一份平时根本接触不到的真实科学数据。这篇文章就围绕“用 Python 读取和处理 NASA 公开 API 数据”这条主线从环境准备讲到清洗落盘再讲到定时采集和踩坑排查全程给出可直接复跑的代码。1. 为什么拿 NASA 公开 API 当练手数据源1.1 接口数量与内容盘点NASA 的开放数据平台统一入口是 api.nasa.gov所有接口共用同一个 API Key 体系交互方式就是标准的 REST JSON。这意味着你不需要理解复杂协议只要会拼 URL、会解析字典就能开始干活。目前比较常用、也最适合数据练手的接口有这几个接口名称数据内容典型场景APOD 每日天文图每天一张天文影像及专业文字说明入门第一个请求、图片元数据解析Asteroid NeoWs近地小行星的轨道、尺寸、速度、接近时间嵌套 JSON 清洗、时间序列分析Mars Rover Photos火星车按 sol火星日或地球日期拍摄的照片多参数筛选、批量下载EPIC 地球影像从卫星视角拍摄的地球多波段影像时间轴数据、图像处理TLE 卫星轨道数据低轨卫星的两行轨道根数航天计算、时间格式处理这些接口看起来都带着“科学光环”但数据结构并不复杂大多是字典套列表、列表套字典的模式恰好是练习数据清洗的绝佳材料。尤其是 NeoWs 的返回结构嵌套层数多、字段单位杂公里、英里、秒差距都有比手工编造的练习数据有意思多了。1.2 这套数据源适合谁、能练到什么如果你正在学 Python 数据分析做项目最怕什么怕数据是“假”的。NASA 的数据是科学探测产生的真实记录清洗完之后你会发现字段之间确实存在规律。比如小行星的直径和它的运动速度、轨道周期之间有明显相关性这种真实感是任何模拟数据集都给不了的。这套数据源还特别适合三类人一是刚学完requests和pandas基础语法、需要项目串联知识的新手二是做数据可视化想找免费且版权干净的数据源的爱好者NASA 数据公开允许非商业用途使用三是对天文、航天感兴趣、想用代码“够到”真实航天数据的业余玩家。我自己就是从 APOD 接口开始一步步把整套接口全跑了一遍过程中踩的坑几乎都集中在限流、参数格式和字段结构上后面会详细拆开讲。2. 申请 Key 和搭建环境提前一小时后面省一天2.1 注册与配额理解访问 api.nasa.gov 后点申请键填一个邮箱就能拿到专属 API Key。官方也提供了公共的演示密钥 DEMO_KEY但配额非常低大概在每小时几十次请求的级别做严肃练习我建议直接申请正式 Key免费、秒批配额会高很多。拿到 Key 后先理解配额的含义每个 Key 在单位时间内能发出的请求数有限一旦超过就会返回 429 状态码提醒你“慢一点”。很多人第一次跑批量采集任务时连续请求几百次结果后半段几乎全是 429这就是没做限速管理。我的习惯是单次请求之间至少间隔 0.5 到 1 秒并且把抓到的数据及时缓存到本地具体策略后面专门讲。2.2 本地环境三件套我的建议是用虚拟环境隔离依赖避免不同项目的包互相冲突。新建项目目录后执行python3 -m venv nasa-env source nasa-env/bin/activate pip install requests pandas matplotlib python-dotenv这里必须装的四个库各有分工requests负责发 HTTP 请求pandas负责把 JSON 转成规整的表格matplotlib用来画基础图表python-dotenv用来读取.env配置文件把 API Key 从代码里剥离出去。如果你只想先跑通最小的例子装requests和pandas就够了后面两个是数据处理和可视化的加分项。2.3 Key 别硬编码用环境变量管理新手最容易犯的错就是把 API Key 直接写死在脚本里然后不小心把脚本传到了公开仓库。这个 Key 虽然不涉及付费但它关联你的邮箱和请求配额被陌生人刷爆了你自己的额度就很冤了。我常用的做法是在项目根目录建一个.env文件内容只有一行NASA_API_KEY你的密钥然后在代码里这样读取import os from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(NASA_API_KEY)这样一来代码本身是干净的密钥只存在本地文件里。配合.gitignore把.env排除在版本管理之外既安全又省心。注意.env文件不要加引号不要有多余空格读取不到时先检查文件路径是否在 Python 的工作目录下。3. 请求数据只是开始从 JSON 到 DataFrame 的完整链路3.1 先拿 APOD 跑通第一行请求所有接口里最友好的是 APOD返回结构简单适合验证整条链路是否通畅。用日期参数指定某一天代码长这样import requests from dotenv import load_dotenv import os load_dotenv() API_KEY os.getenv(NASA_API_KEY) url https://api.nasa.gov/planetary/apod params { api_key: API_KEY, date: 2024-06-01, } resp requests.get(url, paramsparams, timeout10) data resp.json() print(data[title]) print(data[url]) print(data[explanation][:80])这里有两个容易忽略的细节第一请求必须带timeout参数否则网络异常时脚本会一直挂起看起来像死循环第二params里放 API Key 而不是拼在 URL 里requests会自动做 URL 编码省去手工拼接的麻烦。跑通之后把代码里的date换掉就能拿到任意日期的天文影像信息。3.2 封装一个带重试的请求函数跑通单次请求只是第一步。实际要批量拉数据时网络抖动、服务端限流都会让请求失败如果每次失败都得手动重跑效率太低了。我习惯把请求封装成一个独立函数内置指数退避重试import time import requests def nasa_get(url, paramsNone, max_retries3): for attempt in range(max_retries): try: resp requests.get(url, paramsparams, timeout15) if resp.status_code 429: wait 2 ** attempt print(f触发限流等待 {wait} 秒后重试) time.sleep(wait) continue resp.raise_for_status() return resp.json() except requests.RequestException as exc: if attempt max_retries - 1: raise wait 2 ** attempt 0.5 time.sleep(wait) data nasa_get(https://api.nasa.gov/planetary/apod, {api_key: API_KEY, date: 2024-01-01})指数退避的意思是第一次失败等 2 秒第二次等 4 秒第三次等 8 秒给服务端留出恢复时间。这是控制客户端对公网 API 请求的基本礼仪也是规范化脚本和“一把梭代码”的核心区别。注意捕获异常时我单独处理了429因为限流属于可恢复状态和真的断网、DNS 解析失败性质不同。3.3 摸清 NeoWs 的 JSON 结构再动手APOD 结构太简单练不到嵌套解析。真正值得上手的是小行星接口 NeoWs返回结构长这样{ near_earth_objects: { 2024-06-01: [ { id: 123456, name: (2024 ABCD), estimated_diameter: { kilometers: { estimated_diameter_min: 0.12, estimated_diameter_max: 0.28 } }, close_approach_data: [ { close_approach_date: 2024-06-01, relative_velocity: { kilometers_per_second: 5.67 }, miss_distance: { kilometers: 4300000.0 } } ], is_potentially_hazardous_asteroid: false } ] } }看到这个结构第一反应不应该是直接写解析代码而是先搞清楚三个关键点第一外层near_earth_objects是字典键是日期字符串值是对应当天的小行星列表同一天的数据要按列表遍历第二estimated_diameter下面分公里、英里、英尺多个单位体系每个单位体系里又有最小和最大两个值第三close_approach_data是列表里面大概率只有一个元素但严谨的脚本必须按列表处理不能直接取下标 0。我自己的经验是拿到陌生接口的返回后先打印一屏原始 JSON 看结构别急着写字典取值。可以用print(json.dumps(data, indent2))格式化输出或者先把返回体保存成.json文件用编辑器查看。这个习惯能帮你省掉大量调试时间。4. 小行星数据清洗实战展平、筛选、落盘与绘图4.1 把嵌套字段展平成行记录理解了结构之后清洗工作就顺理成章了。把 7 天的小行星数据拉下来然后展平成一行一条记录的表格url https://api.nasa.gov/neo/rest/v1/feed params { api_key: API_KEY, start_date: 2024-06-01, end_date: 2024-06-07, } data nasa_get(url, params) rows [] for date_str, asteroids in data[near_earth_objects].items(): for ast in asteroids: approach ast[close_approach_data][0] rows.append({ date: date_str, name: ast[name], diameter_km: ast[estimated_diameter][kilometers][estimated_diameter_max], approach_date: approach[close_approach_date], miss_distance_km: float(approach[miss_distance][kilometers]), velocity_kms: float(approach[relative_velocity][kilometers_per_second]), is_hazardous: ast[is_potentially_hazardous_asteroid], }) import pandas as pd df pd.DataFrame(rows) print(df.head())这段代码的关键在于单位转换接口返回的速度和距离是字符串类型进入 DataFrame 之前必须float()转成数值否则后续做排序、求均值时会出现类型错误。直径字段我取的是最大值你也可以同时保留最小值和最大值做区间分析。4.2 单位换算与潜在危险小行星筛选NASA 提供的单位体系非常多不换算直接看数字很容易产生误解。比如miss_distance同时给了公里、英里、天文单位、月球距离我通常只保留公里并用float转换。速度字段relative_velocity则同时有“公里/秒”和“公里/小时”如果要做动能分析必须先统一成同一套单位体系。清洗完成后一个很直观的分析是查看潜在危险小行星的规律hazardous df[df[is_hazardous]] print(f潜在危险小行星数量{len(hazardous)}) print(hazardous.sort_values(miss_distance_km, ascendingTrue) .head(10)[[name, miss_distance_km, diameter_km, velocity_kms]])按“最接近地球”排序后你会发现危险小行星里确实有不少直径较大、速度较快的个体。这里的核心训练点是先明确分析目标再决定筛选用哪些字段和条件而不是把全部字段一股脑丢进 DataFrame。4.3 统计汇总、CSV 导出和基础可视化处理好单表之后可以做聚合统计比如看看这七天里每天新增发现的小行星数量daily_count df.groupby(date).size() print(daily_count)导出 CSV 也很简单df.to_csv(asteroids_2024_06.csv, indexFalse, encodingutf-8-sig)utf-8-sig是给 Excel 用户准备的小细节直接双击 CSV 时不会出现中文乱码。如果只是给代码读默认的utf-8就够了。画图方面用matplotlib画一个直径与速度的散点图能直观看出两者关系import matplotlib.pyplot as plt plt.figure(figsize(8, 5)) plt.scatter(df[diameter_km], df[velocity_kms], cdf[is_hazardous], cmapviridis, alpha0.7) plt.colorbar(labelis_hazardous) plt.xlabel(直径公里) plt.ylabel(速度公里/秒) plt.title(近地小行星直径与速度分布) plt.savefig(asteroid_scatter.png, dpi150) plt.show()到了这一步从接口拉数据、JSON 解析、字段清洗、表格落盘、可视化输出就形成了一个完整的数据处理闭环。我建议大家亲手把这条链路跑通因为后续不管换成哪个接口核心代码骨架都是这个套路。5. 我踩过的高频报错限流、参数和字段变动的完整排查链路5.1 HTTP 429 限流别硬冲退避重试加缓存遇到 429 太常见了。现象是脚本跑着跑着开始连续输出 429 状态码最后直接抛异常。原因就是单位时间内请求次数超过了配额。我第一次批量拉取近 30 天小行星数据时压根没考虑配额脚本跑一半就全红了。正确的排查链路是这样先看状态码确认是 429 而不是 500。429 表示服务端明确告诉你“请求过多”不是你的代码逻辑错误。然后退避重试等待时间用 2 的指数次幂递增。最后是从源头解决问题——把已经请求过的数据存到本地缓存避免重复请求同一份数据。三步走完限流基本就不会再成为瓶颈了。5.2 400 参数错误先打印实际请求 URL参数错误是最容易自己解决的但很多人不知道怎么看。比如小行星接口的start_date和end_date中间跨的天数最多是 7 天超过就返回 400。再比如日期格式必须是YYYY-MM-DD写成2024/06/01也会报错。我排查这类问题的固定套路是打印resp.request.url看实际发出的 URL 长什么样。往往答案一眼就能看出来——要么日期格式不对要么某个参数名拼错了要么 API Key 没生效。requests的params会自动把参数编码进 URL直接打印请求对象就能看到完整的拼接结果这比盯着代码猜高效得多。5.3 KeyError 字段缺失先看原始 JSON 再写解析逻辑另一个高频坑是KeyError或IndexError。现象可能是close_approach_data列表是空的直接取[0]就崩了也可能是某个小行星的直径字段单位体系里没有你想要的值。这类问题的根子都在于你假设字段必然存在但真实数据往往有缺失或结构变化。我后来养成了习惯先随机抽几个对象打印原始 JSON确认所有分支都覆盖到了再写解析代码。对于空列表先用if approach_data:判断再取值对于缺失字段用ast.get(xxx, None)给默认值。排查的思路就是不要相信“所有记录都一样”每条记录都要按独立个体对待。用一个统一的flatten_asteroid函数把单条对象解析逻辑隔离出来遇到脏数据时单独调试比在循环里层层排查舒服很多。6. 从一把梭到长期跑缓存、定时任务与更多接口玩法6.1 本地 JSON 缓存节省配额也方便离线调试采集类的项目最忌讳每次分析都重新拉一遍接口。我通常把原始响应按请求参数缓存到本地文件名里带上日期或请求摘要import hashlib import json from pathlib import Path cache_dir Path(./cache) cache_dir.mkdir(exist_okTrue) def cached_get(url, params): key hashlib.md5((url str(sorted(params.items()))).encode()).hexdigest() cache_file cache_dir / f{key}.json if cache_file.exists(): return json.loads(cache_file.read_text(encodingutf-8)) data nasa_get(url, params) cache_file.write_text(json.dumps(data, ensure_asciiFalse), encodingutf-8) return data缓存的好处不只是省配额更重要的是离线可复现数据源没有变动时分析结果不会因为接口偶尔抽风而改变。注意缓存键要包含 URL 和所有参数否则换成不同日期后可能读到旧数据。6.2 定时采集的轻量方案如果你想把小行星数据长期积累下来做成一个趋势库可以加一层定时调度。最轻量的方式是schedule库import schedule def daily_job(): data cached_get(https://api.nasa.gov/neo/rest/v1/feed, {api_key: API_KEY, start_date: 2024-06-01, end_date: 2024-06-07}) # 解析并追加到 CSV print(数据更新完成) schedule.every().day.at(09:00).do(daily_job) while True: schedule.run_pending() time.sleep(60)定时任务的重点是任务本身必须幂等即重复执行不会产生重复数据。我的做法是先查 CSV 里已有的日期范围只拉缺失的那几天避免每天往同一日期追加数据造成重复。每天只请求一次接口配额消耗很可控。6.3 进阶路线火星照片、地球影像与卫星 TLE当你把小行星这条线跑顺之后可以横向扩展到其他接口。火星车照片接口就是一个很好的下一个项目核心参数是rover、sol或earth_date以及相机代号url https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos params { api_key: API_KEY, sol: 1000, camera: fhaz, } photos nasa_get(url, params) for p in photos[photos][:10]: print(p[earth_date], p[img_src])拿到img_src之后你可以用requests下载图片再做批量缩略图或图像分类练习。EPIC 接口则返回地球影像的元数据时间维度跨度大适合做时间序列分析。TLE 接口的数据更专业适合对航天轨道计算有兴趣的读者。这些接口的共同点是返回结构清晰、字段说明完善。官方文档里每个接口都附有示例返回体建议动手之前先对照文档理解一遍再决定解析脚本的结构。我现在做新接口的接入流程基本固定读文档 → 看返回体 → 写最小请求 → 逐层展平 → 落盘验证整个流程下来很少再翻车。分享几个我切身实践后的习惯第一公网的开放接口是公共资源哪怕官方没有严格要求我也会在请求之间加至少 0.5 秒的间隔既是对平台的尊重也是让自己养成规范编码的习惯第二所有解析代码尽量写成独立函数配合缓存机制遇到数据异常时只需要重跑一个函数不用从零开始第三把每天的采集结果归档成带日期的文件一个月后再回头看你会发现积累的数据带给你很多“临时拉取”看不到的长期趋势。这套 API 接入流程不仅适用 NASA换成其他提供 JSON 接口的公共服务同样成立趁手的工具和方法是会迁移复用的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑