在气象分析与建模工作中,获取长期历史天气记录是常见需求。气象局网站通常提供了标准化的数据接口,通过R语言编写脚本可以稳定地拉取指定站点、指定日期范围内的观测值,并将结果有序地保存到本地。本文围绕接口调用、字段解析与归档设计三个层面,说明一套可落地的实现方案。

气象局API的请求构造与鉴权
大多数气象局开放平台要求先申请开发者令牌,然后在每次请求中携带令牌与签名参数。以某省级气象数据接口为例,基础地址为 https://api.ipipp.com/weather/history,需要传入station_id、start_date、end_date与token。R语言中可以使用httr包组装查询,注意日期格式必须严格为YYYYMMDD,否则会返回空数据集。
除了令牌,部分接口还使用时间戳与随机串防止重放攻击。我们可以在脚本里用Sys.time()生成时间戳,再拼接待签名原文做MD5。下面示例展示如何构造带鉴权头的GET请求,并处理可能的限流返回码。当收到429时,应当暂停数秒再重试,而不是频繁发起调用,以免被封禁IP。
library(httr)
library(digest)
base_url <- "https://api.ipipp.com/weather/history"
token <- "your_token_here"
station <- "54511"
start <- "20230101"
end <- "20230131"
ts <- as.character(as.integer(Sys.time()))
nonce <- sample(letters, 8, replace = TRUE) |> paste(collapse = "")
sign <- digest(paste0(token, ts, nonce), algo = "md5")
resp <- GET(base_url, query = list(
station_id = station,
start_date = start,
end_date = end,
token = token,
timestamp = ts,
nonce = nonce,
sign = sign
))
if (status_code(resp) == 200) {
data <- content(resp, "parsed")
} else if (status_code(resp) == 429) {
Sys.sleep(5)
}
上述代码把鉴权逻辑封装在请求前,实际工程中建议将令牌与签名函数独立成模块,便于多任务复用。同时要注意,气象局接口常按日或按旬分页,若一次拉取整年数据可能触发长度限制,需要循环切分时间窗。
返回JSON的字段解析与清洗
接口一般返回JSON数组,每条记录包含观测时间、平均气温、最高最低温、降水量与风速等。R里用jsonlite包的fromJSON可直接转成数据框,但原始字段名常为英文缩写,需要重命名为业务易懂的列。例如t_avg改为avg_temp,pre改为precipitation。
真实数据里经常遇到缺失观测,接口可能返回null或空字符串。若不处理,后续写入数据库会报错。可以用na_if把空串转成NA,再根据相邻日期线性插补。下面代码演示解析并填补缺失气温的过程,同时把字符型日期转为Date类型,方便按时间排序。
library(jsonlite)
library(dplyr)
raw <- fromJSON(toJSON(data$list))
df <- raw |>
transmute(
obs_date = as.Date(date, "%Y-%m-%d"),
avg_temp = na_if(t_avg, ""),
max_temp = na_if(t_max, ""),
min_temp = na_if(t_min, ""),
precipitation = na_if(pre, ""),
wind_speed = na_if(ws, "")
)
df <- df |>
mutate(across(where(is.character), as.numeric)) |>
arrange(obs_date) |>
mutate(avg_temp = approx(obs_date, avg_temp, obs_date)$y)
清洗后的数据框应当做基础校验,比如气温是否在合理区间、降水非负。发现异常值可用clip截断或标记为疑误。这一步保证了归档数据质量,避免污染分析模型。另外,气象局时间多为北京时间,若本地环境时区不同,需用with_tz显式声明,防止日期错位。
本地归档策略与增量更新
数据落盘可选CSV或SQLite。CSV适合一次性导出给非技术同事,但频繁追加会产生重复头。SQLite则支持表结构约束与索引,更适合长期累积。使用RSQLite包可建立连接,用dbWriteTable以追加模式写入,并通过唯一索引避免重复插入同一天记录。
增量更新是归档系统的核心。我们可以记录上次抓取的最大日期,下次只请求该日期之后的值。这样既能减少接口压力,也能应对气象局后期修订历史数据的情况。下面示例展示如何比对库中已有日期并构造补数区间,同时把每日抓取日志写入独立表,方便排查断点。
library(RSQLite)
con <- dbConnect(SQLite(), "weather_archive.db")
existing <- dbGetQuery(con, "SELECT max(obs_date) AS mx FROM weather")
last_day <- if (is.na(existing$mx)) as.Date("2000-01-01") else existing$mx
new_start <- format(last_day + 1, "%Y%m%d")
new_end <- format(Sys.Date() - 1, "%Y%m%d")
if (new_start <= new_end) {
# 此处调用前文请求与解析函数获取 fresh_df
dbWriteTable(con, "weather", fresh_df, append = TRUE)
dbExecute(con, "CREATE UNIQUE INDEX IF NOT EXISTS idx_day ON weather(obs_date)")
}
dbDisconnect(con)
归档完成后,建议定期做完整性检查:统计每年天数是否接近365、关键站点是否连续无空洞。若发现某月缺失,可手动触发补抓任务。对于多站点场景,应为每个station_id建立分区视图,查询时按站点过滤,能够显著提升读取效率并降低单表膨胀风险。