用 AI 分析你的 Apple Watch 健康數據:從匯出到洞察的完整教程

Apple Watch 每天默默記錄你的心率、睡眠、步數、血氧……但大多數人只是偶爾瞄一眼 App,從未真正「讀懂」這些數據。這篇教程教你把健康數據完整匯出,用 Python 整理成 CSV,再交給 AI 做深度分析。


目錄


第一步:從 iPhone 匯出健康數據

  1. 打開 iPhone 上的「健康」App
  2. 點右上角的頭像
  3. 滑到頁面最底部
  4. 點「匯出所有健康資料
  5. 等待打包完成(依資料量需要 1~5 分鐘)
  6. 選擇儲存位置(建議選 iCloud 雲碟,方便後續在電腦下載)
匯出的 export.zip 通常有幾百 MB,請確保手機和 iCloud 空間足夠。

第二步:把 export.zip 傳到電腦

根據你的環境選擇最方便的方式:

方式適合情境
iCloud Drive最簡單,匯出時直接存到雲碟,電腦端下載
電子郵件寄到自己信箱,電腦下載附件
USB + iTunes有線傳輸,速度最快
AirDrop僅限 Mac,Windows 不支援

第三步:安裝 Python

Windows

  1. 前往 python.org/downloads
  2. Download Python 3.x.x
  3. 執行安裝程式時,務必勾選「Add Python to PATH」

    ☑ Add Python to PATH   ← 這個一定要勾!

macOS

macOS 通常已內建 Python 3,打開「終端機」輸入以下指令確認:

python3 --version

若未安裝,同樣前往 python.org 下載,或使用 Homebrew:

brew install python

第四步:執行轉換腳本

下載腳本

將以下腳本儲存為 apple_health_to_csv.py

#!/usr/bin/env python3
"""
Apple Health Export → CSV 轉換工具
支援:心率、步數、活動消耗、心電圖、血氧、睡眠、體重等所有指標
用法:python apple_health_to_csv.py [export.zip 或 export.xml 路徑]
"""

import zipfile
import xml.etree.ElementTree as ET
import csv
import os
import sys
import re
from collections import defaultdict
from datetime import datetime

RECORD_TYPES = {
    "HKQuantityTypeIdentifierHeartRate":                  ("heart_rate",        ["date", "time", "value_bpm", "device"]),
    "HKQuantityTypeIdentifierHeartRateVariabilitySDNN":   ("hrv",               ["date", "time", "value_ms", "device"]),
    "HKQuantityTypeIdentifierRestingHeartRate":           ("resting_heart_rate",["date", "value_bpm", "device"]),
    "HKQuantityTypeIdentifierWalkingHeartRateAverage":    ("walking_heart_rate",["date", "value_bpm", "device"]),
    "HKQuantityTypeIdentifierOxygenSaturation":           ("blood_oxygen",      ["date", "time", "value_pct", "device"]),
    "HKQuantityTypeIdentifierStepCount":                  ("steps",             ["date", "start_time", "end_time", "value_steps", "device"]),
    "HKQuantityTypeIdentifierDistanceWalkingRunning":     ("distance_walk_run", ["date", "start_time", "end_time", "value_km", "device"]),
    "HKQuantityTypeIdentifierActiveEnergyBurned":         ("active_energy",     ["date", "start_time", "end_time", "value_kcal", "device"]),
    "HKQuantityTypeIdentifierBasalEnergyBurned":          ("basal_energy",      ["date", "start_time", "end_time", "value_kcal", "device"]),
    "HKQuantityTypeIdentifierAppleExerciseTime":          ("exercise_time",     ["date", "start_time", "end_time", "value_min", "device"]),
    "HKQuantityTypeIdentifierAppleStandTime":             ("stand_time",        ["date", "start_time", "end_time", "value_min", "device"]),
    "HKQuantityTypeIdentifierBodyMass":                   ("body_mass",         ["date", "value_kg", "device"]),
    "HKQuantityTypeIdentifierBodyFatPercentage":          ("body_fat",          ["date", "value_pct", "device"]),
    "HKQuantityTypeIdentifierBodyMassIndex":              ("bmi",               ["date", "value", "device"]),
    "HKQuantityTypeIdentifierBloodPressureSystolic":      ("bp_systolic",       ["date", "time", "value_mmhg", "device"]),
    "HKQuantityTypeIdentifierBloodPressureDiastolic":     ("bp_diastolic",      ["date", "time", "value_mmhg", "device"]),
    "HKQuantityTypeIdentifierBloodGlucose":               ("blood_glucose",     ["date", "time", "value_mmol", "device"]),
    "HKQuantityTypeIdentifierRespiratoryRate":            ("respiratory_rate",  ["date", "time", "value_bpm", "device"]),
    "HKQuantityTypeIdentifierVO2Max":                     ("vo2max",            ["date", "value_ml_kg_min", "device"]),
    "HKQuantityTypeIdentifierWalkingSpeed":               ("walking_speed",     ["date", "value_km_h", "device"]),
    "HKQuantityTypeIdentifierEnvironmentalAudioExposure": ("env_audio",         ["date", "time", "value_db", "device"]),
    "HKQuantityTypeIdentifierBodyTemperature":            ("body_temp",         ["date", "time", "value_c", "device"]),
    "HKQuantityTypeIdentifierAppleSleepingWristTemperature": ("wrist_temp",     ["date", "time", "value_c", "device"]),
}

def parse_dt(s):
    s = re.sub(r'\s[+-]\d{4}$', '', s.strip())
    try:
        dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
        return dt.strftime("%Y-%m-%d"), dt.strftime("%H:%M:%S")
    except ValueError:
        return s[:10], s[11:19]

def get_device(elem):
    full_device = elem.get("device", "")
    if "Apple Watch" in full_device:
        return "Apple Watch"
    elif "iPhone" in full_device:
        return "iPhone"
    return elem.get("sourceName", "unknown")[:30]

def parse_sleep(elem, rows):
    stage_map = {
        "HKCategoryValueSleepAnalysisInBed":            "in_bed",
        "HKCategoryValueSleepAnalysisAsleepUnspecified":"asleep",
        "HKCategoryValueSleepAnalysisAwake":            "awake",
        "HKCategoryValueSleepAnalysisAsleepCore":       "core",
        "HKCategoryValueSleepAnalysisAsleepDeep":       "deep",
        "HKCategoryValueSleepAnalysisAsleepREM":        "rem",
    }
    stage = stage_map.get(elem.get("value", ""), elem.get("value", ""))
    start_date, start_time = parse_dt(elem.get("startDate", ""))
    _, end_time = parse_dt(elem.get("endDate", ""))
    rows.append([start_date, start_time, end_time, stage, get_device(elem)])

def parse_workout(elem, rows):
    act_type = elem.get("workoutActivityType", "").replace("HKWorkoutActivityType", "")
    start_date, start_time = parse_dt(elem.get("startDate", ""))
    _, end_time = parse_dt(elem.get("endDate", ""))
    duration = elem.get("duration", "")
    energy, distance = "", ""
    for stat in elem.findall("WorkoutStatistics"):
        t = stat.get("type", "")
        if "ActiveEnergy" in t:
            energy = stat.get("sum", "")
        if "Distance" in t:
            distance = stat.get("sum", "")
    rows.append([start_date, start_time, end_time, act_type,
                 round(float(duration), 2) if duration else "",
                 round(float(energy), 2) if energy else "",
                 round(float(distance) / 1000, 4) if distance else "",
                 get_device(elem)])

def parse_ecg(elem, ecg_rows, voltage_rows):
    date, time_ = parse_dt(elem.get("startDate", ""))
    ecg_rows.append([date, time_, elem.get("classification",""),
                     elem.get("averageHeartRate",""), elem.get("symptomsStatus",""), get_device(elem)])
    for vp in elem.findall("VoltageMeasurement"):
        voltage_rows.append([date, time_, vp.get("timeSinceSampleStart",""), vp.get("leadVoltage","")])

def main():
    source = sys.argv[1] if len(sys.argv) > 1 else next(
        (p for p in ["export.zip", "export.xml"] if os.path.exists(p)), None)
    if not source:
        print(" 找不到 export.zip 或 export.xml")
        sys.exit(1)

    output_dir = "health_csv"
    os.makedirs(output_dir, exist_ok=True)

    record_data = defaultdict(list)
    sleep_rows, workout_rows, ecg_rows, voltage_rows = [], [], [], []
    total = 0

    print(f" 解析中:{source}")
    xml_file = zipfile.ZipFile(source).open(
        next(n for n in zipfile.ZipFile(source).namelist() if n.endswith("export.xml"))
    ) if source.endswith(".zip") else open(source, "rb")

    for event, elem in ET.iterparse(xml_file, events=("end",)):
        total += 1
        if elem.tag == "Record":
            rtype = elem.get("type", "")
            if rtype == "HKCategoryTypeIdentifierSleepAnalysis":
                parse_sleep(elem, sleep_rows)
            elif rtype in RECORD_TYPES:
                info = RECORD_TYPES[rtype]
                try:
                    val = round(float(elem.get("value", "")), 4)
                except:
                    val = elem.get("value", "")
                d, t = parse_dt(elem.get("startDate", ""))
                _, et = parse_dt(elem.get("endDate", elem.get("startDate","")))
                dev = get_device(elem)
                if len(info[1]) == 4:
                    record_data[rtype].append([d, t, val, dev])
                else:
                    record_data[rtype].append([d, t, et, val, dev])
        elif elem.tag == "Workout":
            parse_workout(elem, workout_rows)
        elif elem.tag == "Electrocardiogram":
            parse_ecg(elem, ecg_rows, voltage_rows)
        elem.clear()
        if total % 100000 == 0:
            print(f"  已處理 {total:,} 筆...")

    def write_csv(name, headers, rows):
        if not rows:
            return
        path = os.path.join(output_dir, f"{name}.csv")
        with open(path, "w", newline="", encoding="utf-8-sig") as f:
            csv.writer(f).writerow(headers)
            csv.writer(f).writerows(rows)
        print(f"   {name}.csv — {len(rows):,} 筆")

    for rtype, rows in record_data.items():
        info = RECORD_TYPES[rtype]
        write_csv(info[0], info[1], rows)

    write_csv("sleep", ["date","start_time","end_time","stage","device"], sleep_rows)
    write_csv("workouts", ["date","start_time","end_time","activity_type","duration_min","energy_kcal","distance_km","device"], workout_rows)
    write_csv("ecg_summary", ["date","time","classification","avg_heart_rate","symptoms","device"], ecg_rows)
    write_csv("ecg_voltage", ["date","time","time_since_start_ms","voltage_uV"], voltage_rows)

    print(f"\n 完成!CSV 已儲存至 ./{output_dir}/")

if __name__ == "__main__":
    main()

準備文件

把腳本和 export.zip 放在同一個資料夾:

C:\Users\你的名字\Desktop\health\
├── apple_health_to_csv.py
└── export.zip

執行(Windows)

在資料夾空白處按住 Shift + 右鍵,選「在此處開啟 PowerShell 視窗」,輸入:

python apple_health_to_csv.py export.zip

執行(macOS / Linux)

cd ~/Desktop/health
python3 apple_health_to_csv.py export.zip

輸出結果

執行完成後,同目錄會出現 health_csv/ 資料夾:

檔案內容
heart_rate.csv每次心率測量
hrv.csv心率變異性(HRV)
blood_oxygen.csv血氧 SpO2
steps.csv步數
active_energy.csv活動消耗卡路里
basal_energy.csv基礎代謝消耗
sleep.csv睡眠分期(深睡 / REM / 淺眠)
workouts.csv運動紀錄
ecg_summary.csv心電圖結論
ecg_voltage.csv心電圖電壓原始點
vo2max.csv最大攝氧量
body_mass.csv體重
ecg_voltage.csv 包含每筆心電圖的原始電壓點,檔案可能很大,分析時可視情況略過。

第五步:上傳 CSV 給 AI 分析

打開 claude.ai,把 health_csv/ 裡的 CSV 直接拖入對話框,搭配以下提示詞開始分析。


常見問題

Q:腳本執行時出現錯誤怎麼辦?
把完整錯誤訊息複製,貼給 AI 詢問,通常能快速定位問題。

Q:export.zip 很大,要全部上傳嗎?
不需要。建議先上傳你最關心的 2~3 個 CSV(如 heart_rate.csv + sleep.csv),分析完再補充其他指標。

Q:數據安全嗎?
健康數據屬於敏感個人資訊。如有顧慮,可在上傳前刪除 device 欄位或對日期進行偏移處理。