简介面向小米手环用户与数据取证爱好者的自动化导出工具包通过解包Zepp Life软件直接读取底层数据库帮助查看手环记录的具体健康数据并支持后续二次分析与归档。资源共7个文件由6个Python脚本和1个带注释的JSON5数据文件组成压缩包仅8KB。脚本覆盖日期数据提取、备份重建、SFTP远程获取、全天压力提取及tar.gz解压等环节可满足本地解析、远程同步与数据恢复等典型使用场景。JSON5文件内置字段注释便于理解数据结构降低手动分析门槛。当前已有11109人学习下载适合具备一定Python基础、希望深入挖掘自身健康数据或进行电子数据取证的读者。1. 小米手环数据自动化导出把散在 App 里的健康数据变成可持续更新的本地档案很多人以为小米手环的数据导出就是打开 App 截图或者等官方给报告。实际用过就知道步数、心率、睡眠在「小米运动健康」App 里只能一段一段看官方要么不给导出要么给加密压缩包没法分析。这个自动化导出工具解决整条链路把数据从 App 本地数据库自动备份出来解析时间戳和字段合并成按天的 CSV再挂定时任务每天增量更新。不用 root手环不用一直在线。适合做健康数据分析、想留档多年数据、想把手环数据接进自己统计脚本的人。下面按实际拆过的路径来写坑直接给结论。2. 数据到底存在哪本地 SQLite、云端接口和 BLE 直读三条路怎么选2.1 三条数据源的取舍BLE 直读被绑定加密卡住云端接口没有公开文档想自动化导出数据来源只有三条路BLE/GATT 直读手环、云端接口、App 本地 SQLite 库。我选型时做的对比表数据源数据完整度自动化最大的障碍BLE/GATT 直读步数、心率、睡眠都有但历史明细受手环存储限制小米手环 4 之后绑定加密需要设备级密钥手环必须在蓝牙范围内云端接口完整和 App 一致没有公开 API请求带 sign 签名签名逻辑随版本变App 本地 SQLite完整含步数、心率、睡眠、PAI、血氧、压力文件在 App 私有目录需要备份/拉取手段BLE 直读这条线小米手环 3 及更早型号有社区逆向过的 GATT 协议可以直接读历史数据小米手环 4 之后的绑定流程加了加密鉴权每台设备需要单独密钥普通用户拿不到。就算拿到了自动化脚本每次跑都要求手环在蓝牙范围内手机、手环、电脑三方都得在位没法无人值守。所以这条线只适合折腾老手环的玩家。云端接口数据最全但小米运动健康没有公开文档App 每个版本都会换 sign 签名参数今天能跑的脚本明天可能就 403。我见过有人把签名逻辑逆向出来一次 App 大版本更新全废维护成本比写导出工具本身还高。App 设置里也有「导出个人数据」入口往邮箱发一个加密 zip密码要向客服申请导出来字段还不全同样做不了自动化。2.2 为什么本地 SQLite 是自动化导出的主力路径本地 SQLite 这条线数据完整度和云端一致因为 App 本身就是把云端数据缓存在本地、再把本地采集的数据上云健康明细在 databases 目录下全都有。选它做自动化的理由很直接不依赖网络离线也能导不要求手环在线导出时手环扔抽屉里都行文件就在手机上拿到 db 就等于拿到所有原始数据后续解析逻辑完全可控。代价只有一个数据库在 App 私有目录得先想办法把文件弄出来这个在第三章展开。拿到 db 之后第一件事我一般用 DB Browser for SQLite 这类图形化工具打开看表结构先确认有哪些表、字段叫什么再写解析脚本而不是对着文档盲写。另外注意同步时机本地库一直在写入新采样数据但云端数据只有打开 App 时才会拉下来。手机很久没打开 App库里会缺最近几天数据。所以我每次跑导出前先手动打开一次 App 让它同步再拉备份。这个动作也能用 adb 模拟点击自动化但收益不大手动开一下更省心。2.3 库文件与核心表长什么样schema_map 的由来以我拆过的版本为例小米手环 4/5/6 配小米运动健康 Appdatabases 目录下好几个 .db健康明细集中在一个主库里大小从十几 MB 到上百 MB 不等取决于记录时长。核心表按数据种类分三块步数按天/按小时汇总心率按分钟一条睡眠按「段」存储——每段睡眠一条记录带入睡时间和醒来时间阶段明细深睡/浅睡/快速眼动/清醒存在明细表或 blob 里。表名和字段名不同版本差异很大。有的版本步数表叫 step_data有的叫 hc_step_data心率表有叫 heart_rate 的也有 hr_data睡眠表更是五花八门还出现过同表内字段改名的情况。老版本 App 用的 Zepp Life 库结构又是另一套。所以工具包维护一个 schema_map.json把「逻辑字段」映射到「当前 App 版本的实际字段」每次跑导出前自动核对列名对不上就打印实际表结构退出不硬解析。宁可让你改一行映射也不能让脚本把脏数据写进 CSV。3. 把数据库从手机里弄出来adb 备份、文件捞取与时间戳解码3.1 不用 root 的三条备份路径第一步确认包名。小米手环的 App 换过名字老的是 com.xiaomi.hm.health新的是 com.mi.fitness先查清楚adb shell pm list packages | grep -i mi查到包名后按 Android 版本挑路径。Android 11 及以下adb backup 还能用adb backup -f miband.ab -noapk com.mi.fitness java -jar abe.jar unpack miband.ab miband.tar tar xf miband.tar参数说明-f 指定备份文件输出路径-noapk 只要数据不要安装包省时间包名和上一步查到的一致。解出的 tar 里就是 App 私有数据目录重点找 db 目录下的主库文件。判断哪个是主库文件最大、名称带 step/health 字样的基本都是不确定就用 DB Browser 打开看表名。如果电脑上连着多台设备adb 命令要加 -s 指定序列号否则直接报 more than one device。提示先打开 App 完成同步再备份否则拉出来的是几天前的库。备份/解包每次生成新的 .ab 并解到新目录别做覆盖续传解包工具中途失败很容易留下半截 tar 让你误判。Android 12 之后很多机型把 adb backup 禁了备份出来 0 字节或提示 backup not enabled。这时候换厂商备份手机系统设置里找「备份到本地」把 App 数据打包后用 adb pull 拉出来解压。部分机型允许直接访问 Android/data/com.mi.fitness/ 目录能读就直接拷。root 设备最省事cp /data/data/com.mi.fitness/databases/ 整个目录。3.2 用 Python 读 SQLite 并解码时间戳拿到 db 后开始解析关键是时间戳处理。库里的时间戳几乎都是 UTC 毫秒直接转本地时间会差 8 小时导致每天的步数归到错误日期import sqlite3 import json import datetime conn sqlite3.connect(miband.db) cur conn.cursor() with open(schema_map.json, encodingutf-8) as f: schema json.load(f)[step_data] sql fSELECT {schema[ts]}, {schema[value]} FROM {schema[table]} rows cur.execute(sql).fetchall() tz datetime.timezone(datetime.timedelta(hours8)) # 固定东八区 for ts, val in rows: if val in (0, 255): continue dt datetime.datetime.fromtimestamp(ts / 1000, tz) print(dt.date(), val)逻辑说明所有行先除以 1000 转成秒再交给 fromtimestamp避免把毫秒当秒用时区强制写死东八区而不是系统默认时区防止脚本跑到 UTC 环境的机器上日期全错。0 和 255 是无意义占位值导出第一步就滤掉否则统计里会出现一小时 200 步这种假数据。解析时报表不存在先不要急着改代码用命令行列出实际表名sqlite3 miband.db .tables然后把 schema_map.json 里的表名改成实际值。心率、睡眠明细如果存成 blob用 SELECT typeof(字段) 看类型gzip 的先解压再看字段protobuf 的按 proto 定义解。绝大多数情况下步数、心率、睡眠都能在普通字段拿到blob 明细只影响睡眠阶段拆分。3.3 一条命令跑完备份到导出全流程工具包把流程串成一个入口python3 export.py \ --db miband.db \ --out ./data/csv \ --tz Asia/Shanghai \ --types step,sleep,heart参数说明--db 是上一步解出来的主库路径--out 输出目录脚本按日期分文件如 2025-01-01.csv--tz 指定日期归属时区这个参数必须显式传别依赖系统时区--types 控制导哪几类数据不导心率时能省一半时间。导出完成后脚本写 manifest.json记录每个类型最后的起止时间戳给第四章的增量逻辑用。还有一个细节导出时如果 App 还在后台写库SQLite 的锁可能让你读到半截事务或拿不到最新数据。我的习惯是拉备份前先滑掉 App 后台再执行脚本这两分钟换来的是一份干净的数据。4. 增量导出与合并去重每天自动更新但不出脏数据4.1 为什么要做增量全量导出会越来越大首次导出跑全量没问题但每天全量跑解析时间会越来越长而且手环重启、App 重装后云端会把全量数据重新灌回本地库全量导出的结果和昨天完全重叠。正确做法是第一次全量之后只拉「最后一次导出时间戳之后」的新数据。增量断点存在 manifest.json 里每种类型分别记{ step: {last_ts_ms: 1744876800000}, heart: {last_ts_ms: 1744876800000}, sleep: {last_ts_ms: 1744876200000} }逻辑说明三种类型分别记断点因为 App 同步节奏不同心率每小时都在写睡眠一天只有几条。增量 SQL 变成 WHERE ts_ms ?把对应断点传进去。注意断点取「本次成功导出的最后一条时间戳」而不是当前时间否则时间戳乱序的迟到数据会被漏掉。4.2 合并去重以设备加指标加时间戳为唯一键手环重启后继传到本地库的数据时间戳是乱序的晚到的旧数据时间戳可能小于断点所以增量拉取只是第一层去重合并才是兜底import pandas as pd from pathlib import Path IN_DIR Path(./data/csv) def merge_day(day: str): new_path Path(f./incoming/{day}.csv) old_path IN_DIR / f{day}.csv new_df pd.read_csv(new_path) if not old_path.exists(): new_df.to_csv(old_path, indexFalse) return old_df pd.read_csv(old_path) key [device_id, metric, ts_ms] merged pd.concat([old_df, new_df], ignore_indexTrue) merged merged.drop_duplicates(subsetkey, keeplast) merged merged.sort_values(ts_ms).reset_index(dropTrue) merged.to_csv(old_path, indexFalse)逻辑说明唯一键用 device_id metric ts_ms而不是「日期数值」。同一天内心率有上千条只看日期会误删时间戳精确到毫秒同一条记录只可能出现一次。keeplast 表示后到的覆盖先到的App 回灌时修正的数据能纠正旧值。参数说明家里有多块手环时 device_id 字段一定要保留否则多设备互相覆盖metric 是步数/心率/睡眠的类别名三类数据放同一 CSV 靠它区分也可以拆目录看个人习惯。这个 merge 脚本用 pandas 就能跑一年心率数据约 52 万行内存完全够不用上数据库。4.3 挂到系统调度里crontab 与 Windows 任务计划Linux/macOS 用 crontab30 8 * * * cd /opt/miband bash run.sh /var/log/miband.log 21run.sh 内部顺序adb connect 检查设备在线 → 拉备份 → 解包 → export.py 增量导出 → merge 去重 → 写 manifest。任何一步失败脚本以非 0 退出cron 会发错误邮件。Windows 上用任务计划程序触发器设每天 08:30操作指向 run.batadb connect 192.168.1.100:5555 python export.py --db miband.db --out .\data\csv --tz Asia/Shanghai --types step,sleep,heart python merge.py --in .\incoming --out .\data\csvbat 脚本里每条命令后都要判断 errorlevel我在 Windows 上常栽在「中间失败但脚本继续跑」最后导出的数据缺一截还不自知所以每步都加 if errorlevel 1 exit /b 1。实际部署建议手机开无线调试放家里 Wi-Fi 下脚本开头 adb connect断线就重连一次还连不上就退出报警不硬跑。日志文件记得按天轮转不然一年下来几个 GB。5. 避坑清单日期错位、重复行、加密备份五个坑一次说清下面五条是从连续跑了几十次导出的血泪经验里筛出来的按现象、原因、解决写照着能少翻两次车。5.1 步数按 UTC 归档凌晨数据跑到前一天现象CSV 里每天凌晨 0 点到 8 点的步数记到前一天App 里是 3 月 2 日的记录脚本输出成 3 月 1 日。原因库里时间戳是 UTC 毫秒脚本用 datetime.fromtimestamp(ts) 没传时区环境变量又是 UTC日期归属整体偏移 8 小时。解决解析时统一传 Asia/Shanghai时区写进配置而不是依赖系统环境。我在 export.py 里把时区参数设成必填缺了就报错退出宁可失败也不允许用默认时区蒙混过关。5.2 睡眠总时长和 App 对不上少 40 分钟现象App 显示昨晚睡眠 7 小时 12 分脚本算出来只有 6 小时 30 分。原因睡眠按「段」存储一段可能横跨午夜脚本按结束日期分组跨天段被拆两半或漏算部分记录结束时间为空脚本直接丢掉。解决按入睡日期归档而不是醒来日期结束时间为空的段用该段最后一条阶段明细的结束时间补上。对账以各阶段分钟数之和为准不用入睡和醒来两个时间点硬减中间可能有无心率空档。5.3 增量导出后 CSV 出现重复行现象昨天导出的 6 月 10 日文件有 1400 行今天再跑变成 2100 行。原因手环重启后 App 把全量数据重新回灌旧记录时间戳没变但行数翻倍增量脚本按时间断点拉取这些旧数据全被当成新数据。解决合并阶段用 device_id metric ts_ms 唯一键去重keeplast 保留修正值。我在 run.sh 里加了一步合并后统计每个日期文件的行数差超过正常增量预期 5 倍就告警人工确认是否又发生回灌。5.4 Android 12 以上 adb backup 打出来 0 字节现象adb backup 执行成功生成文件 0 字节或解包时提示 backup not enabled。原因Android 12 开始系统默认不允许传统 adb backupApp 没声明 allowBackup 或系统直接拦截备份只剩空壳。解决换厂商「备份到本地」功能打包后 adb pull或看设备能否直接访问 Android/data/com.mi.fitness/能就拷出来root 设备 cp 整个 databases 目录。还有一个思路App 设置里把账号同步关掉再打开触发一次全量同步然后立刻翻系统备份目录找最新备份文件。5.5 心率数据里全是 0 和 255现象导出的心率分钟数据出现大量 0 或 255平均心率明显偏低。原因手环没戴紧、摘下充电、后台被清理这段时间采样失败库里写入占位值。255 在部分版本里表示离线补录值含义随版本不同。解决导出阶段统一过滤 0 和 255CSV 里保留 quality 字段原始标记分析时某小时有效心率不足 30 条按缺失处理而不是硬算平均。export.py 里过滤规则做成可配置默认过滤需要保留时用 --keep-invalid。6. 导出完怎么验证pytest 三表对账与随机抽样写完导出工具不算完数据对不对才是关键。我每次跑完增量导出一律做三道对账。第一道是步数对账随机抽三天把 CSV 当天步数求和跟 App 当天总步数比误差超 3% 就报警。第二道是睡眠对账深睡、浅睡、快速眼动、清醒四段加总跟 App 报告总时长比允许 5 分钟内偏差。第三道是心率抽查随机抽 3 个完整小时检查每分钟是否恰好一条时间间隔在 60 秒左右出现连续缺行说明链路丢数据。这三道对账我用 pytest 固化成自动化测试每次导出后自动执行import pytest def test_step_sum(v): assert abs(v.app_steps - v.csv_steps) / v.app_steps 0.03 def test_sleep_total(v): assert abs(v.app_sleep_minutes - v.csv_sleep_minutes) 5 def test_heart_rate_gap(v): gaps [b - a for a, b in zip(v.csv_ts, v.csv_ts[1:])] assert max(gaps) 90 and min(gaps) 30三个用例对应三道对账v 是装数据的容器。gap 上下限按 60 秒心跳间隔给 30~90 秒容差既允许时钟抖动又能在缺行时立刻暴露问题。从那以后我每次换手环、每次 App 大版本更新都强制全量导出加 pytest 对账跑完才把新数据并进历史库。这套习惯救了我好几次数据回流导致的脏数据都是对账抓出来的。工具包里导出、合并、对账脚本都收在一起拿到后按第三章流程跑一遍就能复现希望帮到你。本文还有配套的精品资源点击获取