kma.KmaClient๋ ์ฌ์ฉ๋์ด ๋์ ๋จ๊ธฐ์๋ณด ์๋น์ค๋ฅผ ํ์
ํ๋ ๋ชจ๋ธ๋ก ๊ฐ์ผ ํด๋ผ์ด์ธํธ์
๋๋ค. apis.data.go.kr/1360000์ ๋ค๋ฅธ KMA ๊ณต๊ณต๋ฐ์ดํฐ ์๋น์ค๋ DataGoKrClient๋ก ๋ฒ์ฉ ํธ์ถํฉ๋๋ค.
๊ณต์ ํ์ธ ์ถ์ฒ:
- https://www.data.go.kr/data/15084084/openapi.do
- https://www.data.go.kr/data/15059468/openapi.do
- https://www.data.go.kr/data/15000415/openapi.do
- https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15059093
- https://www.data.go.kr/tcs/dss/selectDataSetList.do?dType=API&keyword=%EA%B8%B0%EC%83%81%EC%B2%AD&sort=reqCo¤tPage=1&perPage=40&pblonsipScopeCode=PBDE07
- ์์น๋ชจ๋ธ, ์์ฑ, ํญ๊ณต, ํน๋ณด ๋ฑ ์ผ๋ถ ์ต์ data.go.kr ํญ๋ชฉ์ APIHub๋ก redirect/link๋ฉ๋๋ค.
import asyncio
from kma import KmaClient
async def main() -> None:
async with KmaClient.from_env() as kma:
(await kma.now(nx=60, ny=127))
(await kma.forecast_short(nx=60, ny=127))
(await kma.forecast(nx=60, ny=127))
asyncio.run(main())ํ์ ํ ํด๋ผ์ด์ธํธ๊ฐ ๋ค๋ฃจ๋ endpoint:
VilageFcstInfoService_2.0/getUltraSrtNcstVilageFcstInfoService_2.0/getUltraSrtFcstVilageFcstInfoService_2.0/getVilageFcstVilageFcstInfoService_2.0/getFcstVersion
import asyncio
from kma import DataGoKrClient
async def main() -> None:
async with DataGoKrClient.from_env() as client:
body = (await client.request(
"MidFcstInfoService",
"getMidFcst",
{"stnId": "108", "tmFc": "202605010600"},
))
asyncio.run(main())ํ์ค response.body.items.item ๊ตฌ์กฐ๋ฅผ ์ฐ๋ operation์ items()๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
from kma import DataGoKrClient
import asyncio
async def main() -> None:
async with DataGoKrClient.from_env() as client:
items = (await client.items(
"MidFcstInfoService",
"getMidFcst",
{"stnId": "108", "tmFc": "202605010600"},
))
asyncio.run(main())metadata๊ฐ ํ์ํ๋ฉด request_with_metadata()๋ฅผ ์ฌ์ฉํฉ๋๋ค. metadata์ request_params์๋ serviceKey ์๋ฌธ์ด ์์ต๋๋ค.
from kma import DataGoKrClient
import asyncio
async def main() -> None:
async with DataGoKrClient.from_env() as client:
body, metadata = (await client.request_with_metadata(
"MidFcstInfoService",
"getMidFcst",
{"stnId": "108", "tmFc": "202605010600"},
))
asyncio.run(main())2026-05-07 ๊ธฐ์ค ๊ณต๊ณต๋ฐ์ดํฐํฌํธ ๊ธฐ์์ฒญ ์คํ API ๊ฒ์์ ๋ชจ๋ ํ์ด์ง๋ฅผ ํ์ธํด, ์ ๋ชฉ์ด ๊ธฐ์์ฒญ์ผ๋ก ์์ํ๋ ํญ๋ชฉ๋ง KMA_DATA_GOKR_DATASETS์ ๋ด์์ต๋๋ค. ๊ฒ์์ด์๋ ๊ฑธ๋ฆฌ์ง๋ง ๊ธฐ์์ฒญ์ด ์๋ ๊ธฐ๊ด์ ํญ๋ชฉ์ ์ ์ธํฉ๋๋ค.
์นดํ๋ก๊ทธ 86๊ฐ ์ค ๊ธฐ์กด data.go.kr serviceKey gateway ํญ๋ชฉ์ 38๊ฐ์ด๋ฉฐ, ํฌํธ ์์ธ๊ธฐ๋ฅ์์ ํ์ธํ operation 160๊ฐ๋ฅผ ํจ๊ป ๋ณด์กดํฉ๋๋ค. APIHub๋ก ์ฐ๊ฒฐ๋๋ 48๊ฐ ํญ๋ชฉ์ gateway="apihub"๋ก ๊ตฌ๋ถํฉ๋๋ค. APIHub์ ์ ํํ ๊ฐ์ {service}/{operation} ์กฐํฉ์ data.go.kr/APIHub ์ค๋ณต ํ์ธ์ ํ๋ก ์ ๋ฆฌํ์ต๋๋ค.
import asyncio
from kma import KMA_DATA_GOKR_DATASETS, DataGoKrClient, api_catalog
async def main() -> None:
async with DataGoKrClient.from_env() as client:
print(len(KMA_DATA_GOKR_DATASETS)) # 86
for entry in api_catalog(gateway="datagokr")[:3]:
print(entry.dataset_name, entry.operation, entry.service_key_url)
asos_spec = client.dataset("15059093")
rows = (await client.dataset_items(
"15059093",
{
"startDt": "20260501",
"endDt": "20260502",
"dataCd": "ASOS",
"dateCd": "DAY",
},
))
asyncio.run(main())api_catalog()๋ dataset์ operation ๋จ์ row๋ก ํผ์ณ dataset_name, label, gateway, service, operation, credential_param, service_key_url์ ์ ๊ณตํฉ๋๋ค. Streamlit ๊ฐ์ ๋๋ฒ๊ทธ UI์์๋ label์ ์ ํ ํญ๋ชฉ์ผ๋ก ์ฐ๊ณ , ์ฌ์ฉ์๊ฐ ์ ํํ row์ service_key_url์ ์๋น์คํค ๋ฐ๊ธ/ํ์ธ ๋งํฌ๋ก ๋ณด์ฌ์ฃผ๋ฉด ๋ฉ๋๋ค.
dataset_items()๋ ์๋น์ค์ operation์ด ํ๋๋ก ๊ฒฐ์ ๋๋ data.go.kr gateway ํญ๋ชฉ์ ๋ฐ๋ก ํธ์ถํฉ๋๋ค. ์ฌ๋ฌ operation์ ๊ฐ์ง ํญ๋ชฉ์ operation=์ ๋ช
์ํฉ๋๋ค.
from kma import DataGoKrClient
import asyncio
async def main() -> None:
async with DataGoKrClient.from_env() as client:
waves = (await client.dataset_items(
"15102239",
{"beach_num": "1", "searchTime": "202205011600"},
operation="getWhBuoyBeach",
))
asyncio.run(main())์นดํ๋ก๊ทธ์๋ APIHub๋ก ์ฐ๊ฒฐ๋๋ data.go.kr ํญ๋ชฉ๋ gateway="apihub"๋ก ๋จ๊ฒจ ๋ก๋๋ค. ์ด ํญ๋ชฉ์ serviceKey gateway๊ฐ ์๋๋ฏ๋ก dataset_items()๊ฐ ํธ์ถํ์ง ์๊ณ , ApiHubClient ๋๋ ApiHubGeneratedClient๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
๊ธฐ๋ณธ๊ฐ:
serviceKey=<DATA_GO_KR_SERVICE_KEY>๋๋<DATA_GO_KR_SERVICE_KEY>pageNo=1numOfRows=10dataType=JSON
from_env()๋ process env๋ฅผ ๋จผ์ ๋ณด๊ณ , ์์ผ๋ฉด ํ์ฌ ์์
๋๋ ํฐ๋ฆฌ์ ๋ถ๋ชจ ๋๋ ํฐ๋ฆฌ์ .env, .env.local์ ์ฐพ์ต๋๋ค. ๊ฐ์ key๊ฐ ์ฌ๋ฌ ๋ก์ปฌ ํ์ผ์ ์์ผ๋ฉด ๊ฐ๊น์ด ๋๋ ํฐ๋ฆฌ ๊ฐ์ด ์ฐ์ ํ๊ณ , ๊ฐ์ ๋๋ ํฐ๋ฆฌ์์๋ .env.local์ด .env๋ณด๋ค ์ฐ์ ํฉ๋๋ค. ์ธ์ฆํค ๊ฐ์ ๋ณต์ฌ/๋ถ์ฌ๋ฃ๊ธฐ ๊ณต๋ฐฑ์ด๋ ์ค๋ฐ๊ฟ์ด ์์ด๋ฉด ํด๋ผ์ด์ธํธ ์์ฑ ์ ์ ๊ฑฐํฉ๋๋ค.
๊ณต๊ณต๋ฐ์ดํฐํฌํธ ๋ฌธ์๋ ์๋น์ค์ ๋ฐ๋ผ ์ธ์ฆํค ํญ๋ชฉ์ serviceKey ๋๋ ServiceKey๋ก ํ๊ธฐํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ ๊ธฐ์กด data.go.kr gateway์์ ๋์ ํ์ธํ serviceKey์ด๋ฉฐ, ํน์ ์๋น์ค๊ฐ ๋๋ฌธ์ ์ด๋ฆ์ ์๊ตฌํ๋ฉด ์์ฑ์์์ ๋ฐ๊ฟ ์ ์์ต๋๋ค.
client = DataGoKrClient.from_env(service_key_param="ServiceKey")data.go.kr ๊ณ์ด response body๊ฐ pageNo, numOfRows, totalCount๋ฅผ ํฌํจํ๋ฉด ๋ค์ helper๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
from kma import latest_mid_fcst_time
from kma import DataGoKrClient
import asyncio
from kma import has_next_page, next_page_no
async def main() -> None:
async with DataGoKrClient.from_env() as client:
body = (await client.request("MidFcstInfoService", "getMidLandFcst", {"regId": "11B00000", "tmFc": latest_mid_fcst_time()}))
if has_next_page(body):
print(next_page_no(body))
asyncio.run(main())DataGoKrClient.iter_pages()๋ max_pages ๋๋ max_items guard๋ก ๋ฌดํ ๋ฐ๋ณต์ ๋ฐฉ์งํฉ๋๋ค.
from kma import DataGoKrClient
import asyncio
async def main() -> None:
async with DataGoKrClient.from_env() as client:
async for body in (client.iter_pages(
"MidFcstInfoService",
"getMidLandFcst",
{"regId": "11B00000", "tmFc": "202605010600"},
num_of_rows=100,
max_pages=10,
)):
...
asyncio.run(main())์ค๊ธฐ์๋ณด๋ MidFcstInfoService ํธ์ถ๊ณผ row parsing๊น์ง๋ง ์ฑ
์์ง๋๋ค. reg_id๋ ๋จ๊ธฐ์๋ณด nx/ny์ ๋ค๋ฅธ KMA ์ค๊ธฐ์๋ณด ๊ถ์ญ ์ฝ๋์ด๋ฉฐ, kma๋ ์์ ๋งคํ์ ์ถ์ธกํ์ง ์์ต๋๋ค. tm_fc๋ฅผ ์๋ตํ๋ฉด 06:00/18:00 ๋ฐํ์ 10๋ถ ์กฐํ ์ง์ฐ์ ๋ฐ์ํ ์ต์ tmFc๋ฅผ ์ฌ์ฉํฉ๋๋ค.
from kma import DataGoKrClient
import asyncio
async def main() -> None:
async with DataGoKrClient.from_env() as client:
(await client.mid_forecast(stn_id="108", tm_fc="202605010600"))
(await client.mid_land_forecast(reg_id="11B00000", tm_fc="202605010600"))
(await client.mid_land_forecast(reg_id="11B00000")) # ์ต์ ์กฐํ ๊ฐ๋ฅ tmFc ์๋ ์ ํ
(await client.mid_temperature_forecast(reg_id="11B10101", tm_fc="202605010600"))
(await client.mid_sea_forecast(reg_id="12A20000", tm_fc="202605010600"))
asyncio.run(main())๊ฐ row๋ MidForecastItem์ด๋ฉฐ operation, tm_fc, reg_id, stn_id, raw, metadata๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ค์๋ฒ MidFcstInfoService ์๋ต row๋ ์์ฒญ์ tmFc๋ฅผ ์์ฝํ์ง ์์ต๋๋ค. MidForecastItem.tm_fc๋ ์๋ต row์ tmFc๊ฐ ์์ผ๋ฉด ๊ทธ ๊ฐ์ ์ฐ์ ํ๊ณ , ์๊ฑฐ๋ ๋น ๋ฌธ์์ด์ด๋ฉด ์์ฒญ์ ์ค์ ๋ก ์ฌ์ฉํ(์๋ ์ ํ ํฌํจ) tmFc๋ก ํด๋ฐฑํด ํญ์ ๋ฐํ์๊ฐ์ ์๋ณํ ์ ์์ต๋๋ค. raw์๋ ํด๋ฐฑ ๊ฐ์ ์ฃผ์
ํ์ง ์๊ณ ์๋ต ์๋ณธ์ ๊ทธ๋๋ก ๋ณด์กดํฉ๋๋ค.
2026-05-07์ ๊ณต๊ณต๋ฐ์ดํฐํฌํธ ๊ธฐ์์ฒญ ์คํ API ๊ฒ์์์ ํ์ธํ ์ฃผ์ data.go.kr ์๋น์ค๋ ์ ์ฉ helper๋ฅผ ์ ๊ณตํฉ๋๋ค. endpoint๋ณ ์์ ์ ์ธ ๋๋ฉ์ธ ๋ชจ๋ธ์ ํ์ ํ๊ธฐ ์ด๋ ค์ด ์๋น์ค๋ DataGoKrItem์ผ๋ก ๊ฐ์ธ๋ฉฐ, ๊ฐ row์ raw์ ์ธ์ฆํค๊ฐ ์ ๊ฑฐ๋ metadata๋ฅผ ๋ณด์กดํฉ๋๋ค.
import asyncio
from kma import DataGoKrClient
async def main() -> None:
async with DataGoKrClient.from_env() as client:
daily = (await client.asos_daily_weather(
start_dt="20260501",
end_dt="20260502",
stn_ids=108,
))
hourly = (await client.asos_hourly_weather(
start_dt="20260501",
start_hh="00",
end_dt="20260501",
end_hh="23",
stn_ids=108,
))
warnings = (await client.weather_warning_list(
stn_id=108,
from_tm_fc="20260501",
to_tm_fc="20260502",
))
situation = (await client.weather_situation(stn_id=108))
land = (await client.land_forecast_message(reg_id="11B10101"))
sea = (await client.sea_forecast_message(reg_id="12A20100"))
tour = (await client.tour_village_forecast(course_id=1, current_date="20260501", hour="09"))
climate = (await client.city_tour_climate_index(city_area_id=1100000000, current_date="20260501", day=3))
uv = (await client.uv_index(area_no="1100000000", time="2026050106"))
air = (await client.air_diffusion_index(area_no="1100000000", time="2026050106"))
sen = (await client.sensible_temperature_index(
area_no="1100000000",
time="2026050106",
request_code="A41",
))
quake = (await client.earthquake_message_list(from_tm_fc="20260501", to_tm_fc="20260502"))
asyncio.run(main())์ง์ ๋ฒ์:
| service | helper |
|---|---|
AsosDalyInfoService/getWthrDataList |
asos_daily_weather() |
AsosHourlyInfoService/getWthrDataList |
asos_hourly_weather() |
WthrWrnInfoService/* |
weather_warning(), weather_warning_list() |
VilageFcstMsgService/* |
forecast_message(), weather_situation(), land_forecast_message(), sea_forecast_message() |
TourStnInfoService1/* |
tour_village_forecast(), city_tour_climate_index() |
LivingWthrIdxServiceV4/* |
sensible_temperature_index(), uv_index(), air_diffusion_index() |
EqkInfoService/* |
earthquake_info(), earthquake_message(), earthquake_message_list(), tsunami_message(), tsunami_message_list() |
weather_warning(), forecast_message(), earthquake_info()๋ ๊ฐ์ ์๋น์ค ์์ ๋ค๋ฅธ operation์ ์ง์ ์ง์ ํ ์ ์๋ ์์ helper์
๋๋ค. ํ์ค response.body.items.item ํํ๋ฅผ ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ raw row wrapper๋ก ๋ฐํํฉ๋๋ค.
๊ณต๊ณต๋ฐ์ดํฐํฌํธ ๊ธฐ์์ฒญ_์ ๊ตญ ํด์์์ฅ ๋ ์จ ์กฐํ์๋น์ค
(BeachInfoservice)๋ DataGoKrClient์ ์ ์ฉ helper๋ก ํธ์ถํ ์ ์์ต๋๋ค.
import asyncio
from kma import DataGoKrClient
async def main() -> None:
async with DataGoKrClient.from_env() as client:
ultra = (await client.beach_ultra_short_forecast(
beach_num=1,
base_date="20220622",
base_time="1230",
))
forecast = (await client.beach_forecast(beach_num=1))
waves = (await client.beach_wave_height(beach_num=1, search_time="202205011600"))
tides = (await client.beach_tide_info(beach_num=1, base_date="20220620"))
sun = (await client.beach_sun_info(beach_num=1, base_date="20220501"))
water = (await client.beach_water_temperature(beach_num=1, search_time="202205011600"))
asyncio.run(main())์ง์ operation:
| method | service/operation | ๋ฐํ ๋ชจ๋ธ |
|---|---|---|
beach_ultra_short_forecast() |
BeachInfoservice/getUltraSrtFcstBeach |
list[BeachForecastItem] |
beach_forecast() |
BeachInfoservice/getVilageFcstBeach |
list[BeachForecastItem] |
beach_wave_height() |
BeachInfoservice/getWhBuoyBeach |
list[BeachWaveHeight] |
beach_tide_info() |
BeachInfoservice/getTideInfoBeach |
list[BeachTideItem] |
beach_sun_info() |
BeachInfoservice/getSunInfoBeach |
list[BeachSunTime] |
beach_water_temperature() |
BeachInfoservice/getTwBuoyBeach |
list[BeachWaterTemperature] |
์ด๋จ๊ธฐ์๋ณด์ ๋จ๊ธฐ์๋ณด helper๋ base_date์ base_time์ ์๋ตํ๋ฉด KST ๊ธฐ์ค ์ต์ ์กฐํ ๊ฐ๋ฅ ๋ฐํ์๊ฐ์ ์๋ ์ ํํฉ๋๋ค. ๋ช
์ํ ๋๋ ๋ ๊ฐ์ ํจ๊ป ์ ๋ฌํด์ผ ํฉ๋๋ค.
์ฃผ์ํ ์ :
beach_num์ ํด๋ณ์ฝ๋์ ๋๋ค. ์๋/๊ฒฝ๋๋nx/ny๊ฐ ์๋๋๋ค.- ํ๊ณ ์ ์์จ ์กฐํ์
search_time์YYYYMMDDHHMM์ ๋๋ค. - ์ผ์ถ์ผ๋ชฐ endpoint๋ upstream Swagger๊ฐ ๋ ์ง ํ๋ผ๋ฏธํฐ๋ฅผ
Base_date๋ก ํ๊ธฐํ๋ฏ๋ก helper๊ฐ ์ด ์ด๋ฆ์ ๊ทธ๋๋ก ์ฌ์ฉํฉ๋๋ค. - ๋ชจ๋ ๋ชจ๋ธ์
raw์ ์ธ์ฆํค๊ฐ ์ ๊ฑฐ๋metadata๋ฅผ ๋ณด์กดํฉ๋๋ค.
์ค์ ์๋ฒ ํ
์คํธ์์๋ง ์ฐ๋ ์ธ์ฆํค๋ .env.local์ ๋ ์ ์์ต๋๋ค. ์ด ํ์ผ์ .gitignore์ ํฌํจ๋์ด ์ปค๋ฐ๋์ง ์์ต๋๋ค.
DATA_GO_KR_SERVICE_KEY=<data.go.kr decoded service key>
DATA_GO_KR_SERVICE_KEY=<data.go.kr decoded service key>
dataType=JSON ์์ฒญ์ด์ด๋ data.go.kr gateway๋ quotaยท์ธ์ฆ ์ค๋ฅ๋ฅผ HTTP 200
OpenAPI_ServiceResponse XML๋ก ๋ฐํํ ์ ์์ต๋๋ค. DataGoKrClient์ ๋น๋๊ธฐ
ํธ์ถ์ JSON parse ์คํจ ๋ XML์ returnReasonCode/resultCode๋ฅผ ๊ณตํต result-code
์ ์ฑ
์ผ๋ก ๋ถ๋ฅํฉ๋๋ค. 03์ ๋น items/totalCount=0, 22๋
KmaRequestError(failure_kind="quota", retryable=False)์ด๋ฉฐ, XML์ด ์๋๊ฑฐ๋ ์ฝ๋๊ฐ
์์ผ๋ฉด KmaParseError์
๋๋ค.
๊ณต์ data.go.kr ํ์ด์ง์์ ํ์ธํ KMA REST ์๋น์ค ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
| service | operation ์์ | ๋น๊ณ |
|---|---|---|
VilageFcstInfoService_2.0 |
getUltraSrtNcst, getVilageFcst |
KmaClient๊ฐ typed ์ง์ |
MidFcstInfoService |
getMidFcst, getMidTa, getMidLandFcst, getMidSeaFcst |
generic JSON/XML envelope |
WthrWrnInfoService |
getWthrWrnList |
๊ธฐ์ํน๋ณด |
AsosDalyInfoService |
getWthrDataList |
ASOS ์ผ์๋ฃ |
YdstInfoService |
getYdstSatlitImg, getYdstObs |
ํฉ์ฌ์ ๋ณด |
LgtDistrbInfoService |
getLgtDistrb |
๋๋ขฐ๋ถํฌ๋ |
CloudSatlitInfoService |
getGk2acldAll ๋ฑ |
์์ฑ์๋ฃ ๊ฒฝ๋ํ |
UppInfoService |
ํ์ฉ๊ฐ์ด๋๋ณ operation | ๊ณ ์ธต๊ธฐ์๊ด์ธก |
์ผ๋ถ ์ต์ data.go.kr ํญ๋ชฉ์ ๊ธฐ์กด serviceKey gateway๊ฐ ์๋๋ผ APIHub๋ก ์ด๋ํ๋ LINK ์ ํ์
๋๋ค. ๊ทธ๋ฐ ๊ฒฝ์ฐ ApiHubClient๋ฅผ ์ฌ์ฉํฉ๋๋ค.
requests params=๋ก ํธ์ถํ ๋๋ Decoding service key๋ฅผ ์ฌ์ฉํฉ๋๋ค.
DataGoKrClient(service_key="decoded-key")์ด๋ฏธ ์ธ์ฝ๋ฉ๋ ํค๋ฅผ params=์ ๋ฃ์ผ๋ฉด ๋ค์ ์ธ์ฝ๋ฉ๋์ด SERVICE_KEY_IS_NOT_REGISTERED_ERROR๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.