微信读书书摘同步到Notion:Python脚本与API自动化实战

发布时间:2026/10/28 5:23:46
微信读书书摘同步到Notion:Python脚本与API自动化实战 把微信读书里的书摘和书架同步到 Notion这个事我琢磨了不少时间。微信读书的书摘做得不错划线、笔记、想法都挺顺手但数据一直被关在 App 里想按主题回顾、跟自己的知识库打通基本只能手动复制粘贴。Notion 这边则是另一个极端数据库、公式、双向链接、看板视图全都给你但没有现成的微信读书导入入口。这个项目就是干一件事让微信读书里的阅读数据自动流进 Notion变成一份能持续更新、能统计、能检索的个人阅读数据中心。如果你正在用 Notion 管理阅读笔记或者想学 Python 脚本加 API 自动化这篇内容可以直接照着做。我不打算写一套“云端全家桶”的重量级方案就聚焦在两条路径的组合上模拟微信读书 Web 端登录态抓取用户自己的书架与书摘数据再用 Notion 官方 API 写入指定数据库。整个过程我会拆成六块思路选型、基础准备、核心代码、自动化部署、数据库模板、问题排查。每一块都会包含我实测过的东西和踩过坑之后才明白的细节。1. 项目思路与方案选型1.1 为什么要把微信读书的书摘搬到 Notion先说需求。微信读书的书摘功能对“读的时候顺手划线”这件事很友好但对“读完之后回头看”很不友好。你在 App 里能看到某一本书的所有划线也能看到所有笔记但当你跨书搜索比如想找所有关于“注意力”相关的内容就只能一本一本翻效率很低。而且微信读书的导出能力有限自带的导出格式比较粗糙无法保留章节层级也很难接入其他知识管理工具。Notion 的价值在于它是一个“数据容器”。书籍可以是一条条数据库记录书摘可以是子记录或子块每本书可以关联多篇书摘每篇书摘可以被贴标签整个库还可以用视图切换成表格、看板、画廊甚至日历。这意味着你读过的所有内容可以在一个统一结构里被检索、被关联、被二次加工。把微信读书数据放进去之后我还可以给每本书打上“领域”标签比如产品、心理、编程然后按标签聚合所有划线内容。这种能力在读书 App 里基本做不到。我自己的实际情况是书架上有几百本书划线笔记累计几千条。最初几年靠人工整理整理了不到十分之一就放弃了。后来试过各种导出工具有的输出格式混乱有的停更许久有的只支持单一平台。这个项目最终锁定的目标很简单实现对个人微信读书数据的安全、自动、幂等同步每天一次跑完就结束错误能通知不会重复写入。1.2 技术路线对比为什么选“接口抓取 API 写入”动手之前我先列了三条技术路线对比了一下方案优点缺点结论官方导出 手动整理零开发导出格式有限无法自动化费时只适合一次性迁移完整爬虫抓取微信读书页面数据全解析复杂依赖页面结构维护成本高风险偏高不推荐登录态下调用微信读书 Web 接口抓取本人数据 Notion API 写入数据可控、可定时、可增量依赖 Cookie 和接口结构需要更新 Cookie平衡性好最终采用核心判断点在于“自动化”和“稳定性”。我需要的是一个能每天跑、不用我每天盯着的流程。手动导出的方案再完美只要需要人类介入就注定很难长期坚持。完整爬虫方案虽然能拿到更多页面级数据但代价是要把前端渲染出的 HTML 结构变化也纳入维护范围前端一改版爬虫就崩这是最让人疲惫的维护场景。相比之下Web 接口通常更稳定返回 JSON 结构也相对清晰配合定时任务和重试机制能实现“一次授权、定时执行”的长期运行。这条路线还有个容易被忽视的好处因为是登录态下的接口请求拿到的数据天然就是用户自己的不需要处理复杂的跨用户授权逻辑也不涉及任何平台限制绕过问题。要注意的是抓取频率不能太高毕竟任何平台都不希望自己的服务被高频轮询。我把同步频率控制在每天一次实测下来稳定也没有触发过任何限制。2. 核心基础准备2.1 微信读书数据获取机制微信读书的 Web 端登录态通常保存在 Cookie 中。你只需要在浏览器里完成一次登录把登录后产生的有效 Cookie 提取出来后续脚本请求就会以你的身份读取数据。整个过程可以理解为“使用你自己的账号读取你自己的书架与书摘”。书架数据主要来自书架接口返回内容一般包含书籍 ID、书名、作者、封面 URL、阅读状态等字段。书摘数据则按书籍维度获取每本书需要单独请求返回内容里通常包含划线内容、章节标题、划线时间、笔记类型等信息。由于书摘可能较多接口大概率会有分页参数比如基于 cursor 或页码的翻页逻辑因此脚本里需要做循环拉取。提示微信读书接口路径会随版本迭代变动。我在代码注释里保留的是“书架接口与书摘接口”的语义命名具体路径建议实现时先用浏览器的开发者工具观察一次真实请求再填入脚本。不要直接照着网上的旧教程死抄。一个关键细节是书摘类型有区别。有的是纯划线有的是划线加笔记有的是书籍想法。这几种数据在接口里的字段名、内容形式、甚至优先级都可能不同。我把“笔记”和“划线”分开处理纯划线只摘录原文划线加笔记则额外保存我的想法书籍想法单独作为一条记录。这样做的好处是在 Notion 里我可以区分“这本书哪里引起了我的注意”和“我对此有什么思考”。2.2 Notion API 接入三板斧Notion API 的使用门槛不高但第一次接都会卡在权限和页面发现上。整个过程只需要三步第一步在 Notion 里创建一个 Internal Integration。打开 Notion 的 Settings进入 Connections点击 New integration输入名称选择关联的工作空间提交后就能看到一串 Internal Integration Token。这串 Token 就是脚本调用 API 的身份凭证。第二步把集成连接到目标页面。这一步最容易漏。你光有 Token 不够还要在 Notion 页面的右上角菜单里通过 Connections 搜索并选中刚才创建的集成。如果不做这一步API 会返回 403提示没有权限访问该页面。其实可以理解为Token 只是身份具体能看哪些页面由页面所有者显式授权。第三步拿到数据库 ID。在 Notion 里打开你的目标数据库页面观察浏览器 URL。URL 的形如https://www.notion.so/你的工作区/数据库ID?v...中间那段 32 位字符串就是数据库 ID。脚本里查询和写入都需要它。请求头里还需要带Notion-Version我一直在用2022-06-28这个版本目前没有遇到兼容问题。所有调用都通过官方 API 进行这是 Notion 提供的能力合规且稳定。2.3 环境准备与依赖我的运行环境是 Python 3.10包管理用的 pip。核心依赖只有两个requests用于发 HTTP 请求retry用于失败重试。如果你想把数据发给推送服务可以额外装requests就够了不需要引入大型框架。敏感信息不要写死在代码里。我会把三样东西放进环境变量或配置文件微信读书 Cookie、Notion Token、Notion 数据库 ID。跑定时任务时这些值放进 GitHub Secrets本地调试时用.env文件加载但这个文件绝不提交到 Git 仓库。import os import requests WE_READ_COOKIE os.getenv(WE_READ_COOKIE) NOTION_TOKEN os.getenv(NOTION_TOKEN) DATABASE_ID os.getenv(NOTION_DATABASE_ID) HEADERS_NOTION { Authorization: fBearer {NOTION_TOKEN}, Notion-Version: 2022-06-28, Content-Type: application/json, }这段代码虽然简单但它把所有关键凭证都集中到了环境变量里后续部署到 GitHub Actions 时只需要在 Secrets 里配置同名变量即可。3. 核心实现从抓取数据到写入 Notion3.1 抓取书架与书摘数据整个抓取过程分成两个阶段先拿书架再逐本拿书摘。我以“书架接口 书摘接口”的语义来写示例代码实际路径请自行观察确认。书架请求的核心是带上 Cookie 发送 GET 请求然后解析响应中的书列表。书摘请求则通常需要传入书籍 ID 和分页游标循环直到所有书摘拉完。import json import requests def get_shelf(cookie): # 书架接口返回我书架上的书籍列表 url https://weread.qq.com/web/book/shelf/books resp requests.get(url, headers{Cookie: cookie}) data resp.json() books [] for obj in data.get(books, []): book_info obj.get(bookInfo, {}) books.append({ book_id: book_info.get(bookId), title: book_info.get(title), author: book_info.get(author), cover: book_info.get(cover), status: obj.get(readingStatus, 0), }) return books def get_book_highlights(cookie, book_id): # 书摘接口需要按书籍 ID 循环分页拉取 highlights [] cursor 0 while True: url https://weread.qq.com/web/book/highlights resp requests.get( url, params{bookId: book_id, cursor: cursor}, headers{Cookie: cookie}, ) data resp.json() for item in data.get(updatedHighlights, []): highlights.append({ highlight_id: item.get(highlightId), chapter: item.get(chapterTitle, ), mark_text: item.get(markText, ), note: item.get(note, ), create_time: item.get(createTime, 0), }) if data.get(hasMore): cursor data.get(nextCursor, cursor) else: break return highlights核心思路是“先拿到书再逐本拿书摘”这样方便做增量。书太多的时候你不需要每次全量抓所有书摘只需记录同步时间戳只请求新产生的书摘即可。我通常把最近 7 天的书摘作为增量同步窗口既保证不会漏也避免浪费接口请求。注意书摘接口返回的createTime通常是毫秒级时间戳。写入 Notion 时我需要把它转成YYYY-MM-DD格式这样 Notion 的日期属性才能正确识别。3.2 构建 Notion 数据库与写入页面在写入之前数据库设计要先想清楚。我用的数据库字段如下属性名类型用途书名Title主标题书籍名称作者Rich text作者信息内容Rich text书摘原文我的想法Rich text如果有笔记存入这里章节Select书摘所属章节划线时间Date书摘创建时间书籍链接URL微信读书的书籍链接状态Select想读 / 在读 / 读完领域Multi-select标签用于后续聚合检索同步批次Number用于调试去重其中“同步批次”这个字段可以直接看出哪些记录是哪次同步写入的排查重复时特别有用。写入使用 Notion 的 create page 接口。每个书摘单独创建一个页面页面属性按上面的字段填充页面内容用 children blocks 放入原文和相关说明。需要注意每个 create page 请求的 children blocks 上限是 100 个。书摘就是单条文本所以问题不大但如果你想把整本书的所有书摘一次写入一个页面就会碰到 100 个子块的硬限制。我的解法是一本书对应一个独立的 Notion 页面书摘作为子块分批写入每批 90 个以内循环追加。import time import requests def create_page(headers, database_id, payload): url https://api.notion.com/v1/pages resp requests.post(url, headersheaders, json{parent: {database_id: database_id}, **payload}) if resp.status_code 429: retry_after int(resp.headers.get(Retry-After, 2)) time.sleep(retry_after) resp requests.post(url, headersheaders, json{parent: {database_id: database_id}, **payload}) if resp.status_code 400: print(fcreate page failed: {resp.status_code} {resp.text}) return None return resp.json()我在实际运行时发现Notion API 对并发和频率有一定限制触发 429 后按响应头中的Retry-After等待即可。追加子块也可以用类似的方式处理先构建 blocks 列表再每次切出 90 个调用 append 接口。3.3 幂等同步避免每次同步生成重复内容幂等是这套系统能不能长期跑的关键。如果每次运行都全量写入第二次运行就会出现一整批重复页面。解决思路很简单给每条书摘算出一个稳定 ID在写入前先查询 Notion 数据库里是否已经存在这个 ID。最直接的做法是往数据库里加一个唯一ID属性类型用 Rich text值由book_id highlight_id create_time拼接生成。每次同步前用 Notion API 的 query 接口找出所有已有唯一ID放进一个 Set然后遍历新书摘时先检查 Set。如果命中就跳过没命中才写入写入成功后把这个 ID 加入 Set。def is_duplicate(existing_ids, unique_id): return unique_id in existing_ids def build_unique_id(book_id, highlight): return f{book_id}:{highlight.get(highlight_id)}:{highlight.get(create_time)}这个方案比维护本地数据库简单得多因为状态全部存在 Notion 自身所有去重逻辑都基于“目标系统里有什么”来决策。即使同步脚本崩溃重跑也不会产生新的重复数据。经验第一次跑全量同步时一定要先做小范围测试。我当时是从最近 50 条书摘开始测试的确认字段映射和写入格式都正确后再放开跑全书架。否则中途发现书名和作者字段错位清理起来就是大工程。3.4 同步时间线优化同步速度除了受到 Notion API 限制影响还会被书摘接口的逐本请求拉慢。几百本书逐本去请求书摘即使每本只需要 1 秒也要好几分钟。更别提有些书有上千条书摘需要多轮回圈。优化思路是“快照 增量”结合。书架数据每天同步一次书籍状态变化不用太频繁书摘只同步新产生的部分。具体做法是脚本启动时读取上次同步时间戳如果距离上次不到 24 小时就只请求更新时间窗口内的书摘如果超过 24 小时就做一次全量增量同步。另外可以在书架接口返回的书籍列表里过滤掉状态为“想读”或者没有阅读进度的书这些书通常没有新书摘可以跳过节省大量请求。时间窗口之外还需要解决“接口数据乱序”的问题。书摘接口返回时不一定按时间排序如果只取前 N 条很容易漏掉最新记录。我建议先完整拉取某本书的书摘再本地过滤时间不要依赖接口的排序。虽然请求次数多一点但准确性最重要。4. 部署为自动化工作流4.1 用 GitHub Actions 安排定时任务本地跑通之后我把它部署到了 GitHub Actions用定时触发实现全自动同步。核心就是一个 workflow YAML 文件配置schedule里填 cron 表达式。name: sync-weread-to-notion on: schedule: - cron: 0 8 * * * jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-pythonv4 with: python-version: 3.10 - run: pip install -r requirements.txt - run: python sync.py env: WE_READ_COOKIE: ${{ secrets.WE_READ_COOKIE }} NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }}这里有两个容易踩的坑。第一个GitHub Actions 的定时任务默认是 UTC 时区0 8 * * *代表 UTC 时间早上 8 点对应北京时间下午 4 点。你要是想在北京时间早上 8 点跑cron 要写成0 0 * * *也就是 UTC 零点。第二个secrets 里的 Cookie 如果换行或包含特殊字符直接粘贴很容易导致 JSON 解析失败。建议在本地先把 Cookie 值用引号包裹复制到 Secrets 后再加一句打印检查逻辑确认长度和格式。4.2 抓取频率与请求频控在定时任务里频率控制对长期稳定运行非常重要。我建议每天最多跑 1 到 2 次不要设置成每小时甚至更密集。高频请求不仅可能给平台带来压力还会导致你的账号被临时限制反而造成同步中断。如果你确实需要更高的实时性可以考虑在脚本里加入“本地时钟 上次同步时间”的双重限制同一小时内只允许运行一次。有些时候 GitHub Actions 会因为重试或手动触发而产生重复执行频率检查能兜底。我的脚本里有这样一段保护import os import time from pathlib import Path state_file Path(last_sync.txt) last_ts float(state_file.read_text()) if state_file.exists() else 0 if time.time() - last_ts 3600: print(skip: sync too frequent) return这个机制很简单但能有效避免因为误触或任务重跑导致的重复请求。4.3 敏感信息保护与失效处理Cookie 是这套方案里最需要关注的生命周期问题。微信读书的登录态一般能维持几天到几周过期后脚本会返回异常或空数据。如果你在脚本里把这种情况当作“无书摘”处理那么过期几天后你就会发现 Notion 里的数据静默断更。我的处理方式是在同步脚本开头先做一次验证请求如果响应中包含登录态失效的标志就立刻发送通知不要继续跑后续逻辑。通知可以用微信推送、邮件或任意 webhook。最少要做到把错误打印在 Actions 日志里结合 GitHub Actions 的失败通知功能出问题至少能收到邮件。Notion Token 相对稳定不太会失效但它能访问的页面取决于集成授权。如果你重建了集成旧的 Token 会立即失效记得同步更新 Secrets。我建议所有凭证统一放在环境变量里不要散落在代码和配置文件中。这样换机器部署时只要复制环境变量不用改一行代码。5. 数据库模板阅读知识库的高级玩法5.1 用 Notion 公式做阅读统计当数据持续流入后Notion 的数据库公式能力能让这份数据从“记录”升级成“统计”。我的数据库里有一个日期属性叫“开始阅读日期”一个状态属性叫“状态”。利用公式可以算出到目前为止的阅读天数。dateBetween(now(), prop(开始阅读日期), days)这个公式每天都会自动变化不需要手动更新。类似地我还能算出一本书的划线密度比如“划线数量”属性除以“章节数量”属性用公式一列就能显示。这里的核心价值是Notion 公式不是一次性计算而是随数据库数据动态更新你只管同步书摘数据统计结果自动生成。另一个我觉得很实用的公式是“长草警告”。如果一本书的最近划线时间距离今天超过 30 天状态还是“在读”公式就该提醒你了。if(and(prop(状态) 在读, dateBetween(now(), prop(划线时间), days) 30), 警惕长草, 正常)这种公式让阅读管理变得有“主动反馈”的意味而不再是一堆静态数据。5.2 用视图看板管理读书节奏Notion 数据库的视图切换值得用好。我建了三种视图第一种是表格视图展示全部书摘按划线时间倒序快速浏览最近读了什么、画了什么。 第二种是看板视图按书籍状态分组每本书是一个卡片卡片内显示最近一条书摘摘要。 第三种是画廊视图每张卡片用封面图打底下面挂书名和领域标签适合做读书墙。视图之间切换不需要改变底层数据所以我可以根据场景自由切换。最让我满意的是把“最近划线”单独做成一个筛选视图筛选条件是最近 7 天的书摘。这样每次打开 Notion第一眼看到的就是本周摘录过的内容很适合周回顾。与 Workflowy 这类纯大纲工具不同Notion 的数据库可以按属性组合筛选和排序。比如我只想看“产品方向 在读 最近一周有划线”的书籍三个筛选条件叠加一次就能定位到重点。这个能力是微信读书内部怎么都比不了的。5.3 性能注意Notion 数据库在数据量大了以后需要注意性能但我的实测结果是只要不把几千条书摘塞进单个数据库页面整体速度都能接受。每一本书是独立页面书摘作为子块最多每批 90 个这样单页面的内容块不会膨胀到影响加载。属性数量也要控制。每个数据库的属性数量并不是无限膨胀的公式属性数量过多会导致页面加载变慢。我见过一些人给阅读数据库塞了三十多个属性打开要转好几秒。我的建议是属性数量控制在 15 个以内公式属性尤其要克制只保留真正会在日常视图中用到的几个。如果你确实需要更多统计维度更好的方案是把“书籍数据库”和“书摘数据库”分开用关联字段绑定而不是把所有信息塞进一个表。我目前的架构就是两者分离便于各自优化。6. 常见问题与排查实录6.1 Notion API 403 与 429403 错误是接入期最常见的。它基本可以判定为“集成没连到页面”或“Token 不属于当前页面所属工作空间”。排查顺序是先在 Notion 页面右上角 Connections 里确认集成已添加再确认 Token 是 Internal Integration Token不是 OAuth 专用最后确认数据库 ID 确实属于该页面。如果这三步都做了还是 403大概率是数据库 URL 里选的页面和实际授权页面不一致。429 错误则是运行期最常见的。它的特征很明确响应头里有Retry-After。处理策略就是尊重这个时间等它结束再继续。我自己写的重试逻辑不单是定时重试还会读取响应头里的时间因为这个时间是动态的、比固定等待更准确。import time from retry import retry retry(tries3, delay2, backoff2) def call_notion_api(url, headers, payload): resp requests.post(url, headersheaders, jsonpayload) if resp.status_code 429: wait int(resp.headers.get(Retry-After, 5)) time.sleep(wait) raise Exception(rate limited) resp.raise_for_status() return resp.json()这个写法会在触发 429 时自动按服务端建议的等待时间暂停然后再试。三次尝试后仍失败就整个失败退出交给 GitHub Actions 的通知机制处理。6.2 微信读书 Cookie 过期与接口字段变动Cookie 过期后书摘接口响应往往不是空数组而是结构异常或者直接报错。不要把这个当成“没有书摘”。我的经验是每次请求后都检查响应体是否包含业务数据标志如果缺失立即终止同步并打印错误。接口字段变动是更隐蔽的问题。微信读书的接口返回结构并非完全稳定某些字段在 A 版本是markText在 B 版本可能嵌套到markText.xxx里。建议你在写解析代码之前先用浏览器开发者工具看真实返回结构再用代码解析。如果发现字段名不对不要硬猜直接在控制台检查 JSON 树。另外书摘接口的分页逻辑也变过。有些版本用cursor有些用page加pageSize。我建议用“无限循环 hasMore 标志”的方式这样无论分页字段怎么变都能自然适配。6.3 书摘重复与乱序重复问题在第一次全量同步时最容易暴露。因为你可能之前手动导入过一批再跑自动同步时就会看到重复。唯一的根治办法就是“唯一ID 先查重再写入”这个我在前面已经详细说过这里再强调一次同步脚本一定要保持幂等。乱序问题则来自接口返回顺序和真实划线时间不一致。如果你用“先取前 N 条”的方式做增量很容易漏掉最新书摘。正确做法是取完这本书的所有书摘后再在本地按时间筛选。不要尝试在接口层做假设性的限制因为那条边界很微妙不值得赌。6.4 时区与运行时间时区问题是很多定时任务都绕不开的坑。GitHub Actions 的 cron 调度默认是 UTC本地跑脚本则可能按系统时区计算。为了让同步脚本的行为保持一致我在脚本里统一使用 UTC 时间戳作为增量窗口基准只在使用datetime展示时才转换成本地时间。运行时间的设置也要考虑你的实际需求。我自己是把同步放在北京时间的早晨这样每天一醒来就能看到头一天所有新划线的汇总。如果你更看重收盘后的阅读记录那就安排到晚上。定时任务最大的意义是从“记起来才同步”变成“每天都自动更新”它是这套数据同步系统的最后一环。在实际维护这个项目半年之后我的体感是前期所有复杂度都集中在第一轮全量同步的字段对齐和去重策略上一旦跑通之后每天就是静默执行极少需要人工介入。如果你只是想把数据从微信读书一点点搬出来手动复制也能用但你如果想要一个长期自动更新的个人阅读数据库这条“接口抓取 API 写入 幂等同步 定时运行”的路径是目前我认为性价比最高的一条。另外再分享两个小技巧。第一个在 Notion 首页做一个“最近划线”的嵌入式视图这样你打开 Notion 的第一眼就能看到本周的摘录而不是非得进数据库。第二个在同步脚本里加一个失败心跳检测连续两天同步失败就往推送里发一条警告消息能避免“数据静默断更”这种最磨人的情况。方法都不复杂但长期用下来它们才是让这套系统真正“可靠”的关键。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询