How to Scrape Weather Data: Free APIs First (2026)
Learn how to scrape weather data the smart way: free official APIs (NWS, Open-Meteo, OpenWeatherMap) first, then Weather.com and AccuWeather when you must.

Most guides that tell you how to scrape weather data point you straight at Weather.com with a headless browser. That is usually the slowest and most fragile way to get a temperature. Weather is one of the few domains with genuinely good free official APIs, so the right move is to try those first and only scrape a branded site when you actually need its specific numbers. This guide walks the whole ladder: the no-key government feed, two free global APIs, and the exact pattern for pulling data off Weather.com and AccuWeather when nothing else gives you what you need.
Free APIs first: a weather-data decision tree
Here is the insight that saves you a week of maintenance: Weather.com, AccuWeather, and every other consumer weather site are just front-ends over point-forecast models. The underlying numbers, temperature, precipitation probability, wind, humidity, come from a handful of global weather models (GFS, ECMWF, ICON and friends). Government agencies and open projects expose that same modeled data through clean JSON APIs. So before you write a single line of browser automation, ask what you truly need.
- Do you only need US locations? Use the National Weather Service API. No key, no scraping, no proxy.
- Do you need anywhere on Earth, plus history back to 1940? Use Open-Meteo.
- Do you want a batteries-included commercial API with one key? Use OpenWeatherMap or a similar freemium provider.
- Do you specifically need a branded site's own presentation (AccuWeather's RealFeel, The Weather Channel's narrative summaries, a competitor's displayed values)? Only then does scraping earn its place.
The table below is the short version of that decision.
| Source | Coverage | Key needed | Cost | Best for |
|---|---|---|---|---|
| NWS `api.weather.gov` | US and territories | No (User-Agent only) | Free | US forecasts, alerts, station observations |
| Open-Meteo | Global | No (non-commercial) | Free tier, paid for commercial | Forecast plus historical plus climate anywhere |
| OpenWeatherMap | Global | Yes (`appid`) | Free tier plus metered | Current and short forecast, quick setup |
| Weather.com (scrape) | Global | No | Your infra plus a proxy | The Weather Channel's specific values |
| AccuWeather (API or scrape) | Global | API: yes | Free API 50/day, or scrape | RealFeel and AccuWeather indices |
If your answer landed in one of the first three rows, you never touch a browser. Keep reading and you will have live data in a few minutes.
National Weather Service API (US, no key)
The US National Weather Service runs a public REST API at https://api.weather.gov. It needs no API key and no registration. The one hard requirement: send a descriptive User-Agent header that identifies your app and a contact, because the service rejects requests without one. A missing or generic User-Agent is the single most common reason a first NWS call fails.
The API is two steps. You look up a coordinate to get its grid cell, then you read the forecast for that cell.
import requests
UA = {"User-Agent": "(sparkproxy.io weather demo, you@sparkproxy.io)"}
# Step 1: resolve a lat/long to its NWS grid
point = requests.get(
"https://api.weather.gov/points/30.2711,-97.7437", headers=UA
).json()
# Step 2: read the forecast URL the point handed back
forecast_url = point["properties"]["forecast"]
periods = requests.get(forecast_url, headers=UA).json()["properties"]["periods"]
for p in periods[:3]:
print(p["name"], p["temperature"], p["temperatureUnit"], "-", p["shortForecast"])
That prints the next few named periods (Tonight, Tuesday, Tuesday Night) with temperatures and a plain-English summary. Swap forecast for forecastHourly in step two and you get hour-by-hour data instead.
For current conditions, the forecast grid is not enough. You want a real observation from a nearby station. The point response also gives you an observationStations URL:
stations = requests.get(
point["properties"]["observationStations"], headers=UA
).json()["features"]
station_id = stations[0]["properties"]["stationIdentifier"]
latest = requests.get(
f"https://api.weather.gov/stations/{station_id}/observations/latest", headers=UA
).json()["properties"]
print(latest["temperature"]["value"], "C at", latest["timestamp"])
Those observations are METAR reports from airports and official sensors, so they are the real measured value, not a model output. For severe weather alerts, hit the alerts endpoint directly by state:
curl -H "User-Agent: (sparkproxy.io weather demo, you@sparkproxy.io)" \
"https://api.weather.gov/alerts/active?area=TX"
The catch is coverage. NWS only serves the United States and its territories. Point at a coordinate in Berlin or Tokyo and you get nothing useful. That is where the next tool comes in.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
Open-Meteo: free global forecast and historical
Open-Meteo is the most useful free weather API for scraping-adjacent work. No key for non-commercial use, roughly 10,000 calls per day on the free tier, global coverage, and a clean query-string interface. One GET request returns current conditions, an hourly forecast, and a daily forecast together.
curl "https://api.open-meteo.com/v1/forecast?latitude=30.27&longitude=-97.74\
¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m\
&hourly=temperature_2m,precipitation_probability\
&daily=temperature_2m_max,temperature_2m_min\
&timezone=auto&temperature_unit=fahrenheit"
You pick exactly which variables you want in each block, which keeps responses small. Do not have coordinates? Open-Meteo ships a free geocoding endpoint too, so a place name becomes lat/long without a second provider:
curl "https://geocoding-api.open-meteo.com/v1/search?name=Austin&count=1"
The part most tutorials miss is historical weather data. Open-Meteo has a separate archive endpoint backed by ERA5 reanalysis from the European Centre for Medium-Range Weather Forecasts, with hourly data going back to January 1940. Same query grammar, different host:
curl "https://archive-api.open-meteo.com/v1/archive?latitude=30.27&longitude=-97.74\
&start_date=2020-01-01&end_date=2020-12-31\
&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=auto"
That single call returns a full year of daily highs, lows, and rainfall for any coordinate on the planet. Building a weather feature for a model, backtesting a strategy against past conditions, or filling gaps in a sensor log all become trivial. Two things to remember: the free tier is for non-commercial use (commercial projects need a paid API key), and the archive lags real time by about five days because reanalysis is not instant.
OpenWeatherMap and other freemium APIs
When you want a single commercial vendor with an SLA, OpenWeatherMap is the common default. You register for a free appid key and call a REST endpoint. The free tier covers current weather and a 5-day, 3-hour-interval forecast. Historical data and the richer One Call API 3.0 sit behind a paid or metered plan (One Call gives a free daily allotment once you attach a card).
curl "https://api.openweathermap.org/data/2.5/weather?lat=30.27&lon=-97.74\
&appid=YOUR_KEY&units=imperial"
The response is compact JSON with main.temp, weather[0].description, wind.speed, and so on. Other freemium options in the same shape include WeatherAPI.com, Tomorrow.io, and Visual Crossing, each with its own free quota. Pick on quota, variables, and historical depth. If your only worry is "will this key survive a launch," a metered commercial API is calmer than scraping a site that can change its markup any Tuesday.
At this point a lot of projects are done. You have current conditions, forecasts, and history from stable JSON. Scrape only if a branded site holds something these feeds do not.
Forecast, current, and historical: what to pull from where
Different sources are strong at different data types. Match the request to the source that serves it cleanly instead of forcing one API to do everything.
| Data type | NWS | Open-Meteo | OpenWeatherMap | Weather.com / AccuWeather |
|---|---|---|---|---|
| Current conditions | Station observations (METAR) | `current=` block | `/data/2.5/weather` | Embedded JSON |
| Hourly forecast | `/forecast/hourly` | `hourly=` block | One Call 3.0 (paid) | Embedded JSON |
| Daily forecast | `/forecast` | `daily=` block | `/forecast`, One Call | Embedded JSON |
| Historical | Not in main API | Archive (ERA5, 1940+) | Time Machine (paid) | Not exposed |
| Alerts | `/alerts/active` | Limited | One Call alerts | Embedded |
One nuance worth internalizing: a historical forecast and a historical observation are not the same thing. ERA5 reanalysis (Open-Meteo archive) is a modeled best estimate of what conditions actually were. Station observations (NWS, NOAA's NCEI archive) are measured readings. If you are training a model on "what the weather was," reanalysis is usually what you want. If you need certified measured values for a specific airport, go to the station record. Mixing the two silently corrupts a dataset, and most scraping tutorials never mention it.
When you actually need to scrape Weather.com
Say you specifically need The Weather Channel's own displayed values, maybe you are monitoring how a brand presents conditions, or you need their particular "feels like" narrative. Now scraping is justified. Here is the part that changes everything about how you do it.
Weather.com is a Next.js application. It does not render forecasts into static HTML on the server for you to parse with CSS selectors. Instead, it ships a big JSON blob in a script tag and hydrates the page from it in the browser. That blob lives in , and inside it sits a data-access-layer object keyed by config names like getSunV3DailyForecastWithHeadersUrlConfig, getSunV3HourlyForecastUrlConfig, and getSunV3CurrentObservationsUrlConfig. Those getSunV3... keys are the internal api.weather.com/v3 endpoints, already fetched and embedded for you.
So the correct "scrape" is not parsing the DOM. It is reading the embedded JSON:
import json
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
next_data = json.loads(soup.find("script", id="__NEXT_DATA__").string)
# The data-access-layer holds the already-fetched Sun v3 responses
dal = next_data["props"]["pageProps"]["dal"]
for key, entry in dal.items():
if key.startswith("getSunV3DailyForecast"):
# each entry is keyed by a request hash; grab the first payload
payload = next(iter(entry.values()))["data"]
print(payload["daypart"][0]["temperature"][:4])
This is dramatically more stable than chasing The obstacle is getting that HTML in the first place. Weather.com sits behind Akamai Bot Manager, which fingerprints your TLS handshake, checks your headers, and often blocks datacenter IPs outright. A plain AccuWeather is the same story with its own twist. The site is JavaScript-heavy and also fronted by Akamai, so the raw-request approach hits challenges. AccuWeather's data model is keyed by a Before you scrape, check the front door: AccuWeather runs an official developer API with a free tier (around 50 calls per day at the time of writing) that returns the same location-key-based data cleanly. For low volume, that key beats fighting Akamai. For higher volume where the free API quota is too small and their paid tier does not fit, scraping the rendered site is the fallback. Why do both of these sites use Akamai in the first place? Weather data is expensive to model and serve, and it is heavily scraped. Akamai Bot Manager lets them throttle automated traffic, protect ad impressions, and keep their compute bill sane. That is also why a simple script fails and why you need infrastructure that presents as a normal browser on a normal network. Our write-up on bypassing Akamai Bot Manager covers the fingerprinting details. Every weather source is keyed on location, and getting location right is where subtle bugs hide. Coordinates beat place names. APIs want Coordinate order trips people up. Weather APIs take Units are per-API, and defaults differ. Set them explicitly so a metric-versus-imperial mixup never reaches your database. OpenWeatherMap defaulting to Kelvin surprises almost everyone at least once. Set Timezones matter for daily aggregates. A "daily high" depends on which timezone defines the day boundary. Open-Meteo's When you do scrape a branded site, location becomes a geo-targeting problem too. Weather.com and AccuWeather localize both the location list and the displayed units to the visitor's region. If you request from an IP in a different country than your target, you can get the wrong defaults. That is one more reason the request path needs country control, which the next section handles. When you do have to scrape Weather.com or AccuWeather, the SparkProxy Scraping API handles the browser, the residential IP, and the Akamai fingerprint in one call so you can go back to parsing JSON. The base URL is A single request that gets you the fully hydrated Weather.com page, ready for the For a structured response with metadata instead of raw HTML, add For a monitoring job that walks many cities, keep a consistent browser profile per worker with Weather data sits in a friendly legal spot compared to most scraping, but there are still rules worth honoring. For the fuller framework, see our guide on ethical scraping and rate limiting. Good manners here also happen to be good engineering: cached, rate-limited, API-first pipelines break far less often than brute-force browser farms. Weather data itself is factual and generally not copyrightable, and pulling public data through official APIs is clearly fine. Scraping a rendered site like Weather.com is a grayer area that depends on the site's terms of service, your jurisdiction, and how you use the data, so review the terms and prefer the official API or a free feed when one exists. Not for API-based sources. NWS, Open-Meteo, and OpenWeatherMap serve JSON directly and need no proxy at all. You only need residential proxies or a scraping API when you scrape a branded site like Weather.com or AccuWeather that sits behind Akamai and blocks datacenter IPs. For US locations, the National Weather Service API ( Use Open-Meteo's archive endpoint ( Weather.com is a Next.js app behind Akamai Bot Manager, so a plain HTTP request usually gets a challenge page with no forecast in it. The reliable approach is to render the page with a real browser fingerprint on a residential IP, then read the embedded Yes. Open-Meteo covers the entire globe for free non-commercial use, including forecast and historical data, while the NWS API is free but limited to the United States and its territories. For worldwide commercial use, a metered API like OpenWeatherMap or an Open-Meteo paid key is the clean path. Get 20% off your first month Premium datacentre proxies with unlimited bandwidth. Use the code at checkout. Save up to 15% more on quarterly, half-yearly and yearly plansgetSunV3 prefix rather than hard-coding a full key. For the deeper version of this technique on any site, see our guide on scraping hidden JSON API endpoints.
requests.get frequently returns a challenge page with no __NEXT_DATA__ at all. Two things fix that: a real browser fingerprint and a residential IP. Scraping AccuWeather and the Akamai problem
locationKey, an internal ID for each city that you first resolve from a search, then use to request current conditions or forecasts. Their data holds proprietary indices, most famously RealFeel, that no free government feed reproduces. That proprietary layer is the one honest reason to scrape AccuWeather instead of using Open-Meteo. Latitude, longitude, units, and geo-targeting
latitude and longitude. Resolve a city to a coordinate once (Open-Meteo's geocoding endpoint above, or any geocoder) and cache it. Coordinates are unambiguous; "Springfield" is not.lat,long (Y then X). GeoJSON responses, including some NWS fields, express geometry as [long, lat] (X then Y). Flip them and you land in the ocean.API Default Switch units with NWS `/forecast` US units (Fahrenheit) `?units=si` Open-Meteo Celsius, km/h, mm `temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch` OpenWeatherMap Kelvin `units=metric` or `units=imperial` units on every call.timezone=auto resolves the local zone from the coordinate, which is what you usually want. Scraping at scale with the SparkProxy API
https://scrape.sparkproxy.io/api/v1, and you authenticate with an X-API-Key header. The parameters that matter for weather sites:Parameter Why it matters for weather sites `render_js=true` Weather.com and AccuWeather hydrate via Next.js; you need the executed page `premium_proxy=true` Route through residential IPs to clear Akamai Bot Manager `country_code=US` Force the geo so the site serves US locations and units `wait_for` Wait for the forecast module selector before capture `stealth=true` Extra fingerprint hardening for aggressive Akamai configs `json_response=true` Return a JSON envelope with status, credits, and a base64 body __NEXT_DATA__ parse from earlier:import requests
resp = requests.get(
"https://scrape.sparkproxy.io/api/v1",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": "https://weather.com/weather/today/l/30.27,-97.74",
"render_js": "true",
"premium_proxy": "true",
"country_code": "US",
"wait_for": "[data-testid='CurrentConditionsContainer']",
},
timeout=120,
)
html = resp.text # feed this straight into the __NEXT_DATA__ parser
json_response=true and decode the base64 body. That envelope tells you the upstream status and how many credits the call cost, which is handy for budgeting a large crawl:import base64
resp = requests.get(
"https://scrape.sparkproxy.io/api/v1",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": "https://weather.com/weather/today/l/30.27,-97.74",
"render_js": "true",
"premium_proxy": "true",
"json_response": "true",
},
timeout=120,
)
data = resp.json()
html = base64.b64decode(data["body"]).decode("utf-8")
print(data["status_code"], data["credits_used"], data["duration_ms"], "ms")
session_id, and add retry-with-backoff around timeouts and challenge responses. Our notes on retry and backoff strategies for web scraping apply directly here. If the target is a pure JavaScript app, the general playbook in scraping dynamic JavaScript websites is the companion read. Ethics, terms of service, and rate limits
robots.txt and the site's terms before scraping Weather.com or AccuWeather, and keep your request rate reasonable. You are not entitled to hammer someone's servers because the data is visible.Frequently asked questions
FAQ
api.weather.gov) is free and needs only a User-Agent header. For anywhere in the world, Open-Meteo is free for non-commercial use with no key and includes current, forecast, and historical weather data in one interface.archive-api.open-meteo.com/v1/archive), which serves ERA5 reanalysis back to January 1940 for any coordinate, free for non-commercial use. OpenWeatherMap's Time Machine and NOAA's NCEI station archives are paid or bulk alternatives when you need measured station records instead of reanalysis.__NEXT_DATA__ JSON rather than parsing the visual HTML.
Related articles

Proxy Acceptable Use Policies: What Providers Ban and Why
Proxy acceptable use policy explained: the targets, ports and account behaviours providers restrict, how violations are detected, and how to stay unsuspended.

Monthly vs Annual Proxy Plans: When Committing Pays Off
Is an annual proxy plan worth it? Break-even months for 5% to 30% term discounts, the resizing and vendor risks that erase them, and what to ask first.

Scraping API Pricing: How Credit Multipliers Set Real Cost
Scraping API pricing explained: how JS rendering, premium proxies, domain surcharges and billed failures multiply credit costs, with a worked estimate.
