Python实战NASA开放API:数据采集、清洗与可视化指南 打开NASA开放数据平台的时候我其实有点意外。这个天天造火箭、发火星车的机构居然把大量科学数据整理成标准REST接口免费开放给所有人。不需要企业认证不需要复杂的OAuth授权流程注册一下拿个API Key就能开始调。对Python开发者来说这几乎是最好的练手项目接口设计规范、返回格式统一、数据类型足够丰富既有JSON又有图片流从入门到进阶都能找到对应的玩法。这篇文章我会把整个流程拆开讲清楚从注册API Key、理解接口规则到用requests拉数据、用pandas做清洗、再用matplotlib画几张能看的图最后聊一聊我在实际调用中踩过的坑。内容不深奥但足够落地适合想把Python网络请求和数据处理真正串起来的人。1. 项目整体思路为什么选NASA公开数据练手1.1 NASA API到底有哪些家底很多人听到NASA API会以为就一两个接口实际上官方开放平台api.nasa.gov下面挂了十几个不同类型的API覆盖天文、行星、地球观测、小行星监测等领域。我挑几个最常用、也最适合拿来练手的说一下。APODAstronomy Picture of the Day每日一张天文图片或视频附带一段科普说明返回数据简洁适合做第一个接口练手。Mars Rover Photos火星车在火星表面拍的照片支持按Sol火星日、地球日期、相机类型和火星车名称筛选。返回结果是带图片URL的JSON数组数据量很大。NeoWSNear Earth Object Web Service近地小行星数据包含直径估算、接近日期、速度、距地球距离等字段是结构最复杂的接口之一非常适合拿来练习嵌套JSON的解析。EPICEarth Polychromatic Imaging Camera地球影像数据每天多张高清地球照片返回数据里带有经纬度和拍摄时间适合做地理相关的数据处理。其他还有TLE卫星轨道数据、太阳活动数据、NASA图片库等。我的建议是刚上手不要贪多先把上面四个吃透每个接口的数据结构差异足够让你把Python请求、JSON解析、pandas处理这整套流程跑通。1.2 围绕这个项目Python这边该准备什么这个项目的技术栈非常标准核心就三个库。requests发起HTTP请求获取API返回的数据。相比Python标准库里的urllibrequests的API设计友好得多处理超时、请求头、连接池都更方便。pandas处理返回的JSON数据。NASA接口返回的JSON大多是嵌套结构直接转成DataFrame会遇到不少坑用json_normalize做展平处理会顺手很多。matplotlib把处理好的数据可视化画直方图、散点图、时间序列曲线。有人可能会问为什么不直接用爬虫框架比如Scrapy我的看法是NASA给你的是正儿八经的REST接口返回的是结构化JSON不需要你去解析HTML、处理反爬。用Scrapy属于杀鸡用牛刀requests一个循环就能解决的事没必要引入额外复杂度。这个项目真正考验的不是爬虫能力而是拿到数据之后怎么处理的能力。安装方面我建议直接用pip安装Python 3.9以上版本即可不需要额外配置虚拟环境以外的东西。pip install requests pandas matplotlib顺便说一句如果你用的是Anaconda发行版pandas和matplotlib通常已经装好了只需要补一个requests。2. 准备阶段注册API Key与接口规则2.1 申请API Key的完整过程NASA的API Key申请流程简单得不像一个航天机构做出来的东西。访问api.nasa.gov官网填一个表单内容包括你的邮箱、应用名称、简单描述为什么使用API。提交后几分钟内邮箱里就会收到一封带有Key的邮件。我建议不要在网页上直接复制Key然后粘贴到代码里。更好的做法是把Key写进环境变量或者存在项目根目录的.env文件中。代码里通过os.getenv(NASA_API_KEY)读取这样即使你把代码提交到公开仓库也不会泄露密钥。export NASA_API_KEY你的Key如果没有注册也没关系接口默认支持DEMO_KEY作为临时Key但共享的DEMO_KEY限速比较严格而且高峰期可能会出现限流。个人做项目还是建议花两分钟注册一个专属Key又不要钱没必要省这个步骤。注意千万不要把API Key硬编码到代码里尤其当你打算把项目开源的时候。Key泄露后别人可以用你的额度甚至导致你被限速。2.2 搞清楚NASA API的调用规则NASA API的整体规范是标准的REST风格所有接口的Base URL都是https://api.nasa.gov通过不同的路径区分资源Query参数传递筛选条件API Key统一通过api_key参数传递返回格式绝大部分是JSON。规则看起来简单但有几个细节值得留意。速率限制官方文档里写的是每小时请求次数限制注册Key一般比DEMO_KEY额度更高。不同接口的限速策略略有差异但总体策略是短时间内不要狂发请求。我实测下来个人学习用途基本碰不到限制但如果你写了个循环去批量拉数据就很有必要在请求之间加上sleep。URL长度限制Mars Rover Photos接口支持多个参数组合查询参数过多会导致URL过长。虽然requests没有强限制但部分代理服务器对URL长度有默认上限建议保持Query参数精简。返回数据分页NeoWS和Mars Photos接口都返回大量数据API通过分页参数控制每次返回的数量。如果不做分页处理就直接遍历最后拿到的可能只是第一页数据这个问题我在后面会专门展开讲。图片URL是独立的Mars Rover Photos和EPIC返回的图片链接指向另外一个域名images-api.nasa.gov或epic.gsfc.nasa.gov需要单独发请求下载图片。这一点很多新手会忽略以为一次请求就把图片数据也拿到了其实只拿到了元数据。3. 核心实操五段代码打通全流程3.1 第一段请求封装与基础数据获取APOD我习惯先封装一个通用的请求函数把API Key注入、超时处理、错误检查都收敛到一个地方。后续调用任何接口都复用这个函数代码结构会清爽很多。import os import time import requests API_KEY os.getenv(NASA_API_KEY, DEMO_KEY) BASE_URL https://api.nasa.gov def nasa_request(endpoint: str, params: dict | None None, timeout: int 30): url f{BASE_URL}/{endpoint} if params is None: params {} params[api_key] API_KEY try: resp requests.get(url, paramsparams, timeouttimeout) resp.raise_for_status() return resp.json() except requests.exceptions.HTTPError as e: print(fHTTP错误: {e}, URL: {resp.url}) raise except requests.exceptions.Timeout: print(f请求超时: {endpoint}) raise然后调用APOD接口这一步的核心是体验请求-解析-展示的完整链路。apod_data nasa_request(planetary/apod) print(f日期: {apod_data[date]}) print(f标题: {apod_data[title]}) print(f说明: {apod_data[explanation][:100]}...) print(f图片地址: {apod_data[url]})APOD返回的JSON结构简单明了没有嵌套直接按字段取就行。但这个接口有个容易忽略的点media_type字段可能是image也可能是video。如果你写代码自动下载图片必须判断一下media_type否则拿到一个视频URL却在用图片方式保存最后存下来一个打不开的文件。3.2 第二段近地小行星数据NeoWS与pandas处理接下来直接进入硬核部分。NeoWS的browse接口返回近地小行星列表数据结构是整个NASA API里数一数二的复杂嵌套层级深字段多。正好可以用它来练JSON展平。neo_data nasa_request(neo/rest/v1/neo/browse, params{page: 1}) objects neo_data[near_earth_objects] print(f本页返回: {len(objects)} 颗小行星) print(f总数: {neo_data[page][total_elements]})每条小行星记录的字段包括name、id、is_potentially_hazardous_asteroid是否潜在危险天体、estimated_diameter直径估算、close_approach_data接近地球数据。如果直接把这些数据塞进DataFrame你会发现DataFrame的一列里套着一整个字典根本没法分析。这时候需要用到pandas的json_normalize函数。它会自动把嵌套的字典和列表展平成扁平表格是处理NASA这类嵌套JSON的利器。import pandas as pd from pandas import json_normalize flat_df json_normalize( objects, record_pathclose_approach_data, meta[id, name, is_potentially_hazardous_asteroid, estimated_diameter], errorsignore )做完这一步DataFrame里就有了每颗小行星的ID、名称、是否危险、直径以及它每一次接近地球的日期、距离、相对速度。为什么用record_path还带着meta因为close_approach_data本身是列表一条小行星记录可以对应多次接近事件json_normalize会自动做一对多的展开。这样做出来的表格可以直接用于后续的时间序列分析和距离排序。3.3 第三段火星车照片检索与下载Mars Rover Photos接口能按Sol火星日或地球日期检索指定火星车拍的照片。我选择按Sol检索因为Sol是火星独有的计时单位从着陆日开始计算这样检索结果更规整。mars_data nasa_request( mars-photos/api/v1/rovers/curiosity/photos, params{sol: 1000, camera: rhaz} ) photos mars_data.get(photos, []) print(fSol 1000 由 RHaz 相机拍摄的照片数量: {len(photos)}) if photos: print(f第一张照片拍摄于: {photos[0][earth_date]}) print(f照片URL: {photos[0][img_src]})注意返回结果里photos字段可能是空列表说明当天或该Sol没有对应相机拍摄的照片。写代码时务必做空值检查不然索引0就会报IndexError。拿到img_src之后下一步是把图片下载到本地。这里有个容易被忽略的坑img_src指向的域名和api.nasa.gov不是同一个如果你带着API Key去请求图片URL反而会报错。正确做法是单独发一次GET请求下载图片流。def download_image(url: str, save_path: str): resp requests.get(url, timeout60) resp.raise_for_status() with open(save_path, wb) as f: f.write(resp.content)我一般把批量下载循环里加上time.sleep(1)无论对限速还是对目标服务器都友善一些。NASA虽然没明确要求但一次性请求几百张图还是节制一点好。3.4 第四段地球影像EPIC的坐标处理EPIC接口返回的是地球自然光的影像元数据字段里有centroid_coordinates包含地球影像中心点的经纬度。这部分数据比前两个接口更偏地理信息适合结合地图或坐标系做进一步分析。epic_data nasa_request(EPIC/api/natural, params{limit: 10}) for item in epic_data[:3]: date_str item[date][:10] img_name item[image] lat item[centroid_coordinates][lat] lon item[centroid_coordinates][lon] print(f拍摄时间: {date_str}, 中心点: ({lat:.2f}, {lon:.2f})) # EPIC图片URL有固定拼接规则 parts date_str.split(-) img_url ( fhttps://epic.gsfc.nasa.gov/archive/natural/ f{parts[0]}/{parts[1]}/{parts[2]}/png/{img_name}.png ) print(f图片地址: {img_url})EPIC接口返回的date字段是完整的ISO 8601时间字符串包含时分秒。图片存档路径却只用到年月日所以必须先截取前10个字符再拆分。这种API返回字段与资源寻址规则不一致的情况在实际开发中很常见处理思路就一句话先搞清楚资源在服务器上的组织规律再写拼接逻辑。我建议在处理EPIC数据时顺手把原始date字符串转成datetime对象存下来后面做时间序列排序会非常方便。from datetime import datetime for item in epic_data: item[date_obj] datetime.fromisoformat(item[date].replace(Z, 00:00))3.5 第五段把数据沉淀到本地文件API请求是即时的但数据分析和可视化往往需要在本地做多轮尝试。与其每次都重新请求接口不如把请求到的JSON原样保存后面全部基于本地文件工作。import json def save_json(data, filename): with open(filename, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def load_json(filename): with open(filename, r, encodingutf-8) as f: return json.load(f)保存到本地的好处有三个一是省流量、省请求额度二是调试速度快不需要每次跑代码都等网络三是数据结果可复现记录采集时间后以后可以对比不同日期的数据差异。我个人的习惯是每个接口的数据单独存一个文件文件名带上日期比如neo_objects_20250107.json。4. 数据处理进阶清洗、转换、可视化4.1 用pandas清洗NASA返回的半结构化数据NASA返回的JSON质量总体算高的但离直接可以分析还有一段距离问题主要出在几个地方。缺字段部分小行星记录没有完整的close_approach_data或者某些物理参数缺失。类型不一致直径估算是一个嵌套字典里面用英尺和公里两种单位分别存了最小值和最大值。日期格式不统一有的字段直接就是字符串不转成datetime没法参与时间计算。我总结了一套百试百灵的处理套路先用record_path和meta展平再统一把日期字段转成datetime再处理缺失值最后把分析需要的数值字段单独提取出来。# 从展平后的DataFrame中筛选出核心字段 analysis_df flat_df[[ name, is_potentially_hazardous_asteroid, estimated_diameter.kilometers.estimated_diameter_max, close_approach_date, relative_velocity.kilometers_per_second ]].copy() analysis_df[close_approach_date] pd.to_datetime(analysis_df[close_approach_date]) analysis_df analysis_df.dropna(subset[estimated_diameter.kilometers.estimated_diameter_max])这里有个细节estimated_diameter后面跟着的是一长串带点的字段名这是json_normalize默认的嵌套列命名规则。点号在DataFrame里是合法字符但操作起来麻烦我习惯用rename统一改短。analysis_df analysis_df.rename(columns{ estimated_diameter.kilometers.estimated_diameter_max: diameter_km_max, relative_velocity.kilometers_per_second: velocity_kps })4.2 基于matplotlib做几张能看的图数据处理最终要落到可视化上。我的经验是NASA数据本身话题性强不用太多花哨的技巧几个基础图表就能出效果。先看小行星直径分布用直方图import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 显示中文按需配置 plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 6)) ax.hist(analysis_df[diameter_km_max], bins30, edgecolorblack, alpha0.7) ax.set_xlabel(最大直径估算公里) ax.set_ylabel(小行星数量) ax.set_title(近地小行星直径分布) ax.grid(axisy, linestyle--, alpha0.5) plt.tight_layout() plt.savefig(neo_diameter_distribution.png, dpi150) plt.show()再画一张接近日期时间序列图把未来几年内接近地球的小行星全部标出来能很直观看到哪些时间点附近会有密集的到访。fig, ax plt.subplots(figsize(12, 5)) ax.scatter( analysis_df[close_approach_date], analysis_df[diameter_km_max], sanalysis_df[velocity_kps] * 10, alpha0.5, corange, label点大小相对速度 ) ax.set_xlabel(接近日期) ax.set_ylabel(小行星最大直径公里) ax.set_title(近地小行星接近地球事件时间分布) plt.xticks(rotation45) ax.legend() plt.tight_layout() plt.savefig(neo_approach_timeline.png, dpi150) plt.show()散点图上点的面积映射相对速度一眼就能看出哪些天体又快又大是作图上很实用的小技巧。提示如果你在无图形界面的服务器上跑代码plt.show()这一步会报错。可以改成plt.savefig()之后用plt.close(fig)释放内存或者直接用Agg后端。5. 实战中的坑错误码、限速与数据质量问题5.1 常见HTTP状态码逐一说清楚调用NASA API过程中遇到的状态码就那么几个我把含义和排查思路整理成一张速查表。状态码含义常见原因处理建议200 OK请求成功无无400 Bad Request参数不合法日期格式写错、sol超出范围、camera名称拼错检查API文档的枚举值403 Forbidden禁止访问API Key被禁用或个人Key超过月配额去官网检查Key状态404 Not Found资源不存在endpoint拼写错误或请求了不存在的照片ID检查路径是否和文档一致429 Too Many Requests请求过于频繁触发限速查看Retry-After头增加sleep时长500 Internal Server Error服务器内部错误NASA侧服务暂时异常做好重试间隔几秒再试429是我遇到最多的状态码。实际项目中我一般写一个带重试的循环遇到429不是直接退出而是等待一段时间重新请求。NASA的响应头里通常带Retry-After字段会明确告诉你要等多少秒尊重这个时间间隔就不会越等越长。def nasa_request_with_retry(endpoint, paramsNone, max_retries3): for attempt in range(max_retries): try: return nasa_request(endpoint, params) except requests.exceptions.HTTPError as e: if e.response.status_code 429: wait int(e.response.headers.get(Retry-After, 60)) print(f触发限速等待 {wait} 秒) time.sleep(wait) continue raise raise RuntimeError(重试多次仍未成功)5.2 限速与重试策略怎么设计NASA的限速不像一些商业API那么激进但也不能不管。我建议在批量请求场景下每两个请求之间至少间隔0.5到1秒这比等触发了429再回头等待更顺滑。另一个策略是分级重试。第一次失败后隔1秒重试第二次隔5秒第三次隔30秒。这种指数退避的策略虽然简单但在实际调用里非常管用不仅能解决429也能应对偶尔出现的500错误。5.3 关于NASA其他开放数据的一点补充除了api.nasa.gov的REST接口NASA官网也有不少非API形式的开放数据。比如NASA锂电池数据集Battery Data Set里面包含不同工况下锂电池充放电循环的电流、电压、容量变化数据格式是Excel文件需要自己下载后读取。这个数据集在电池寿命预测领域很出名处理思路和API数据类似读取文件、清洗、特征提取、建模。如果你在API项目之外还想练一练从文件到分析的完整链路它是个不错的后续选择。另外气候研究方向的读者可能听过CMIP6数据。这套数据体量极大动辄几十GB直接放进pandas处理不现实通常需要专门工具读取NetCDF格式。如果你对气候数据感兴趣建议先掌握xarray这个库它是处理多维科学数据的标准工具和pandas的关系可以理解成pandas的N维版本。这里先提个方向具体细节展开又是一篇长文了。6. 数据处理与认知升级API只是起点6.1 用NASA数据练项目练的到底是什么很多人以为这个项目练的是requests用法但真正有价值的是后面那几步理解接口文档、设计健壮的请求层、处理嵌套JSON、应对限速和异常、把数据沉淀成结构化表格。这五步能力在任何一个真实的API项目里都用得上不管是企业内部的业务系统还是第三方的开放平台。经常有人问接口文档看得懂但拿到JSON不知道下一步干嘛。我的建议是先别急着分析花20分钟把返回的JSON原样打印出来递归遍历看看里面有多少层嵌套哪些字段是你真正关心的。这个习惯能帮你少走很多弯路。我用json_normalize也是因为已经看清楚了数据长什么样才知道该用哪个参数去展平。6.2 把项目扩展成自己的太空数据仪表盘做完上面那些步骤你已经完整走完一遍拉数-存数-清洗-建模-出图的流程。如果还想继续深入有几个很自然的扩展方向。把每日获取的APOD数据和火星照片同步到一个excel清单里累积成自己的太空素材库。定期爬取小行星接近事件触发条件判断后自动发邮件提醒做一个小行星接近预警器。把EPIC的地球影像加载到GIS软件里叠加地理坐标看看不同时间拍摄的地球云层变化。将TLE轨道数据结合观测时间做卫星过境预测这个方向偏航天工程一点但数据源也是NASA开放的。扩展的同时你也会遇到新的问题请求多了怎么持久化保存、数据量大了怎么高效存储、多台机器采集时怎么管理Key。这些问题每解决一个你的工程能力就上一个大台阶。我在实际做这个项目时的体会是NASA的数据接口质量在免费API里属于第一梯队响应速度快、字段含义清晰、文档完整非常适合作为学习素材。但也不要一开始就想着把所有接口跑一遍先拿APOD跑通流程再碰NeoWS处理复杂结构最后挑战火星照片的批量下载循序渐进比一口吃成胖子有效得多。如果你在跑代码的过程中遇到某个状态码或者字段解析的问题卡住了建议第一步先返回去看接口文档里的字段说明大部分问题其实都是返回的数据和你想的不一样而已。