3步搞定天气通官网数据抓取,手写实现避坑指南
官方文档动辄几十页,翻完脑子还是空的?别慌。很多项目现场管理员接手“天气通官网”对接任务时,最大的噩梦不是写代码,而是在那堆晦涩的 API 描述和鉴权流程里迷路。其实,核心逻辑就三板斧:获取 Token、请求数据、解析结果。
今天这篇教程,我不贴那种复制粘贴就能跑但出了错就抓瞎的“玩具代码”。我们直接手写实现一套轻量级的数据获取方案。哪怕你只懂一点点前端基础,跟着敲一遍,就能在实战中把天气数据稳稳地抓到手。重点不在于背下每个参数,而在于搞懂数据流动的逻辑,这样以后换接口、换字段,你都能心里有数。
概念速懂:别被名词吓住
在动手前,先厘清三个最容易混淆的概念。很多初学者在这里卡壳,导致后面报错找不到方向。
- API Key 与 Secret:这是你的“身份证”和“密码”。Key 用于标识你是谁,Secret 用于验证你是否是你。在“天气通官网”的开发者后台生成后,严禁硬编码在前端代码里,这等于把家门钥匙贴在大门上。
- Token 机制:为了防止 Secret 泄露,通常第一步是用 Key+Secret 去换一个短期的 Token。这个 Token 有时效性,过期了就得重新换。
- 经纬度 vs 城市名:天气数据接口通常依赖精确的地理位置。虽然部分接口支持城市名搜索,但生产环境建议直接传入经纬度,或者先调用“地理编码接口”把城市名转成经纬度。
避坑提示:很多新手会问,“为什么我请求成功了,但数据是空的?” 90% 的原因是 IP 白名单没加。检查一下你的服务器出口 IP 是否在开发者后台配置了。
环境准备:极简配置
我们不需要重型框架,Node.js 环境配合 axios 库足矣。如果你的项目是纯前端 Vue 或 React,逻辑完全通用,只是网络请求库换成 fetch 或 axios 而已。
安装依赖:
npm install axios
关键配置:在项目的 .env 文件中配置敏感信息。
# .env
WEATHER_API_KEY=your_key_here
WEATHER_API_SECRET=your_secret_here
WEATHER_BASE_URL=https://api.weather-tong.example.com
注意:确保 .env 文件已加入 .gitignore,防止密钥泄露到 GitHub 开源仓库 中。一旦密钥泄露,立即去官网重置,否则你的调用量会被恶意刷爆,甚至产生高额账单。
核心语法:手写 Token 获取逻辑
很多官方示例直接给了一个 getWeather() 函数,但隐藏了最关键的鉴权步骤。这里我们手写实现最底层的鉴权逻辑,让你看清 HTTP 请求的本质。
const axios = require('axios');
const crypto = require('crypto');class WeatherClient {constructor() {this.apiKey = process.env.WEATHER_API_KEY;this.apiSecret = process.env.WEATHER_API_SECRET;this.baseUrl = process.env.WEATHER_BASE_URL;this.token = null;this.tokenExpiry = 0;}// 核心:生成签名,模拟官方鉴权逻辑generateSign(params) {// 1. 参数按字母顺序排序const sortedKeys = Object.keys(params).sort();// 2. 拼接成 query stringconst queryString = sortedKeys.map(key => `${key}=${params[key]}`).join('&');// 3. 使用 HMAC-SHA256 签名const sign = crypto.createHmac('sha256', this.apiSecret).update(queryString).digest('hex');return sign;}// 获取或刷新 Tokenasync getToken() {// 如果 Token 还有效(提前 60 秒过期),直接返回if (this.token && Date.now() < this.tokenExpiry) {return this.token;}const timestamp = Date.now();const params = {key: this.apiKey,timestamp: timestamp,nonce: Math.random().toString(36).substring(2) // 随机字符串防重放};const sign = this.generateSign(params);const url = `${this.baseUrl}/v1/auth/token?${new URLSearchParams({...params, sign})}`;try {const response = await axios.get(url, {headers: { 'Content-Type': 'application/json' }});if (response.data.code === 200) {this.token = response.data.data.access_token;// 假设 Token 有效期 7200 秒,这里我们设为 7140 秒(提前 1 分钟刷新)this.tokenExpiry = Date.now() + 7140 * 1000;return this.token;} else {throw new Error(`Auth Failed: ${response.data.message}`);}} catch (error) {console.error('Token 获取失败:', error.message);throw error;}}
}module.exports = WeatherClient;
逐行解析重点:
crypto.createHmac:这是签名的核心。官方文档通常会强调“使用 HMAC-SHA256”,这里就是具体实现。nonce字段:随机数。这是为了防止“重放攻击”,即黑客截获你的请求包反复发送。tokenExpiry缓存:不要每次请求天气都去换 Token,那样性能极差且容易触发频率限制。这里做了一个简单的内存缓存。
完整代码示例:从鉴权到数据解析
有了鉴权模块,接下来就是真正的数据获取。我们封装一个 fetchWeather 方法,并加入错误处理。
const WeatherClient = require('./weatherClient');class WeatherService {constructor() {this.client = new WeatherClient();}async fetchWeather(lat, lon) {try {// 1. 获取有效的 Tokenconst token = await this.client.getToken();// 2. 构建请求头const headers = {'Authorization': `Bearer ${token}`,'X-App-Key': this.client.apiKey};// 3. 发起业务请求const url = `${this.client.baseUrl}/v1/weather/current`;const params = {lat: lat,lon: lon,units: 'metric' // 使用公制单位:摄氏度、米/秒};const response = await axios.get(url, { params, headers });// 4. 解析数据if (response.data.code !== 200) {throw new Error(`API Error: ${response.data.message}`);}return this.formatData(response.data.data);} catch (error) {// 区分是网络错误、鉴权错误还是业务错误if (error.response) {// 服务器返回了错误状态码if (error.response.status === 401) {console.warn('Token 已失效,强制刷新...');// 可选:强制重置 token 并重试一次this.client.token = null;return this.fetchWeather(lat, lon); }throw new Error(`HTTP ${error.response.status}: ${error.response.data.message}`);} else if (error.request) {// 请求已发出但没有收到响应throw new Error('Network Error: 无法连接服务器,请检查 IP 白名单或网络状态');} else {throw error;}}}// 格式化数据,只保留前端展示需要的字段formatData(raw) {return {temp: raw.temp, // 温度feelsLike: raw.feels_like, // 体感温度humidity: raw.humidity, // 湿度windSpeed: raw.wind_speed, // 风速weatherDesc: raw.weather.description, // 天气描述(如:小雨)icon: raw.weather.icon, // 图标 URLupdateTime: new Date(raw.update_time).toLocaleString('zh-CN')};}
}// 使用示例
const service = new WeatherService();(async () => {try {// 假设获取北京的天气const data = await service.fetchWeather(39.9042, 116.4074);console.log('当前天气:', data);} catch (err) {console.error('获取失败:', err.message);}
})();
这段代码的亮点:
- 自动重试机制:捕获到
401错误时,自动清除旧 Token 并重试一次。这在网络波动或 Token 临界过期时非常有用。 - 数据瘦身:
formatData方法过滤掉了后端返回的大量冗余字段(如气压、紫外线指数等,如果前端不用)。减少数据传输量,提升加载速度。 - 明确的错误分类:区分了网络层错误(Network Error)和业务层错误(API Error),方便现场管理员快速定位是网断了还是账号没钱了。
常见报错:现场急救包
在实际项目中,以下三个报错占了 80% 的情况。
| 错误代码 | 常见原因 | 解决方案 |
|---|---|---|
| 401 Unauthorized | Token 过期、Secret 错误、IP 不在白名单 | 检查 .env 配置;强制刷新 Token;联系服务商加白名单。 |
| 429 Too Many Requests | 请求频率超限 | 加入请求队列或节流(Throttle);升级 API 套餐;检查是否有死循环请求。 |
| 404 Not Found | 接口路径错误、经纬度格式错误 | 检查 URL 拼写;确保经纬度是 lat, lon 顺序,且为数字类型而非字符串。 |
特别提示:如果报错 404,请仔细检查 URL 末尾是否有多余的斜杠 /,或者 params 中的经纬度是否被序列化了。有时候 lat=39.9 和 lat='39.9' 在某些严格的后端实现中会导致 404。
小结:从文档到代码的跨越
回到开头的话题,官方文档太长抓不住重点,是因为它试图涵盖所有边缘情况。但作为开发者,我们只需要掌握主干流程:鉴权 -> 请求 -> 解析。
通过手写实现这套逻辑,你不再依赖黑盒库,而是真正理解了 HTTP 请求的每一次跳转。当“天气通官网”升级接口,或者你需要对接其他类似的气象数据源时,你只需要修改 baseUrl 和 generateSign 的逻辑,核心架构不用动。
对于项目现场管理员来说,这种可控的代码意味着更少的意外故障和更快的排错速度。不要害怕看源码,也不要害怕手写基础逻辑,那是你掌控项目的底气。
这个知识点你面试被问过吗? 特别是关于“Token 刷新策略”和“API 限流处理”的部分,很多后端和全栈岗位都会深挖。你在实际项目中遇到过哪些奇葩的天气 API 坑?留言说说,大家一起避坑。