划线只是起点:Readmate 让微信读书笔记变成可重读的本地档案
1. Readmate 上线为什么微信读书里的“已读”会变成“未读”Readmate 上线了这个工具想解决的事情其实只有一个把微信读书里的书和划线带回来重读。过去一年我的微信读书书架从几十本涨到了几百本划线也攒了好几千条。但坦率讲真正会主动回去翻这些笔记的次数一只手数得过来。问题不在于我懒也不在于微信读书的笔记功能不好用而是“重读”这条路径实在太长了打开 App进到书架找到书点开笔记在一条条评论里往下滑过程中很容易被各种新书推荐带走注意力。最后大概率会关掉 App心里想着“下次再看”然后就没有下次了。更核心的问题其实是划线这个动作本身正在被当成阅读的终点。收藏了一本书就等于读过了画了一条线就等于记住了。但实际上划线只是起点真正有价值的是划线之后的重读。如果一个句子值得被标记出来那它大概率值得被翻出来多读几遍。可绝大多数人没有一套顺畅的重读机制所以那些让人眼前一亮的句子最后都安静地躺在数据库里再也没被唤起。Readmate 的出发点就是把这些数据从微信读书里“带回来”整理成本地、可搜索、可定期回看的阅读档案。它不尝试替代微信读书也不做笔记社区而是老老实实做三件事把书架和划线同步下来整理成干净的本地格式然后提供几种让人愿意重读的视图。简单说这是一个给重读爱好者和知识管理型用户准备的小工具。如果你像我一样在微信读书里存了很多书、画了很多线但总觉得笔记被“藏”起来了或者你担心有些书会下架、自己的账号数据不好迁出那 Readmate 的这套设计思路应该能给你一些参考。即使你完全不用这个工具后半部分关于数据怎么整理、增量同步怎么做、隐私边界怎么定的内容也可以直接迁移到你自己的笔记系统里。1.1 收藏越来越多重读次数越来越少我做过一次不太严谨的统计当时微信读书书架上有 600 多本书真正从头到尾读完的可能不到 60 本划线笔记累计超过 3000 条。更扎心的是过去三个月里主动翻看历史划线的次数只有几次。也就是说我花了很多时间在“标记喜欢的内容”上却几乎没有给这些内容一个再次出场的机会。这个现象在微信读书里其实很普遍。书架的收藏成本极低看到推荐就点一下“加入书架”划线成本也很低选中一句话点一下就好。但这两个动作带来的堆积速度远远大于消化的速度。书架变成了一种“数字囤积”划线变成了“收藏即遗忘”。Readmate 想解决的不是让你读得更快而是让已经标记过的东西能够被重新看见。它把重读变成一个低摩擦的动作而不是一条布满干扰的深链。1.2 划线本身不是目的重读才是我一直觉得划线这个行为背后藏着一种很朴素的期待这句话在某一天可能会用到可能会被想起来的瞬间重新击中。但现实是纯粹依赖“手动想起”几乎不可能。大脑对信息的遗忘曲线是非常陡峭的划线当周的回忆率还比较高一个月之后就会断崖式下降。所以 Readmate 里最基础的功能就是“重读列表”把历史划线按时间倒序、按书籍聚合、按主题筛选让用户每次打开都能看到一个低门槛的回顾入口。不必像考试一样强迫自己复习只需要像刷社交媒体一样快速划过去。工具再好如果每一次打开都让人感到负担它就不可能被长期使用。这也是我在设计重读视图时反复提醒自己的事情。2. 产品定位与数据边界Readmate 只做三件事Readmate 的定位从一开始就很明确它是一个“书摘重读工具”不是一个阅读 App更不是另一个知识库。市面上已经有不缺功能强大的笔记软件也不缺各种稍后读服务但微信读书的划线数据一直缺少一条顺畅的出口。很多人的做法是手动一条条复制粘贴到备忘录里效率低不说还容易丢上下文。Readmate 想做的就是把“微信读书里的书和划线”自动整理成一个可以长期使用、随时重读的本地档案。这个定位决定了它的功能边界第一只处理用户自己的微信读书数据第二只在本地做整理和分析第三只提供重读相关的展示方式。不做社区不做积分体系不做推荐算法。这里的每一条“不做”都是我认真想过之后才定下来的。因为一旦想做太多产品就会变成一个谁都用不顺手的大杂烩。2.1 书和划线的双向找回“把书和划线带回来重读”这句话拆开看其实是两个场景。第一个场景是“找书”微信读书里有些书可能会因为版权、授权等原因下架用户已经画过线的内容一旦离开平台就没有地方再看了。哪怕书名还在书架里打开后可能只剩一个封面和“无法阅读”的提示。Readmate 会按期把每本书的基本信息、阅读进度和划线内容同步到本地相当于给这些已经付出过阅读时间的内容留一份底稿。第二个场景是“找划线”想找某句话但想不起来是哪本书里的或者想集中看某个主题下的所有笔记。微信读书自己的笔记列表是按时间排列的做主题聚合非常费劲。Readmate 会把所有划线内容做成一个可搜索的本地索引支持按作者、按书名、按关键词查也支持把同一个主题下的划线拼在一起。这就是“双向找回”从一个句子找到书的出处也从一本书找到所有句子。需要强调的是Readmate 不会下载任何电子书正文也不会绕过微信读书的会员机制。它处理的信息范围很明确书名、作者、封面、阅读进度、划线文本和你在划线下面写的笔记。这些内容本质上是用户自己的阅读痕迹属于可以通过正常授权接口拿到的个人数据。这个边界我在产品说明里写得很清楚一方面是避免版权风险另一方面也是为了给用户安全感。2.2 本地持久化Markdown JSON SQLite数据整理方式是我认为 Readmate 最核心的设计之一。很多同类工具会把数据存在云端用户一旦停止订阅数据就变得难以导出。Readmate 则从一开始就采用“本地优先”的方案所有数据落在三个层的本地存储里分别是可读的 Markdown 文件、结构化 JSON 文件和 SQLite 数据库。Markdown 文件用来给人看。每本书一个文件夹文件夹里是这本书的信息页和全量划线笔记格式近似# 《人类简史》— 尤瓦尔·赫拉利 阅读状态已读完 最后同步2024-11-03 14:22 划线数量23 ## 第 5 章 史上最大骗局 不是人类驯化了小麦而是小麦驯化了人类。 笔记农业革命不一定是进步可能是陷阱。 ## 第 8 章 科学革命 科学革命并不是知识的革命而是无知的革命。 笔记承认无知是现代科学最核心的起点。JSON 文件用来给程序读保留完整字段方便以后做统计和迁移。SQLite 数据库则用来支持搜索、筛选和去重尤其适合划线数量超过几万条的情况。三层结构的好处是就算软件以后不更新了用户手里的 Markdown 文件也完全可用甚至可以没有 Readmate 也可以重读。2.3 为什么不做云端同步和社区产品设计过程中不止一个人问我“为什么不把划线同步到云端为什么不做个公开书摘广场”这两个功能确实很诱人也更容易做增长但我不打算做。原因很现实第一云端同步意味着要把用户的微信读书数据拿到自己的服务器上这会带来很大的信任成本也扩大了数据风险面第二社区化会让产品重心从“重读自己的内容”跑偏到“看别人的内容”到最后用户花在刷书摘上的时间会越来越多真正重读自己的划线反而越来越少。所以 Readmate 是一个完全没有账号体系的工具。它不需要手机号注册不需要邮箱验证不需要订阅后才能导出数据。你通过本地客户端授权微信读书把数据拉下来剩下的事情全都在设备上完成。这种“笨拙”的做法在一个所有产品都想让你登录、都想把你留在云端的时代反而成了一种宝贵的安全感。3. 关键实现拆解从书架到本地阅读档案如果说产品定位决定了“做什么”那技术实现就是“怎么做”。Readmate 第一版能够快速落地很大程度上是因为我没有去重造轮子而是把重点放在“数据整齐”和“流程不出错”上。下面这几个实现细节是我觉得最值得展开说的地方也是后来迭代中花时间最多的地方。3.1 登录态与会话获取只读接口拒绝后台抓取Readmate 在数据读取上采用了一个很保守的方法本地客户端内嵌一个浏览器视图用户在 Readmate 里访问微信读书网页版并完成扫码登录。登录成功之后会话信息只保存在内存或系统自带的安全存储里Readmate 只用来读取当前用户自己的书架、笔记和划线数据不会用来执行任何写操作。选择网页版而不是移动端原因有几个。网页版的接口返回数据结构更规整翻页逻辑也简单移动端的内容往往做了很多客户端专属处理解析起来容易碰到兼容问题。另一个原因是网页版登录不需要短信验证码用户用微信扫码就能完成授权整个过程对普通用户来说没有心理门槛。这里有一个比较关键的安全设计Readmate 不会把登录凭证上传到任何第三方服务器。同步请求直接从用户设备发到微信读书自己的服务端Readmate 的开发者也就是我完全看不到这些数据。很多人会怀疑“软件到底有没有偷偷上传数据”最直接的验证方式就是关闭网络之后同步仍然能正常工作。本地优先的好处就在这里它的隐私承诺是可以被技术审计的而不是一句空话。3.2 数据模型的取舍早期设计数据模型的时候我走了不少弯路。最初想把“书”和“划线”做成两张表书下面挂划线划线下面挂笔记看起来很清晰。但真正拿到微信读书的接口数据后发现事情没那么简单一章的内容在接口里可能被拆成多个段落节点划线文本可能横跨多个节点同一本书在网页端和手机端返回的章节 ID 可能不一样有些划线带有很多干扰信息比如引用的“来自微信读书”这类尾巴或者因为竖排排版出现的空格。最终的数据模型我砍掉了不少“看起来很美好”的字段只保留真正需要的实体核心字段说明BookbookId, title, author, cover, status以书为主体的基础信息HighlighthighlightId, bookId, chapterTitle, text, note, color划线和笔记SyncRecordbookId, updatedAt, checksum用于增量同步和冲突处理我用了一段简单的 TypeScript 类型描述核心结构type Book { bookId: string; title: string; author: string; coverUrl: string; status: reading | finished | unread; progress: number; updatedAt: string; }; type Highlight { id: string; bookId: string; chapterTitle: string; text: string; note?: string; color: yellow | green | blue | red; createdAt: string; source: wxreading; };少即是多。字段少的好处是解析逻辑稳定用户改个颜色、改个笔记内容都能准确反映到本地不会因为某个字段解析失败而中断整本同步。事实证明这个取舍是对的后面所有功能基本都没有推翻过这两个基础表。3.3 增量导入与去重逻辑第一次同步时把几万条划线全量导入还好真正麻烦的是之后每天的增量同步。如果每次都全量重建速度慢不说还容易把用户手动编辑过的本地内容覆盖掉。所以我给 SyncRecord 加了一个 checksum 字段用内容哈希来判断“这条划线有没有变化”。具体流程是每次同步时Readmate 先从微信读书拿到书的最后更新时间再根据本地记录的上次同步时间决定是否拉取全量笔记。对于拉回来的每一条划线计算bookId text chapterTitle color note的哈希值和本地已有记录的 checksum 对比。相同则跳过不同则更新本地新增但接口中不存在的记录则标记为 archived。这么做还有一个好处用户可以放心在本地给划线补充自己的笔记不会因为下一次同步被覆盖掉。哪怕某条划线在微信读书里被删了Readmate 也不会强行把本地的记录删掉只是把它收进“已归档”里。这个设计后来收到了很多好评因为很多人会把自己画过的线二次加工成自己的想法这种本地数据值得被保护。3.4 重读视图主题聚合与随机翻牌数据整齐之后重读体验就成了产品差异化的关键。Readmate 第一版提供了三种重读视图。第一种是“按书重读”适合完整看完某一本书之后隔一段时间再把划线过一遍第二种是“按时间重读”适合快速浏览最近新画的线第三种是“随机重读”每次随机抽取几条划线配合间隔重复的思想让旧内容在不经意间回到视线里。随机重读是我个人最喜欢的模式。它不追求系统性反而制造了一种“偶遇感”。很多当时画的时候没太在意的句子过了几个月再看到反而能读出不同的味道。有用户给这个功能起了个名字叫“记忆翻牌”我觉得很贴切。技术实现上其实很简单就是从 SQLite 里按时间倒序取出划线 ID再用加权随机抽几条权重取决于距离上次被抽到的时间和划线本身的长度。越久没出现的内容越容易被抽中这样就避免了永远抽到同一批热门划线的问题。4. 踩过的坑数据不全、封面防盗链、多端不一致任何工具只要是接第三方平台的数据就免不了被各种奇怪问题折磨。Readmate 从原型到正式上线中间遇到的坑不少但最有代表性的有四个拿出来说说主要是想给大家提个醒第三方平台的数据接口从来不是给你的产品准备的它随时可能变所以所有解析逻辑都要做好“失败也能接受”的心理准备。4.1 划线文本的完整性第一个坑出现在划线文本的解析上。微信读书网页版的页面展示和接口返回的数据并不完全一致。页面上看到的划线接口里可能被拆成多个 span 节点如果直接取文本经常会出现漏字、换行错乱的情况甚至会把“来自微信读书”这个水印尾巴带进去。解决思路有两个层级。第一层是用更细的数据结构去拼而不是直接取 innerText。需要根据节点的 data 属性判断哪些是真正的原文、哪些是 UI 元素拼完之后再做一次清洗去掉常见水印和多余空白。第二层是清洗规则不做死用正则表达式识别那些“看起来像水印”的固定后缀一旦接口变化导致水印文案变了至少还能把正文保下来。这个坑在开发自测阶段几乎不会暴露因为测试账号的书架不够杂一旦放到真实用户手里几百本不同类型的书各种排版情况全冒出来了。4.2 封面图的防盗链第二个坑是封面图加载不了。微信读书的封面图服务器做了防盗链处理直接在 Readmate 里引用远程图片地址经常会出现请求被拒绝或者直接返回空白。这个问题在移动端尤其明显很多 WebView 默认会自动附加 Referer导致图片请求被后端的防盗链策略拦下来。解决方案也比较常规在拉取书籍数据时把封面图下载到本地存成一个以bookId命名的文件并在 Markdown 和 HTML 视图里引用本地路径。这样一来即使微信读书的 CDN 后续对某个封面做了清理用户本地已经保存的那一份也不会受影响。下载封面的时候注意控制并发数避免一次性几十张图片同时下载导致设备网络拥堵我实际测下来5 个并发是比较稳定的值。4.3 同一本书在不同端的章节 ID 不一致第三个坑是章节 ID 的冲突。同一个微信读书账号在手机上看的书和在网页版导出的数据章节 ID 字段可能对不上。这就会导致同一本书记录被拆成两半或者更新划线之后旧的章节仍然留在数据库里造成“划线重复、章节分裂”的假象。去重逻辑不能单纯依赖 ID。我的做法是在匹配章节时先走bookId chapterId如果匹配不到就走bookId chapterTitle的模糊匹配。因为标题这种文本信息在不同端之间一般是一致的最多就是多了些空格或者全半角差异。做一层归一化之后把两个来源的数据合并到同一条章节记录下基本能解决大半冲突。剩下的极少数异常就保留原样绝不因为合并失败而丢弃数据。4.4 增量同步中“下架书”的处理第四个坑关乎下架书。微信读书的接口在用户取消收藏或者书籍下架之后经常会直接在当前列表里把这个书摘掉导致增量同步时以为这本书被删了。如果直接同步删掉本地数据用户之前在书里画下的几百条笔记就全没了这是不可接受的。Readmate 的应对策略是软删除本地记录不会因为“这次同步里没有它”而自动消失而是先把它标记成archive状态默认不在主列表里展示但可以通过搜索找回。只有用户在 Readmate 里主动执行“删除此书本地档案”数据才会被真正清除。这个策略花了我不少心思但非常值得之后收到的用户反馈里“数据从来没丢过”是被提到最多的信任点之一。5. 用户最关心的隐私问题你的书单和划线会跑到服务器上吗每次产品上线私信里问得最多的不是功能而是隐私。这我很理解读书数据其实是很私密的东西你在读什么、画了什么线、写了什么批注几乎等于你当前的思想状态。这些东西一旦被泄露比通讯录泄露还让人难受。所以 Readmate 在隐私设计上不是做了点表面功夫而是从架构上就把“不上传”定成了底线。5.1 本地优先的设计Readmate 的整个数据处理链路都在本机完成。微信读书网页版登录后同步请求由用户设备直接发出返回的数据直接写入本地文件索引、搜索、重读展示全部走本地 SQLite封面图也下载到本地。也就是说用户在安装 Readmate 之后即使关闭 Wi-Fi也能看到自己所有历史数据。这里说的“本地优先”不是一个营销口号而是可以被验证的事实。用户可以在系统网络监控里看到连接的目标地址如果发现有第三方服务器出现那一定不是 Readmate 的官方行为。为了让这个承诺更扎实Readmate 的隐私说明里写得也比较直白不收集使用统计数据不设远程配置不给用户埋点唯一的环境检查是版本更新提示而且这个提示也可以手动关闭。5.2 授权最小化微信读书登录之后能够拿到的权限范围其实很大等于用户把整个微信读书账号的操作权都交给了本地客户端。为了降低风险Readmate 在代码层面做了一个限制所有和微信读书相关的网络请求只允许 GET不允许 POST、PUT、DELETE。也就是说即使前端代码被改写出问题也没有能力修改用户在微信读书上的任何数据。这个“授权最小化”的思路我在文档里反复强调过。因为对一个本地工具来说规则不是靠产品文案约束出来的而是靠技术架构硬性限制出来的。任何请求如果试图执行写操作客户端内置的安全策略会直接拒绝。这既是对用户账号的保护也是对产品自身的保护。5.3 数据导出与可迁移性本地优先的另一个好处是数据永远可迁移。Readmate 在设置里提供了一键导出功能用户可以导出三种格式Markdown、JSON 和完整 SQLite 数据库。导出之后数据可以导入到 Obsidian、Notion、印象笔记或者干脆就在文件夹里直接打开看。我推荐普通用户用 Markdown 格式保存因为它是纯文本永远可读任何编辑器都能打开。喜欢折腾的用户可以选 SQLite 导出里面保留了最完整的结构化字段配合一些简单的 SQL 查询就能做出各种自定义统计。比如-- 统计最近 30 天画线最多的作者 SELECT author, COUNT(*) AS cnt FROM books b JOIN highlights h ON b.id h.book_id WHERE h.created_at datetime(now, -30 days) GROUP BY author ORDER BY cnt DESC LIMIT 20;这样的查询在 Readmate 里已经内置了可视化版本但用户仍然可以拿到原始数据做自己的分析。我始终认为工具可以消失但用户的数据不应该跟着消失。这个原则值得每一个做本地工具的开发者记在心里。6. 上线后的反馈与后续计划Readmate 上线之后我收到了不少真实用户的使用反馈。大多数反馈都集中在同一个感受“原来我在微信读书里攒了这么多东西。”有人看到了自己三年前画下的标记有人重新想起了当时读书的心情还有人拿着导出 Markdown 开始搭建自己的主题文集。这些反馈给我最大的启发是工具层面的效率其实还在其次真正珍贵的是它唤起了人和旧内容之间的连接。6.1 用户真正爱用的重读方式我原以为用户最常用的是“按书重读”但统计下来“随机重读”的使用频率反而最高。这也很合理因为它完全没有心理负担。如果你让我现在列一本“我要从头复盘划线”的书我会纠结先看哪本但如果随机给我弹出一句我反而会停下来读两秒钟。还有一类用户喜欢用“主题搜索”去重读尤其是写作方向比较明确的人。他们会搜“自由”“创造”“注意力”这类词把散落在几十本书里的相关划线全部捞出来拼成一份临时的主题文档。这种用法让 Readmate 变成了一个创作素材收集器不只是重读工具。我在后续迭代里也专门优化了搜索结果的排版让主题聚合页能直接复制成 Markdown方便这些人做二次加工。6.2 下一步规划后续版本我比较想做的有三件事。第一是给“重读”加上节奏感提供一个完全离线的每周回顾摘要把最近 30 天没怎么出现的划线重新排进高亮位第二是完善章节归属的模糊匹配进一步减少多端同步产生的重复章节第三是把导出格式增强一下增加对 EPUB 批注格式的兼容方便用户把本地划线导入其他阅读器。不过这些都是“以后的事”。眼下我更愿意把 Readmate 维持在一个小而稳的状态先把基本盘做扎实。说实在的做一个工具最难的从来不是写代码而是保持克制不把用户的需求当成无限堆功能的机会。对我个人来说能每天在随机重读里撞见一句之前画过的句子就已经很值了。毕竟书本可以放下但那些真正打动过你的话值得被再次想起。