串接中央氣象署
Node.js 與氣象數據:從 API 串接到你的第一個資料集
1. 核心工具:認識 axios
在我們開始抓取資料之前,我們需要一個工具來幫我們「發送網路請求」。
什麼是 axios?
axios 是一個非常受歡迎的 JavaScript 套件(庫),專門用來處理 HTTP 請求。您可以把它想像成 Node.js 的專屬「瀏覽器」,但它不是去抓網頁 (HTML),而是專門去跟 API 溝通並取回「純資料」(通常是 JSON)。
為什麼不用 Node.js 原生的 http 模組?
Node.js 內建了 http 模組,但它使用起來非常繁瑣(需要手動處理資料流、chunk 等)。axios 幫我們把這一切都打包好了,讓我們能用更簡潔的語法完成任務。
axios 的基本使用 (Async/Await)
axios 是基於 Promise 的。在 ES6+ 時代,我們最常用 async/await 語法來處理它,這讓非同步程式碼看起來像同步一樣好讀。
安裝:
npm install axios基本範例:
import axios from 'axios';
async function fetchData() {
try {
// 1. 使用 await 等待 axios.get() 完成
// 我們請求一個公開的測試 API
const response = await axios.get('https://api.example.com/data');
// 2. 資料會被 axios 自動打包在 .data 屬性中
console.log('成功取得資料:', response.data);
} catch (error) {
// 3. 如果網路出錯或 API 回傳錯誤狀態 (404, 500...)
console.error('抓取失敗:', error.message);
}
}
fetchData();2. 觀念建立:什麼是 API?fs 模組、JSON/CSV 處理
網站 (Website) vs. API:
- 網站 (HTML): 是設計給「人」看的,充滿了視覺設計、按鈕和圖片。
- API (Application Programming Interface): 是設計給「程式」看的。它像是一個「資料的點餐窗口」,我們只要用固定的格式去「點餐」(發送請求),它就會回傳純粹的「資料」。
JSON (JavaScript Object Notation):
- 一種輕量級的資料交換格式,也是 API 最常回傳的格式。
- 它的語法和 JavaScript 的「物件」幾乎一模一樣,所以 Node.js 處理起來非常方便。
3. 準備工作:建立專案與設定 API 金鑰
步驟一:取得中央氣象署 API 金鑰 (Authorization Key)
這是最重要的第一步。API 需要「認證」,防止被濫用。
- 請學生至 中央氣象署開放資料平台 註冊會員。
- 登入後,至「會員中心」 > 「取得授權碼」。
- 複製這串「授權碼」,這就是我們的 API Key。
步驟二:建立 Node.js 專案 (ES6 模式)
- 建立一個新的專案資料夾,例如
cwa-data-project。 - 打開終端機,進入該資料夾,並初始化專案:
npm init -y - [關鍵] 為了使用 ES6 的
import語法,打開package.json檔案,在裡面加入一行"type": "module":{ "name": "cwa-data-project", "version": "1.0.0"Node.js 與氣象數據:從 API 串接到你的第一個資料集, "description": "", "main": "index.js", "type": "module", <-- 加入這一行 // ... 其他內容 ... } - 安裝
axios:npm install axios
步驟三:安全管理 API Key (原生模組方法)
我們不希望將 API Key 這麼敏感的資訊直接寫在 index.js 主程式中。我們使用一個獨立的 config.js 檔案來存放它,並利用 ES6 模組的 export 功能。
- 在專案根目錄下,建立一個新檔案叫做
config.js。 - 在
config.js檔案中,貼上你的 API Key 並匯出:// config.js // 將你的金鑰貼在這裡 export const CWA_API_KEY = '這裡是你的API金鑰'; // (未來如果還有其他設定,也可以放這裡)
4. 實戰編碼:擷取「臺北市 36 小時天氣預報」
我們的目標:抓取「一般天氣預報-未來36小時天氣預報」(資料代碼 F-C0032-001)中「臺北市」的資料。
在專案中建立 index.js 檔案:
// index.js
// 1. 載入 ES6 模組
import axios from 'axios';
// [修改] 從我們自訂的 config.js 載入 API Key
import { CWA_API_KEY } from './config.js';
// 2. 變數設定
const API_KEY = CWA_API_KEY; // API Key
const DATA_ID = 'F-C0032-001'; // 資料代碼:36小時天氣預報
const LOCATION = '臺北市'; // 目標地點
/**
* 核心函式:使用 async/await 取得天氣資料
*/
async function getWeatherData() {
console.log(`🚀 正在擷取 [${LOCATION}] 的天氣資料...`);
// 組合 API 網址
const url = `https://opendata.cwa.gov.tw/api/v1/rest/datastore/${DATA_ID}`;
try {
// 3. 使用 axios 發送 GET 請求
const response = await axios.get(url, {
headers: {
'Authorization': API_KEY, // 4. 身份驗證
},
params: {
'format': 'JSON', // 5. 指定回傳格式
'locationName': LOCATION, // 6. 指定查詢地點
},
});
// 7. 檢查 API 是否成功回傳
if (response.data.success === 'true') {
console.log('✅ 資料擷取成功!');
// 8. 呼叫資料處理函式
processForecast(response.data.records);
} else {
console.error('❌ API 請求失敗:', response.data.message);
}
} catch (error) {
// 網路層級的錯誤 (例如:超時、404)
console.error('❌ 擷取資料時發生錯誤:', error.message);
}
}
/**
* 協助函式:處理並印出預報資料
* (這部分是資料科學的「資料清理」)
*/
function processForecast(records) {
const location = records.location[0];
console.log(`\n--- [${location.locationName}] 的未來 36 小時天氣預報 ---`);
// API 回傳的資料是「依要素分類」的 (天氣、降雨機率、溫度...)
// 我們需要把它們「依時間重組」
// 取得所有天氣要素
const weatherElements = location.weatherElement;
const wx = weatherElements.find(el => el.elementName === 'Wx'); // 天氣現象
const pop = weatherElements.find(el => el.elementName === 'PoP'); // 降雨機率
const minT = weatherElements.find(el => el.elementName === 'MinT'); // 最低溫度
const maxT = weatherElements.find(el => el.elementName === 'MaxT'); // 最高溫度
// 預報資料分為 3 個時段 (每 12 小時一筆)
for (let i = 0; i < wx.time.length; i++) {
console.log(`\n🗓️ 時段 ${i + 1}:`);
console.log(` ${wx.time[i].startTime}`);
console.log(` 至`);
console.log(` ${wx.time[i].endTime}`);
console.log('---');
console.log(` 天氣:${wx.time[i].parameter.parameterName}`);
console.log(` 降雨:${pop.time[i].parameter.parameterName} %`);
console.log(` 溫度:${minT.time[i].parameter.parameterName}°C - ${maxT.time[i].parameter.parameterName}°C`);
}
}
// 執行主程式
getWeatherData();執行程式
在終端機中執行:
node index.js預期輸出:
🚀 正在擷取 [臺北市] 的天氣資料...
✅ 資料擷取成功!
--- [臺北市] 的未來 36 小時天氣預報 ---
🗓️ 時段 1:
2025-10-27 18:00:00
至
2025-10-28 06:00:00
---
天氣:多雲
降雨:20 %
溫度:22°C - 25°C
... (以此類推)5. 資料科學應用與延伸
取得「乾淨」的資料只是第一步。資料科學的價值在於「應用」。
應用一:將資料儲存為 CSV 檔案 (使用原生 fs 模組)
學會將資料轉換成表格格式 (CSV),以便後續用 Excel, Python (Pandas) 或 R 進行分析。
(修改 index.js,加入 fs 模組)
// index.js
import axios from 'axios';
import { CWA_API_KEY } from './config.js';
import fs from 'fs'; // [新增] 載入 Node.js 內建的檔案系統模組
// ... (getWeatherData 函式保持不變) ...
/**
* 協助函式:處理預報資料並存檔
*/
function processForecast(records) {
// ... (同上,找到 location 和 weatherElements) ...
const locationName = records.location[0].locationName;
const wx = records.location[0].weatherElement.find(el => el.elementName === 'Wx');
const pop = records.location[0].weatherElement.find(el => el.elementName === 'PoP');
const minT = records.location[0].weatherElement.find(el => el.elementName === 'MinT');
const maxT = records.location[0].weatherElement.find(el => el.elementName === 'MaxT');
let csvContent = '地點,開始時間,結束時間,天氣現象,降雨機率(%),最低溫(C),最高溫(C)\n'; // CSV 標頭
for (let i = 0; i < wx.time.length; i++) {
// 組合 CSV 的每一列
const row = [
locationName,
wx.time[i].startTime,
wx.time[i].endTime,
wx.time[i].parameter.parameterName,
pop.time[i].parameter.parameterName,
minT.time[i].parameter.parameterName,
maxT.time[i].parameter.parameterName,
].join(','); // 用逗號分隔
csvContent += row + '\n';
}
// [新增] 寫入檔案
try {
fs.writeFileSync('forecast.csv', csvContent, 'utf8');
console.log('✅ 預報資料已儲存為 forecast.csv');
} catch (err) {
console.error('❌ 儲存 CSV 檔案時發生錯誤:', err.message);
}
}
// ... (執行主程式 getWeatherData()) ...應用二:簡易資料分析
- 自動天氣提醒器: 檢查未來 36 小時的
PoP(降雨機率),如果任何一個時段的parseInt(pop.time[i].parameter.parameterName)> 50,就在主控台印出「提醒:未來 36 小時內可能下雨,記得帶傘!」。 - 計算平均溫差: 找出
MaxT的最大值和MinT的最小值,計算溫差。
// index.js
// 1. 載入 ES6 模組
import axios from 'axios';
import { CWA_API_KEY } from './config.js'; // 從 config.js 載入 API Key
import fs from 'fs'; // 載入 Node.js 內建的檔案系統模組
// 2. 變數設定
const API_KEY = CWA_API_KEY; // API Key
const DATA_ID = 'F-C0032-001'; // 資料代碼:36小時天氣預報
const LOCATION = '臺北市'; // 目標地點
/**
* 核心函式:使用 async/await 取得天氣資料
*/
async function getWeatherData() {
console.log(`🚀 正在擷取 [${LOCATION}] 的天氣資料...`);
const url = `https://opendata.cwa.gov.tw/api/v1/rest/datastore/${DATA_ID}`;
try {
const response = await axios.get(url, {
headers: { 'Authorization': API_KEY },
params: {
'format': 'JSON',
'locationName': LOCATION,
},
});
if (response.data.success === 'true') {
console.log('✅ 資料擷取成功!');
// 呼叫 *更新後的* 資料處理函式
processForecast(response.data.records);
} else {
console.error('❌ API 請求失敗:', response.data.message);
}
} catch (error) {
console.error('❌ 擷取資料時發生錯誤:', error.message);
}
}
/**
* 協助函式:處理預報資料、存檔並執行簡易分析
* (*** 這是更新後的函式 ***)
*/
function processForecast(records) {
const location = records.location[0];
const locationName = location.locationName;
console.log(`\n--- [${locationName}] 的未來 36 小時天氣預報 ---`);
// 1. 取得所有天氣要素
const weatherElements = location.weatherElement;
const wx = weatherElements.find(el => el.elementName === 'Wx');
const pop = weatherElements.find(el => el.elementName === 'PoP');
const minT = weatherElements.find(el => el.elementName === 'MinT');
const maxT = weatherElements.find(el => el.elementName === 'MaxT');
// 2. 準備 CSV 內容
let csvContent = '地點,開始時間,結束時間,天氣現象,降雨機率(%),最低溫(C),最高溫(C)\n';
// 3. 準備分析用的變數
let willRain = false; // 降雨提醒旗標
let overallMinT = Infinity; // 36小時內的最低溫 (初始設為無限大)
let overallMaxT = -Infinity; // 36小時內的最高溫 (初始設為無限小)
// 4. 迭代處理 3 個時段
for (let i = 0; i < wx.time.length; i++) {
// --- (A) 取得時段資料 ---
const startTime = wx.time[i].startTime;
const endTime = wx.time[i].endTime;
const weatherDesc = wx.time[i].parameter.parameterName;
const rainProbStr = pop.time[i串接中央氣象署].parameter.parameterName;
const minTempStr = minT.time[i].parameter.parameterName;
const maxTempStr = maxT.time[i].parameter.parameterName;
// 印出時段預報 (原功能)
console.log(`\n🗓️ 時段 ${i + 1}: ${startTime} 至 ${endTime}`);
console.log(` 天氣:${weatherDesc}`);
console.log(` 降雨:${rainProbStr} %`);
console.log(` 溫度:${minTempStr}°C - ${maxTempStr}°C`);
// --- (B) 組合 CSV (原功能) ---
const row = [
locationName,
startTime,
endTime,
weatherDesc,
rainProbStr,
minTempStr,
maxTempStr,
].join(',');
csvContent += row + '\n';
// --- (C) 【新功能】執行資料分析 ---
// C-1: 檢查降雨機率 (將字串轉為數字)
const rainProbNum = parseInt(rainProbStr, 10);
if (rainProbNum > 50) {
willRain = true;
}
// C-2: 檢查高低溫 (將字串轉為數字)
const minTempNum = parseInt(minTempStr, 10);
const maxTempNum = parseInt(maxTempStr, 10);
// 更新36小時內的極端溫度
overallMinT = Math.min(overallMinT, minTempNum);
overallMaxT = Math.max(overallMaxT, maxTempNum);
}
// 5. 寫入 CSV 檔案 (原功能)
try {
fs.writeFileSync('forecast.csv', csvContent, 'utf8');
console.log('\n✅ 預報資料已儲存為 forecast.csv');
} catch (err) {
console.error('❌ 儲存 CSV 檔案時發生錯誤:', err.message);
}
// 6. 【新功能】印出分析結果
console.log('\n--- 簡易資料分析結果 ---');
// 6-1: 降雨提醒
if (willRain) {
console.log('☔️ 天氣提醒:未來 36 小時內有時段降雨機率超過 50%,記得帶傘!');
} else {
console.log('☀️ 天氣提醒:未來 36 小時降雨機率皆低於 50%。');
}
// 6-2: 溫差計算
const tempDifference = overallMaxT - overallMinT;
console.log(`🌡️ 溫差分析:未來 36 小時最大溫差為 ${tempDifference}°C (最高 ${overallMaxT}°C, 最低 ${overallMinT}°C)`);
}
// 執行主程式
getWeatherData();