Where historical weather data lives
| Source | Coverage | Type | Best for |
|---|---|---|---|
| NOAA NCEI | Some stations to the 1800s | Official station records | US locations; legal- and research-grade history |
| Open-Meteo archive | 1940 → present, global grid | Reanalysis (modeled) | Any coordinates worldwide, station or not |
| Meteostat | Varies by station | Aggregated station records | Python-native access to global stations |
| API Ninjas Weather API | From when you start logging | Live observations you record | Exactly your locations, intervals, and fields |
The key distinction is station versus reanalysis data. Station records are real instrument readings — authoritative, but only where a station exists. Reanalysis reconstructs a continuous global grid from observations and models — available everywhere, but modeled rather than measured.
Prerequisites
Every route below needs just:
- Python 3.8 or newer with the
requestslibrary. - For Step 3 only: a free API Ninjas API key — issued instantly, no credit card; existing accounts find it on the profile page. The NOAA and Open-Meteo archives need no key at all.
pip install requestsStep 1: Download official records from NOAA
NOAA's National Centers for Environmental Information serve daily summaries over plain HTTP — pass a station ID and a date range:
import requests
response = requests.get(
'https://www.ncei.noaa.gov/access/services/data/v1',
params={
'dataset': 'daily-summaries',
'stations': 'USW00094728', # New York — Central Park
'startDate': '2020-01-01',
'endDate': '2020-12-31',
'dataTypes': 'TMAX,TMIN,PRCP',
'units': 'metric',
'format': 'json',
},
timeout=30,
)
records = response.json()
print(records[0])
# {'DATE': '2020-01-01', 'STATION': 'USW00094728',
# 'TMAX': '4.4', 'TMIN': '-1.0', 'PRCP': '0.0'}TMAX/TMIN are the daily temperature extremes and PRCP is precipitation. Airport stations (IDs starting USW) have the longest, cleanest records — find IDs with NOAA's station search.
Step 2: Download reanalysis history for any coordinates
For places without a convenient station — or outside the US — the Open-Meteo archive serves modeled daily and hourly values back to 1940 for any point on Earth, free for non-commercial use:
response = requests.get(
'https://archive-api.open-meteo.com/v1/archive',
params={
'latitude': 51.5074,
'longitude': -0.1278,
'start_date': '2010-01-01',
'end_date': '2020-12-31',
'daily': 'temperature_2m_max,temperature_2m_min,precipitation_sum',
'timezone': 'UTC',
},
timeout=30,
)
daily = response.json()['daily']
print(daily['time'][0], daily['temperature_2m_max'][0])Have an address instead of coordinates? Geocode it first — see How to Convert a City to Latitude and Longitude.
Step 3: Log your own history going forward
Archives end at yesterday and contain whatever fields the archive chose. For dashboards, ML training data, or monitoring specific sites, the most useful history is the one you record yourself: call the Weather API on a schedule and append every observation to a database:
import os
import sqlite3
import time
import requests
session = requests.Session()
session.headers['X-Api-Key'] = os.environ.get('API_NINJAS_KEY', 'YOUR_API_KEY')
SCHEMA = """CREATE TABLE IF NOT EXISTS observations (
city TEXT,
observed_at INTEGER,
temp REAL,
feels_like REAL,
humidity REAL,
wind_speed REAL,
cloud_pct REAL
)"""
def log_weather(city: str) -> None:
"""Fetch current conditions and append them to the local history."""
data = session.get(
'https://api.api-ninjas.com/v1/weather',
params={'city': city},
timeout=10,
).json()
with sqlite3.connect('weather_history.db') as db:
db.execute(SCHEMA)
db.execute(
'INSERT INTO observations VALUES (?, ?, ?, ?, ?, ?, ?)',
(city, int(time.time()), data['temp'], data['feels_like'],
data['humidity'], data['wind_speed'], data['cloud_pct']),
)
for city in ['London', 'Tokyo', 'New York']:
log_weather(city)
print('Logged 3 observations')Schedule it with cron and the dataset builds itself:
# Log observations every hour, on the hour
0 * * * * /usr/bin/python3 /home/app/log_weather.pyAfter a few weeks you have a gap-free, per-hour record for exactly the places you care about — something no public archive offers.
Step 4: Add the forward view
The same key serves /v1/weatherforecast, so one pipeline can store yesterday's observations next to tomorrow's forecast — which is exactly what you need to score forecast accuracy against what actually happened:
forecast = session.get(
'https://api.api-ninjas.com/v1/weatherforecast',
params={'lat': 51.5074, 'lon': -0.1278},
timeout=10,
).json()Where to go from here
Extend the logger with the Air Quality API to record PM2.5 and ozone alongside the weather, and the Geocoding API to resolve new place names as you add them. The complete logger script is below.