ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

NASA开放数据接口全指南:用Python从APOD到小行星分析的实战教程

NASA开放数据接口全指南:用Python从APOD到小行星分析的实战教程 NASA的公开API可能是这个星球上最宝藏的免费数据源之一。这句话我写过很多次每次安利给朋友第一次跑通的人都会回来说一句原来拿Python读官方数据可以这么爽。不需要爬虫逆向不需要处理反爬NASA把真实的天文数据、小行星轨道、火星车照片挂在外网只要你注册一个免费API Key就能用Python的requests库一行一行拉下来再用pandas整理成表格做分析。这个项目很适合两类人一类是刚接触Python、想找个真实数据源练手的朋友另一类是已经会基础语法、想做点数据分析和可视化作品的爱好者。它解决的问题很直接——让“从公开API取数、清洗、处理、可视化”这一整套链路在一个具体场景里跑通。你不需要自己有卫星也不需要懂天体物理只要你愿意打开终端就可以在半小时内拿到当天的天文照片或者近地小行星的实时数据。本文会从申请Key开始到写第一个requests请求再到解析JSON、做数据清洗、画图表、避坑完整带你走一遍。全程用我能写出的最直白的话讲代码可以直接复制重点是告诉你每一步为什么这么做。1. 动手之前的全局认识NASA开放API到底能做什么1.1 先看看NASA API里有哪些数据源NASA的开放API入口在api.nasa.gov官方维护了几十个数据接口其中最适合普通人玩的有这么几个。APODAstronomy Picture of the Day是天文每日一图接口返回当天或指定日期的天文图片、标题和一段官方说明文案。这个接口参数最少返回结构简单我一般把它推荐为“人生的第一个NASA API”。你只需要传一个日期和一个API Key就能拿到一张可能是哈勃望远镜拍的深空照片或者一段太阳活动视频。NeoWsNear Earth Object Web Service是近地天体接口专门返回靠近地球轨道的小行星数据包括小行星直径、接近地球的距离、相对速度、是否构成潜在威胁等字段。这一接口的数据量非常大一次请求就能返回二十条小行星信息非常适合用来练pandas的数据清洗和统计。很多人第一次用它做数据分析时会被“直径从几十米到几公里分布”“未来一个月有几十颗小行星飞掠地球”这种真实数据震撼到。火星车照片接口Mars Rover Photos则直接返回好奇号、机遇号、勇气号火星车拍回的照片地址。你可以指定火星车的相机制式、火星日或地球日期拿到某个具体日期该相机拍摄的所有照片URL。这组数据是图片链接而非图片本身所以你还需要写一段下载代码才能把火星照片存到本地。涉及分页、批量下载、HTTP图片保存是进阶练习的绝佳素材。除了这三个还有地球卫星影像、NASA图像库搜索、太阳系天体历表等接口。对大多数人和博客演示场景来说抓住APOD、NeoWs、火星照片这三个就足够构建一套完整项目了所以我下文的所有实操都围绕它们展开。1.2 免费申请API Key的完整流程在api.nasa.gov页面左侧找到“Generate API Key”表单填上姓名和邮箱点击生成按钮API Key会立即显示在网页上同时N A S A也会给这个邮箱发一封确认邮件。整个过程中不需要审核也不需要等待实测从填写到拿到Key正常不到一分钟。申请下来的是一个32位左右的字符串长得像一串随机字符。我有几个朋友第一次拿到后直接把Key写死在Python脚本里然后顺手把脚本推到GitHub公开仓库结果没两天就收到了滥用告警邮件。这是个非常常见的低级错误API Key本质上相当于你的调用凭证别人拿到后可以冒用你的身份高频请求导致你的IP被限流严重的会被封掉整个Key。所以我建议从第一步就把Key放到环境变量里。在Linux或macOS终端写入export NASA_API_KEY你的Key在Windows的PowerShell里写入setx NASA_API_KEY 你的Key。然后Python代码里用os.getenv(NASA_API_KEY)读取。这样脚本本身不含任何敏感信息就算公开源码也不怕。如果你不想设置系统环境变量也可以把Key写进项目根目录下的.env文件用python-dotenv库加载效果一样只是多装一个依赖。需要说明的是NASA的免费Key有请求频率限制。政策细则偶尔会调整不同数据接口的限流阈值也不完全一样量产场景下最稳妥的策略是“每次请求之间主动加sleep延时”或“把结果缓存到本地”。我后面会专门写一节讲缓存和限流怎么处理这里先记住一个原则拿到的Key只是入场券不是无限流量卡别一上来就疯狂并发请求。1.3 为什么选Python而不直接上Postman有的读者会问这种纯GET请求用Postman点两下不就行了为什么要用Python单次调试确实用Postman更快。填好URL、加上API Key参数、点SendJSON结果直接展示在界面里字段一目了然。我也确实推荐你先用Postman或浏览器打开API地址把返回结果肉眼看一遍确认结构。但项目的价值不在“点一次按钮”而在“批量拉取、清洗、建模、图表化”。比如你想分析过去30天所有近地小行星的直径分布靠手点30次请求显然不现实。这类批量任务Python的requests库、pandas、matplotlib三者无缝衔接才是效率最高的路径。另外Python生态里还有现成的错误重试库、缓存装饰器、定时任务工具能让你在一个语言环境里把数据工程全链路都跑完。简单说Postman是单发步枪Python是自动步枪你要上战场打连发肯定选后者。顺带说一句用Python请求公开API本身也算广义的“爬虫”只不过NASA欢迎正常调用数据也是官方接口直接给的完全绕开了反爬、UA校验这些无聊问题。学这套流程你能把requests会话管理、JSON解析、分页、重试这些基本功练扎实再去接其他要爬虫的数据源时思路是通的。2. 从requests请求到JSON解析跑通第一个最小闭环2.1 环境准备我默认你的机器上已经装好了Python 3.8及以上版本。没装的话去python.org下载对应安装包安装时务必勾选“Add Python to PATH”这一步很多人容易漏漏了的话在命令行里敲python会提示找不到命令。项目要复现建议用虚拟环境隔离依赖不要直接往全局环境里塞包。终端进入项目目录后执行python -m venv venvWindows下激活虚拟环境用venv\Scripts\activateLinux和macOS用source venv/bin/activate。激活后命令行前面会出现一个(venv)标记说明你已经在虚拟环境里了。接下来安装依赖pip install requests pandas matplotlib python-dotenvrequests负责发HTTP请求pandas负责把JSON转成结构化表格matplotlib负责画图python-dotenv用来加载.env文件里的API Key。这四个库足够跑完本文所有代码。装完后在项目目录新建一个.env文件内容写NASA_API_KEY你的32位Key在代码开头加一行load_dotenv()之后就能用os.getenv(NASA_API_KEY)拿到Key了。注意.env文件要加进.gitignore千万别提交到Git仓库。2.2 第一个API调用APOD天文每日一图环境就绪我们开始写第一段完整代码。文件名就叫apod_demo.pyimport os import requests from dotenv import load_dotenv 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) print(HTTP状态码:, resp.status_code) data resp.json() print(标题:, data[title]) print(日期:, data[date]) print(图片地址:, data[url]) if data.get(hdurl): print(高清图地址:, data[hdurl]) print(说明:, data[explanation][:200])requests.get会拼出完整的URLparams字典里的参数会被自动做URL编码和拼接你不用自己处理问号、等号和特殊字符。timeout10意思是10秒内连不上或拿不到响应就直接报超时错误这能防止程序因为网络问题无限卡住。多数入门教程不会写这个参数但我强烈建议所有外部请求都带timeout否则一旦目标服务器无响应你的脚本就会像死机一样挂在那一行。resp.json()会自动把HTTP响应体里的JSON字符串解析成Python字典或列表。APOD返回的是一个单层字典直接按键名取值就行。注意官方说明里可能提到“如果当天是视频”返回的media_type字段会是video此时url指向的是视频页面而不是图片。代码里已经通过data.get(hdurl)做了防御式取值get方法在键不存在时会返回None不会抛出KeyError。如果你只想把这个接口用熟到这儿差不多就够了。但既然目标是完成一个项目我们就不只满足于打印字段下一步把它变成图片文件存到本地import os.path img_url data.get(hdurl) or data[url] filename data[date] .jpg with open(filename, wb) as f: img_resp requests.get(img_url, timeout30) f.write(img_resp.content) print(图片已保存:, os.path.abspath(filename))这里直接用了wb二进制写入模式。注意高清图地址hdurl体积可能很大几十MB的图也有下载时要留足超时时间我设的30秒。实测普通APOD图片在正常网络条件下几秒内能下完但偶尔会遇到超大原图30秒不够的话可以加大到60。2.3 JSON嵌套结构怎么快速拆解第一次接触NeoWs或火星照片数据时你会发现返回的JSON结构远没有APOD那么平铺直叙它是典型的多层嵌套列表套字典字典里又有列表。粗暴地按data[near_earth_objects]取出来的还可能是一堆混合对象新手一看就头大。我的排查方法很简单先不要写解析代码先用pprint把整个返回结果打印出来看一遍。import pprint data resp.json() pprint.pprint(data)pprint会将嵌套结构缩进格式化字段层级一目了然。你只需要盯住自己关心的那几个字段沿着路径一层层取值。比如NeoWs里某颗小行星的估计直径最大值路径是data[near_earth_objects][0][estimated_diameter][kilometers][estimated_diameter_max]。路径这么长里面任何一个键名拼错都会报KeyError所以肉眼确认路径是写解析代码前的必修课。还有一个小技巧如果某个键不确定是否存在尽量用.get(键名)而不是[键名]。按路径逐层取值时每层都用get并在关键位置加一个空值兜底。再配合列表推导式可以一次性提取多颗小行星的字段。我给一个提取“小行星名称、最大直径、是否危险”的示例def build_mini_record(asteroid): return { name: asteroid.get(name), diameter_km: asteroid.get(estimated_diameter, {}) .get(kilometers, {}) .get(estimated_diameter_max), hazardous: asteroid.get(is_potentially_hazardous_asteroid), } records [build_mini_record(item) for item in data[near_earth_objects]]这里的每一层get都返回空字典或None作为默认值即使NASA改了字段结构顶多得到None不会让整个程序崩溃。这种“宁可取不到不要报崩溃”的思路在真实的数据工程里比“追求一次取对”重要得多。2.4 异常处理与重试机制公开API毕竟是服务器提供的服务没人能保证每一秒都稳定。网络抖动、目标服务器过载、误传参数都会让请求失败。代码写得再漂亮不对失败做处理一跑批量任务就会在某个请求上报错退出。最基础的防御是检查HTTP状态码。我们在APOD例子里已经用了resp.status_code更多时候会用resp.raise_for_status()它的作用是如果状态码是4xx或5xx主动抛出一个HTTPError异常方便你定位。但只抛异常还不够真实世界里更合理的方式是“重试几次”。我写项目时常用这样一个带重试的请求函数import time import requests def fetch_json(url, params, retries3, backoff2): for attempt in range(retries): try: resp requests.get(url, paramsparams, timeout15) resp.raise_for_status() return resp.json() except requests.exceptions.Timeout: print(f第{attempt 1}次请求超时) except requests.exceptions.HTTPError as e: print(fHTTP错误: {e}) if resp.status_code 404: raise # 404通常是URL或日期参数错误重试也无用 except requests.exceptions.RequestException as e: print(f网络错误: {e}) time.sleep(backoff * (attempt 1)) return None重点看time.sleep(backoff * (attempt 1))这是指数退避策略第一次失败等2秒第二次失败等4秒第三次等6秒。给服务器喘息的时间也避免你自己因为疯狂重试被API网关临时封禁。对于404这类“请求本身不对”的错误重试多少次都没有意义我选择直接抛出异常让程序停下来、及时发现问题。对于断网、超时、5xx这种临时性问题才值得重试。3. 进阶实战小行星数据与火星照片的数据分析3.1 NeoWs近地小行星数据拉取与清洗现在把requests和pandas串起来做一个正经的数据分析项目。先拉取NeoWs的小行星数据地址是url https://api.nasa.gov/neo/rest/v1/neo/browse这个接口不需要传日期默认以小行星编号排序分页返回。返回的JSON里有page字段和near_earth_objects列表。我写一段把单页数据转成pandas DataFrame的代码import pandas as pd from dotenv import load_dotenv import os load_dotenv() API_KEY os.getenv(NASA_API_KEY) url https://api.nasa.gov/neo/rest/v1/neo/browse data fetch_json(url, {api_key: API_KEY}) rows [] for item in data[near_earth_objects]: approaches item.get(close_approach_data, [{}]) approach approaches[0] if approaches else {} rows.append({ id: item.get(id), name: item.get(name), diameter_max_km: item.get(estimated_diameter, {}) .get(kilometers, {}) .get(estimated_diameter_max), hazardous: item.get(is_potentially_hazardous_asteroid), approach_date: approach.get(close_approach_date), miss_distance_km: approach.get(miss_distance, {}).get(kilometers), relative_velocity_kph: approach.get(relative_velocity, {}).get(kilometers_per_hour), }) df pd.DataFrame(rows) print(df.head()) print(df.info())这段代码的核心是close_approach_data字段它本身是列表一颗小行星会有多次接近记录。稳妥起见我只取了第一次接近记录这在学习场景里已经够用。df.info()会告诉你有没有缺失值列数、类型一目了然。跑通后你会发现真实数据里面name字段大多是带括号的编号比如“(2024 AB)”这种清洗时可以按需去掉括号。3.2 pandas统计与筛选数据进了DataFrame后续操作全是pandas的常规操作。最让人兴奋的几个统计是找到最近距离最小的小行星、直径最大的小行星、所有潜在危险小行星。# 数值列转float避免字符串比较 numeric_cols [diameter_max_km, miss_distance_km, relative_velocity_kph] for col in numeric_cols: df[col] pd.to_numeric(df[col], errorscoerce) print(距离最近的小行星:) print(df.loc[df[miss_distance_km].idxmin()][[name, miss_distance_km]]) print(直径最大的小行星:) print(df.loc[df[diameter_max_km].idxmax()][[name, diameter_max_km]]) dangerous df[df[hazardous] True] print(f潜在危险小行星数量: {len(dangerous)})idxmin()和idxmax()返回最小/最大值的索引再用loc取出整行。pd.to_numeric可以把看起来是数字的字符串批量转成floaterrorscoerce表示转换不了的值变成NaN而不是报错。这一步不做的话很多从JSON来的数字其实是字符串比较大小会得到错误结果。还可以按日期处理。把approach_date转成datetime后你可以统计每月接近地球的小行星数量。真实数据里某些月份可能一个都没有某些月份多到要拉好几页。这类洞察是纯手动浏览网页得不到的也是这个项目最有意思的地方。3.3 火星车照片API分页与批量下载火星车照片接口是另一个练手利器。接口路径是url https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos常见参数是earth_date地球日期、camera相机缩写、page页码。比如拉取2019年8月1日好奇号MAST相机拍摄的所有照片params { api_key: API_KEY, earth_date: 2019-08-01, camera: MAST, page: 1, } data fetch_json(url, params) photos data.get(photos, []) print(f第1页照片数量: {len(photos)})注意camera字段值必须是接口文档里规定的缩写比如FHAZ、RHAZ、MAST、CHEMCAM、NAVCAM。传错或传成中文全称接口可能返回空列表或者报422错误。火星照片数据里面每张照片的核心字段是img_src直接指向图片URL。下载到本地的方法和之前APOD图片一样。但是一个日期一个相机可能有很多页照片每页固定25条左右所以批量下载时要处理分页。我的经验是写成“边拉边存”把已经下载的URL记在一个集合里防止重复同时每下载一张就追加写入文件而不是全部攒在内存里再一次性写盘。这样就算程序中途崩了已经下好的照片都在断点续传也容易实现。3.4 用matplotlib把分析结果可视化数据分析的最后一步通常是可视化。以NeoWs数据为例我画一张“直径和接近距离”的散点图把潜在危险小行星用红色高亮import matplotlib.pyplot as plt plt.figure(figsize(10, 6)) plt.scatter(df[diameter_max_km], df[miss_distance_km], s30, alpha0.6, label普通小行星) danger df[df[hazardous] True] plt.scatter(danger[diameter_max_km], danger[miss_distance_km], s50, alpha0.8, colorred, label潜在危险) plt.xscale(log) plt.yscale(log) plt.xlabel(最大直径 (km)) plt.ylabel(接近距离 (km)) plt.title(近地小行星直径与接近距离分布) plt.legend() plt.grid(True, linestyle--, alpha0.5) plt.show()为什么横纵坐标都用log缩放因为小行星直径跨了几个数量级有的只有几十米有的是几公里距离也从几十万公里到几百万公里不等。线性坐标画出来小值全部挤在左下角根本看不清。log坐标能把数量级差异摊开分布规律更明显。如果你拿到一批火星车照片还可以画一个不同相机的照片数量柱状图直观展示哪个相机的出图量最多。4. 避坑指南我在实操中踩过的坑与排查经验4.1 API Key和401状态码最常见的一个坑明明申请了Key请求却一直返回401 Unauthorized。排查思路很简单第一步看请求的URL里api_key参数是否真的带上了。用requests时如果params里键名拼成apiKey或key服务端认不出来就会被当成匿名请求拒绝。第二步看环境变量有没有读对。我见过不少次os.getenv(NASA_API_KEY)返回None原因是.env文件没放在当前工作目录下或者load_dotenv()没执行。你可以在代码里加一句检查if not API_KEY: raise RuntimeError(NASA_API_KEY 环境变量未配置)这样就不会带着空的Key发请求也能第一时间发现问题。4.2 日期参数格式与422错误APOD和火星照片接口都要求日期参数严格遵循YYYY-MM-DD格式。2024-1-5这种看似合理的写法很多情况下会被服务端拒绝返回422 Unprocessable Entity或400 Bad Request。还有些日期根本不存在比如2023-02-29也会报错。另一个容易忽略的点是API返回的日期字段是UTC时间“今天”在你本地可能是明天。如果你想拉“今天”的照片不要想当然地取本地日期的字符串最好先确认你的接口到底需要哪个时区的日期。我处理的办法很简单先用一个明确知道的正常日期跑通比如2024-06-01确认项目闭环没问题再改成动态日期。4.3 频率限制与多线程踩坑NASA公开API对免费Key的请求频率有限制虽然文档里写的限额随政策可能调整但“不要短时间高频请求”这个原则永远适用。我有一次拉火星车一个月的数据天真地写了个循环一页页请求没加延时结果跑到十几页就开始收到429 Too Many Requests。当时我还以为是程序写错了排查半天才发现是被限流。处理办法有三个一是每两次请求间隔至少1秒可以用time.sleep(1)实测能大大降低被限流概率二是把请求结果缓存到本地能不开新请求就不开新请求三是如果数据量确实很大就分段拉取比如每天只拉一个日期把任务拆成多个批量脚本分批跑。这里尤其不建议新手一上来就用多线程并发刷NASA API。requests本身是阻塞的多线程确实能提速但在免费Key的限流下并发越高被拉黑的概率越大。我建议先把单线程加延时跑通实在有性能需求再考虑用concurrent.futures控制一个低并发数比如同时3个线程并且每个请求之间仍然保留一点随机延时。4.4 图片下载中的HTTP 403与证书问题下载火星照片或APOD高清图时偶尔会遇到HTTP 403 Forbidden。一种常见原因是目标服务器校验了User-Agent默认的requests库UA可能被拒绝。解决办法是伪装一个常见浏览器的UAheaders {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36} resp requests.get(img_url, headersheaders, timeout30)另一种是SSL证书校验失败。如果你是在一些特殊网络环境下测试可能会遇到这类报错。我的建议是优先配好本地CA证书不到万不得已不用verifyFalse跳过校验因为这会引入中间人攻击风险。如果你只是快速下载一张公开的太空图片、且确认来源可信verifyFalse也能应急但请把它当作临时手段别出现在正式代码和教程里。4.5 网络环境差时程序容易超时如果你的网络访问境外站点不稳定拉取NASA数据时会频繁超时。我处理这个问题的习惯是把上一节写的fetch_json做成一个公共模块所有请求都走它。超时时间设成15秒到30秒配合重试三次和指数退避成功率会高很多。不要以为加了重试就万事大吉重试只是兜底最关键的还是控制请求频率和做好本地缓存。5. 从好玩到实用后续扩展思路5.1 落盘缓存少打几次APINASA数据大部分是“当天新增、历史不变”的类型。APOD某一天的图片说明无论查多少次结果都一样。所以完全可以做一层本地缓存请求前先检查本地有没有对应日期或页码的缓存文件命中就直接读取没命中再去请求。这既省流量又不会把自己刷进限流名单。一个最简单的缓存实现是把每个接口的返回JSON存成本地文件文件名带上前缀和参数。比如APOD的缓存可以叫apod_2024-06-01.jsonNeoWs的缓存可以叫neo_browse_page1.json。下次运行前用os.path.exists()判断存在就json.load读取。更有工程味道的做法是写一个缓存装饰器但考虑到项目规模用文件加日期检查的方式已经足够。等你的代码开始跑定时任务时才能真正体会到缓存的好处——历史上拉过的数据不会再反复消耗你的请求额度每次新运行只拉最新数据即可。5.2 定时任务自动拉取数据链路跑通后很自然的想法是“每天早上自动拉一次APOD”或者“每周汇总一次近地小行星清单”。这类定时任务不必依赖复杂的分布式调度一个cron表达式就能解决。Linux和macOS上用crontab -e加一条任务比如每天早上8点运行脚本0 8 * * * cd /path/to/project /path/to/venv/bin/python fetch_apod.pyWindows用户可以打开“任务计划程序”新建基本任务触发器选“每天”操作选“启动程序”程序指向venv里的python.exe参数填脚本路径。这样电脑开着脚本就会自动执行把当天照片下载到本地、把当天小行星数据追加进CSV。唯一的坑是脚本运行环境里要确保能读取到.env文件。建议在脚本开头用os.path.dirname(__file__)拼出脚本所在目录再加载该目录下的.env否则定时任务的工作目录和你手动运行时可能不一样。5.3 项目合规与发布注意事项公开API虽然免费不代表没有规则。我整理一下发布项目、写博客时需要注意的几点。第一不要把你的API Key提交到公共仓库。就算NASA不会立刻封号这终究是授人以柄。第二下载的图片如果用于公开文章最好在页面底部标注图片来源说明。APOD的官方页面上有版权归属和引用格式要求大多数NASA内容明确适用于教育和信息用途但注明来源是基本的尊重。第三不要做超出正常调用范围的压力测试大批量高并发拉全量数据既不优雅也没有必要。5.4 更多可玩的数据源组合如果觉得这三个API不够玩NASA还有一个图像和视频库APIimages-api.nasa.gov支持按关键词搜索历史太空照片和视频返回成千上万条带描述的媒体资源。你可以把“近地小行星数据分析”“火星照片下载”“APOD历史图片回顾”组合成一套个人数据作品集配上本地SQLite存档就能形成一个完全由公开数据驱动的小型太空数据仓库。我个人在实际操作中的体会是这个项目最大的价值不在于代码本身有多复杂而在于它让你第一次认真对待“真实世界的数据”。返回结果里的字段名、数据缺失、类型不一致、限流、缓存、重试这些东西没有真实API是学不到的。建议你跑通本文例子后去找一个自己最感兴趣的数据源把它吃透。或者做一张属于自己的“太空数据看板”把每天自动拉取的天文图片和近地天体数据汇总到一个页面上。这也是一个不错的后续方向。
返回列表