按:本文是少数派会员专属内容,适逢春节返家潮,我们将其作为试读文章发出,希望给有类似想法或需求的朋友一些参考。少数派新版会员自 2022 年夏季上线以来,已经积累了数十万字的优质内容;我们还于近日为年度会员上线了测试版的会员社区,可以直接通过少数派 iOS app(web 版即将上线)与志趣相近的朋友交流。如果你对与本文类似的内容感兴趣,请考虑进一步了解并加入少数派会员。
背景
年初开始,我爸就时不时遇到手机莫名发烫重启或 app 突然闪退等问题,让我意识到他手上那部 2018 年初买的一加 5T 该退休了。一加简洁的「氢 OS」是我之前一直坚持使用一加手机的重要原因,可时至今日,一加终于也向市场妥协,让我决定把我父母的手机也从 Android 转向 iOS。
借助苹果的「转移到 iOS」app,从 Android 向 iOS 迁移的整体过程还算比较顺利,只需要一步步按照引导操作,就能很快将原来一加手机中的联系人、短信、照片这些重要数据迁移到新 iPhone 中。
但我很快发现苹果的转移助手漏掉了一个很重要的数据:备忘录。
我爸最近这两年偶尔会有血压升高的情况,所以他之前会时不时地把自己测量的血压数据记录到手机的备忘录里,前前后后已经产生了近 700 条不是特别规范的纯文本血压和心率记录。如何把这些数据导入 iPhone 成了问题。
思路
我首先大致在 iOS 健康 app 里摸索了一下,发现这个 app 并没有提供批量写入数据的功能,只提供了手动添加数据的功能。
添加过程操作并不方便;每次打开添加数据的面板,都要手动调整日期和时间。我简单尝试了一下,加一条数据差不多要半分钟,近 700 条数据如果都手动添加,差不多需要 6 个小时,果断放弃。
之后,我用各种关键词在 Google 上乱搜了一通,竟然一条能用的结果都没有,甚至知乎和百度知道上都没有相关的问题。居然没有人曾经遇到过把数据批量导入 iOS 健康 app 这个需求?这真让我挺意外的。
不过也并不是完全没有收获,虽然没有找到批量导入数据的文章,但找到一篇我派作者写的关于批量导出数据的文章《教你通过快捷指令建立个人健康指标数据库》。这篇文章用快捷指令读取健康 app 里数据的方法让我深受启发:既然快捷指令可以读取健康数据,应该也可以写入数据吧。
简单尝试之后我就明确了方案:先把我爸手动记录的备忘录数据清洗整理成格式统一的文本,发到 iPhone 上;再用快捷指令功能挨个处理这些文本数据,提取其中的关键信息,写入健康 app。
清洗数据
首先要解决的问题就是数据格式不统一。我爸用备忘录记录血压数据没有一个特别规整的格式,以下是几个例子:
20210313 星期三
11:00 142/78 64
20210314 星期日 晴20℃
06:54 165/89 心率70
20210316 星期二小雨
17℃
07:15 146/82 心率 67
20210319 星期五 阴
11℃
07:25 148/82 心率65
09:58 161/79(1) 73
152/77(2) 76
可以看到,这些数据除了血压,有时还会记录天气,其中的气温有时在日期的同一行,有时在单独行;数据之间有时会用单个空格分隔,有时会用多个空格;有时一天会连续记录两条;有时会用中文标记「心率」……
这样的数据很难用被批量处理,所以我需要先把所有数据都「清洗」成统一的格式。
数据清洗的方法其实非常多也非常灵活,需要结合数据的特点和自己的能力范围来选择合适的工具和方法。这里,只简单介绍几种最基础的常见手段供大家打开思路。
我用到了一款程序员非常熟悉的编辑器 Visual Studio Code(简称 VS code),大家可以从官方网站免费下载使用。当然,你也可以使用熟悉其他编辑器工具,方法大同小异。我使用 macOS,如果你的系统是 Windows,快捷键与文中提及的会有所区别(大多数可以直接将 Command
换成 Ctrl
)。
删除多余内容
清理日期所在行中的多余数据:批量选择和多光标
注意到记录中的日期都是 2021 或 2022 开头,所以先选中第一行前面的「202」,然后可以选择如下方法之一:
- 连续按
Command
+D
键(将下一个匹配项添加到选择区域);或者 - 按
Shift
+Command
+L
(选中所有与已选部分相同的区域)。
这样就可以一次性选中文档中所有的「202」。
现在按一下右箭头键,就能在文档中所有 2021 或 2022 之后添加一个光标。按钮把光标一起移动到每个日期行中的日期数据后面,然后用 Command
+ Shift
+ →
组合键(将选中区域延伸到行尾),从而批量选中每个日期行中除日期以外的数据。
最后一下按删除键,日期数据就被「清洗」干净了。
删除无关信息:使用正则查找替换
接下来,我们用查找替换的功能删除数据中与心率无关的信息。
首先是一个简单的纯文本查找:要删掉所有的「心率」字样,直接用 Command
+ F
打开搜索替换面板,搜索「心率」并批量替换为空即可。
接下来删除气温数据。由于涉及变量,需要使用正则表达式。为此,点击搜索框最右侧的按钮,启用正则表达式匹配。
这里用到的正则表达式是:
[0-9]{2}℃\n
它的意思是,连续两个数字,后接一个摄氏度符号 ℃,后接一个换行。
将其替换为空,就去掉了所有单成一行的气温数据。
再来删除用来标记多次记录的数字编号 (1)、(2) 等。用到的正则表达式是: ([0-9])
,同样替换为空。
最后去掉多余的空格。这里既可以用普通搜索,将两个空格替换为一个空格,多次操作直到不再有连续空格;也可以用正则表达式 \s{2,}
,搜索两个或以上的连续空格,替换为一个空格。
经过上面一系列操作,数据就变成了下面这样,明显更加清爽:
20210313
11:00 142/78 64
20210314
06:54 165/89 70
20210316
07:15 146/82 67
20210319
07:25 148/82 65
09:58 161/79 73
152/77 76
统一记录格式:使用 JavaScript
能不能再整洁一点呢?我想将格式统一成「YYYYMMDD HH:MM/高压/低压/心率」这样的格式。为此,一点点在编辑器中查找替换当然也可以,但要想一步到位,就得写一点简单的程序了。
我这里直接分享自己用 JavaScript 写的脚本,供大家参考。主要思路是根据每行开头的内容判断其内容,如日期、血压数据等,然后分别通过正则表达式查找替换为所需的格式。每行的具体作用可以参见注释。
const data = ``;
function main() {
// 把数据按行拆分
const dataItems = data.split("\n");
const dataToShow = []; // 格式化好的数据列表
let currentDate = ""; // 当前日期
let currentTime = ""; // 当前时间
// 遍历按行拆分之后的数据,也就是遍历每一行数据
dataItems.forEach((dataItem) => {
if (dataItem.startsWith("202")) {
// 当前是日期开头的行
currentDate = dataItem.trim();
} else if (dataItem.trim().length === 0) {
// 当前是空行
} else if (dataItem.match(/^[0-9]{2}\:[0-9]{2}/)) {
// 当前是时间开头的行
dataToShow.push(`${currentDate} ${dataItem.trim().replace(" ", "/")}`);
currentTime = dataItem.match(/^[0-9]{2}\:[0-9]{2}/)[0];
} else {
// 当前是纯数据行
dataToShow.push(
`${currentDate} ${currentTime}/${dataItem.trim().replace(" ", "/")}`
);
}
});
// 把格式化好的数据显示出来
console.log(dataToShow.join("\n"));
}
main();
用 JavaScript 的一个附带好处是不需要额外安装运行环境:大家电脑上都有的浏览器就是天然的 JavaScript 运行工具。
以 Chrome 或 Edge 浏览器为例:随便打开一个新标签页,按 F12 键就打开开发者工具,然后这时切换到「控制台」选项卡。
现在,把我们刚刚处理了一半的数据赋给上面代码中的 data
变量,即粘贴到代码第一行的一对反引号 ``
内部,然后将代码整体粘贴到控制台中:
按下回车键,就可以看到程序输出的结果了:
选中程序输出的数据,保存为文本文件,发送到 iPhone 上。
至于如何传输,方法很多:
- 如果你的电脑是 Mac,可以用 AirDrop 功能直接把文件发送到 iPhone 上。
- 如果你是 Windows 用户,可以用微信文件传输助手。
- 当然,还可以硬核一点,通过 NodeJS 的
http-server
或者 Python 的http.server
模块等,让 iPhone 可以直接在 Safari 中访问局域网文件内容。
小结
这一节,我简要的介绍了多选、查找替换、正则表达式匹配、JavaScript 脚本这几种数据清洗方法。其中正则表达式和 JavaScript 编程可能会有一些门槛,但这也不是必须的,你完全可以使用你自己熟悉的各种方法来清洗数据。比如,如果你熟练掌握 Excel,上面那些数据处理步骤用 Excel 也能完成。
如果你对正则表达感兴趣,我推荐一个零基础交互式教程 RegexLearn。只用一两个小时就能大幅提升你的文本搜索能力。如果你对 JavaScript 编程感兴趣,我推荐《现代 JavaScript 教程》。对于本文目的,只用看这个教程第一部分的 1、2、5 三个章节就行。
制作快捷指令
清洗和传输数据后,终于到了激动人心的快捷指令编排环节。
在正式开始之前,我们需要先思考清楚处理数据的步骤和逻辑。我们前面处理好的数据如下所示,每行代表一条记录,每行有日期时间、高压、低压、心率 4 组数据。
20220515 05:55/148/79/58
20220515 05:58/136/76/58
20220515 13:16/137/72/68
根据这个数据结构,快捷指令的逻辑就是:
- 获取所有数据;
- 按行拆分数据;
- 针对每一行处理数据;
- 结束。
其中第三步的处理过程又分为这几个步骤:
- 用
/
符号分割数据分别获取日期时间、高压、低压、心率; - 用日期时间、高低压数据创建血压记录;
- 用日期时间、心率数据创建心率记录。
逻辑梳理清楚了就开干啦,首先把最外层的框架编排出来。遵循循序渐进的原则,比较复杂的第三步暂时不做,用一个「显示通知」占位,顺便测试效果。
这时复制三条数据测试一下:
可以看到,执行快捷指令之后,会连续弹出三条通知把刚刚复制的数据内容显示出来,说明快捷指令的外层循环框架已经完成了。
接下来就是编排第三步里面的逻辑。将之前用来占位的通知步骤替换为红框中的部分:
简单来说,这些步骤提取剪贴板中的每行文本,将 /
符号视为内部分隔符,拆分出日期时间、高压、低压和心率等各项数据。谨慎起见,最后一步暂时不直接写入健康 app,而是继续用一个通知显示处理结果,作为测试。
经过测试,弹出的通知能按照给定格式显示数据,说明我们的数据拆分逻辑是正确可行的。
接下来就是把这些数据写入健康 app 了。将上一步中的显示通知步骤替换为下图中的步骤:
这里,我们提取之前步骤获得的各项信息,填写到「记录健康样本」步骤的各个栏位中。
此外,这里还用到一个技巧:快捷指令可以识别并灵活调整变量的类型,只要选中相关变量,在弹出的面板中选择正确的类型即可。如下图所示,我们将 datetime
变量的类型固定为「日期」。
好了,一切就绪,让我们先用几条测试数据来看看效果:
可以看到,三条数据在执行快捷指令后立刻就被写入了健康 app。在健康 app 中查看时,显示的图标也说明这些数据是由快捷指令写入的。
执行脚本
数据和脚本都准备好了,就可以正式开始导入数据了。
首先把电脑上准备好的文本数据文件发到手机上,并保存到「文件」app 中。长按这个文件,点选「快速查看」,然后长按并拖动选区,直到将整个文件的所有内容都选中后,点击复制,这样文件内容就都保存到剪贴板中了。
现在来到快捷指令 app 中,点击运行我们刚刚编写好的快捷指令。如果你的数据量比较多,可能需要多等一会;快捷指令的右上角此时会显示一个进度圆环:
比如我一次导入了将近 700 条数据,在这个状态大概等了差不多 1 分钟,等到右上角按钮变成对勾的时候,数据就导入完成啦。
总结
这次数据导入的过程,从最初的摸索到最终数据导入完成大概花了一个半小时。虽然时间也不短,但和手动输入的近 6 个小时比起来,还是节省了很多时间,过程也有趣得多。而且如果去掉摸索和走弯路的过程,仅仅清洗数据并导入其实只需要十来分钟就可以完成。如果正在读文章的你在生活中也凑巧碰到了类似的需求,相信你花费的时间要比我少得多。希望大家能有所收获。