Python爬虫实战:一键导出微信读书书籍与笔记

简介:这份资源是一套用 Python 编写的微信读书书籍与笔记导出工具,面向有基础 Python 使用经验、希望把个人书架和读书笔记批量保存到本地的读者,可用于个人知识库整理、笔记归档与二次分析等场景。压缩包共 8 个文件,约 277KB,以 3 个 py 脚本为核心,分别承担爬取逻辑、Excel 处理与 PyQt 图形界面入口,另含 2 个 txt 依赖与说明文件、2 张 png 界面截图和 1 个 md 说明文档,结构紧凑、便于快速上手。目前已有 2180 人学习下载。代码附带依赖安装与运行说明,读者可据此完成环境配置并启动图形界面,将微信读书中的书籍信息与笔记导出为可编辑表格,同时可参考脚本中的请求与解析思路,理解爬虫与 GUI 结合的实现方式,并自行调试适配个人账号环境。资源仅供个人学习交流,请勿用于商业用途。

1. 一键导出微信读书的书籍和笔记:为什么我最后放弃了截图存档

微信读书里的划线、想法和书评,攒到几百条之后,靠手动复制粘贴基本等于自虐。我最早的做法是每本书开一个备忘录,读到金句就切出去粘贴一次,坚持了不到两周就放弃了——切屏打断心流是小事,真正要命的是笔记散落在不同 App 里,想按书检索、想导出成 Markdown 归档,完全无从下手。后来我换了个思路:既然阅读数据都在账号里,那就用 Python 写个爬虫,把书架、划线、想法、书评一次性拉下来,落成结构化文件。这篇笔记讲的就是这条路怎么走通,包括登录态怎么拿、接口怎么找、翻页怎么处理、频率怎么控。适合有 Python 基础、想给自己做一套个人阅读归档的开发者,也适合想练手接口分析的新手。需要提前说清楚:只导出自己账号下的数据,别碰别人的,也别拿去批量分发。

2. 先搞清楚数据从哪来:微信读书的接口结构与登录态

动手写代码之前,得先弄明白一件事:你要的数据到底藏在哪个请求里。很多人一上来就打开开发者工具乱翻,翻半天找不到划线列表,最后怀疑是不是加密了。其实微信读书 Web 端的数据流并不复杂,关键是分清「页面渲染数据」和「接口返回数据」这两类,前者是 HTML 里塞的,后者是 XHR 异步拉的,你要的是后者。

2.1 书架、划线、想法分别对应哪类请求

打开 Web 端书架页,按 F12 切到 Network 面板,筛选 XHR,刷新页面,你会看到几个明显的请求。书架列表通常是一个带bookId数组的接口,返回的是你账号下所有书的元信息,包括书名、作者、封面、阅读进度。点进某本书的笔记页,又会触发另一批请求,划线(underline)和想法(thought)往往是分开返回的,有的版本会合并成一个bookmarklist之类的接口。这里有个血泪经验:不同端的接口路径不一样,Web 端和 App 端的参数结构经常对不上,你照着某篇老教程抄的 URL,很可能早就失效了。所以别背接口,学会自己抓。

判断方法很简单:在笔记页滚动加载,看哪个请求的响应里出现了你刚划的那句话。找到之后,把它的 Request URL、Method、Headers、Payload 全部复制出来,这就是你的「数据源模板」。一般来说,划线接口会带bookId、chapterUid之类的参数,想法接口还会多一个分页游标。

2.2 登录态怎么拿:Cookie 与关键 Header 的取舍

微信读书的接口基本都要登录态,核心就是 Cookie。你登录之后,浏览器里会存一串wr_skey、wr_vid之类的字段,请求时带上就能通过校验。最省事的做法是直接从浏览器复制完整 Cookie 字符串,塞进代码里跑。但这里有个坑:Cookie 是有有效期的,而且换设备、换网络环境可能触发重新验证,所以别把它硬编码进仓库,用环境变量或者本地配置文件存。

除了 Cookie,还有几个 Header 值得注意。User-Agent最好伪装成正常浏览器,别用 requests 默认的,否则容易被识别。Referer有时也校验,填成对应的页面地址即可。至于Authorization之类的字段,Web 端一般用不到,App 端才常见。我一般会先把浏览器里那条成功请求的 Headers 原样抄下来,跑通之后再逐个删减,看哪些是必需的,这样比盲猜高效得多。

import os import requests # 从环境变量读取,避免硬编码进代码仓库 COOKIE = os.environ.get("WEREAD_COOKIE", "") BASE_HEADERS = { "Cookie": COOKIE, "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ), "Referer": "https://weread.qq.com/", "Accept": "application/json, text/plain, */*", } def fetch_json(url, params=None): """统一封装 GET 请求,方便后续替换成 POST""" resp = requests.get(url, headers=BASE_HEADERS, params=params, timeout=10) resp.raise_for_status() return resp.json()

这段代码做了三件事:把 Cookie 从环境变量读进来,避免泄露;统一了请求头,减少被拦截的概率;封装了一个fetch_json,后面所有接口调用都走它。参数说明上,timeout=10是防止某个请求卡死拖垮整个脚本,raise_for_status让你在状态码异常时立刻发现,而不是拿到一堆空数据还以为是解析问题。实际跑的时候,如果返回 401 或 403,八成是 Cookie 过期了,重新复制一份即可。

3. 把书架和笔记拉下来:分页、解析与落盘

拿到接口模板之后,下一步就是把它变成能批量跑的脚本。这里分三块:先拉书架拿到所有书的 ID,再针对每本书拉划线和想法,最后把数据落成 Markdown 或 JSON。听起来线性,但真正写起来,分页和字段解析是最容易翻车的地方。

3.1 书架列表的翻页逻辑与字段提取

书架接口一般不会一次性返回所有书,尤其是书多的时候,会有分页或者滚动加载。常见做法是带一个offset或lastBookId参数,每次返回一批,直到返回空列表为止。我一般会写一个循环,把每页的结果累加,同时加一个最大页数保护,防止接口异常时死循环。

def fetch_bookshelf(): """循环拉取书架,直到没有更多数据""" books = [] offset = 0 while True: data = fetch_json( "https://weread.qq.com/api/user/bookshelf", params={"offset": offset, "limit": 20}, ) batch = data.get("books", []) if not batch: break for item in batch: books.append({ "bookId": item.get("bookId"), "title": item.get("title"), "author": item.get("author"), "progress": item.get("readingProgress", 0), }) offset += len(batch) if offset > 2000: # 保护阈值,按自己书量调整 break return books

逻辑上,每次请求拿 20 本,拿到空列表就停。offset累加的是实际返回数量,而不是固定步长,这样即使接口返回数量有波动也不会漏。字段提取时只保留你需要的,别把整个响应塞进内存,书多了会很占资源。progress字段有的版本叫readingProgress,有的叫finishReading,以你实际抓到的为准,别照抄。

3.2 划线、想法、书评的解析与 Markdown 落盘

拿到 bookId 之后,针对每本书调笔记接口。划线通常返回一个数组,每条包含章节名、划线原文、位置信息;想法则多一个content字段和创建时间。解析的时候要注意,有些字段是嵌套的,比如章节信息可能在chapter对象里,别直接取顶层。落盘我习惯用 Markdown,一本书一个文件,划线用引用块,想法用普通段落加时间戳,这样在 Obsidian 或 Typora 里直接能看。

def export_book_notes(book): """导出单本书的划线和想法为 Markdown""" data = fetch_json( "https://weread.qq.com/api/book/bookmarklist", params={"bookId": book["bookId"]}, ) lines = [f"# {book['title']}\n", f"作者:{book['author']}\n"] for bm in data.get("updated", []): chapter = bm.get("chapterName", "未知章节") text = bm.get("markText", "").strip() if not text: continue lines.append(f"## {chapter}\n") lines.append(f"> {text}\n") # 想法可能挂在同一条记录下 for thought in bm.get("thoughts", []): lines.append(f"- {thought.get('content', '')}\n") filename = f"notes/{book['bookId']}.md" with open(filename, "w", encoding="utf-8") as f: f.write("\n".join(lines)) return filename

这里的关键是字段容错:markText可能为空,thoughts可能不存在,所以用get加默认值,避免 KeyError 直接崩掉。章节名有的接口返回chapterName,有的返回chapterTitle,跑之前先打印一条完整响应确认。落盘路径建议单独建目录,文件名用 bookId 而不是书名,避免书名里有特殊字符导致写入失败。

3.3 用并发把几百本书的导出时间压下来

串行跑几百本书,每本一次请求,加上网络延迟,可能要十几分钟。这时候可以用concurrent.futures开几个线程并发拉取。但注意别开太多,微信读书的接口对频率敏感,线程数控制在 3 到 5 比较稳妥,再高容易触发限流。

from concurrent.futures import ThreadPoolExecutor, as_completed def export_all(books): """并发导出,控制线程数避免触发限流""" results = [] with ThreadPoolExecutor(max_workers=3) as pool: futures = {pool.submit(export_book_notes, b): b for b in books} for future in as_completed(futures): book = futures[future] try: path = future.result() results.append(path) print(f"完成:{book['title']} -> {path}") except Exception as e: print(f"失败:{book['title']},原因:{e}") return results

max_workers=3是经验值,你可以从 2 开始试,观察有没有返回 429 或空数据。每个任务独立捕获异常,避免一本书失败导致整批中断。打印进度是为了长任务时心里有数,不然跑半天不知道卡在哪。

4. 避坑与排查:导出微信读书笔记时最容易翻车的五件事

这一章是我踩过的坑合集,每一条都按「现象 → 原因 → 解决」写,你遇到问题时可以直接对号入座。

现象一:请求返回 200,但数据是空的。原因通常是 Cookie 过期或者缺少关键 Header,接口没报错但返回了空结构。解决方法是先打印完整响应,确认是不是{"updated": []}这种;如果是,重新复制 Cookie,并检查Referer是否填对。

现象二:翻页翻到一半开始重复。原因是offset用了固定步长,而接口实际返回数量小于步长,导致偏移错位。解决方法是每次用实际返回的len(batch)累加,而不是写死 20。

现象三:划线内容里出现 HTML 标签或转义字符。原因是接口返回的是富文本片段,直接写进 Markdown 会显示异常。解决方法是做一次简单清洗,把<br>换成换行,把&nbsp;换成空格,或者用html.unescape处理。

现象四:跑几十本之后开始大量失败。原因是并发过高或请求间隔太短,触发了频率限制。解决方法是把线程数降到 2 到 3,并在每次请求之间加time.sleep(0.5)到1秒的随机延迟。

现象五:书名里有斜杠或冒号,写文件时报错。原因是文件名非法字符。解决方法是文件名统一用 bookId,书名只写在文件内容里,或者用正则把非法字符替换掉。

提示:调试阶段建议只跑一本书,把完整响应打印出来看结构,确认字段名之后再批量跑。直接上全量,出了问题很难定位。

5. 进阶:把导出结果接进自己的知识库

跑通基础版之后,你手里就有了一堆 Markdown 文件。但光导出还不够,真正有价值的是让这些笔记能被检索、被关联。我现在的做法是:导出时在文件头部加 YAML front matter,写上书名、作者、导出时间、划线数量,这样 Obsidian 的 Dataview 插件就能直接按作者或时间筛选。另外,我会把同一本书的划线和想法按章节聚合,而不是按接口返回顺序平铺,读起来更接近原书结构。

还有一个技巧是增量导出。每次全量跑太浪费,可以在本地存一个last_export.json,记录每本书上次导出的时间戳,下次只拉更新时间晚于这个点的书。接口里一般有updateTime字段,拿来比较即可。这样日常维护只需要几秒钟。

import json import os from datetime import datetime STATE_FILE = "last_export.json" def load_state(): if os.path.exists(STATE_FILE): with open(STATE_FILE, encoding="utf-8") as f: return json.load(f) return {} def save_state(state): with open(STATE_FILE, "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) def need_update(book, state): """对比本地记录与接口返回的更新时间""" last = state.get(book["bookId"], {}).get("updateTime", 0) return book.get("updateTime", 0) > last

这段代码的核心是状态文件,记录每本书最后一次导出的时间。need_update判断是否需要重新拉取,避免无谓请求。注意updateTime的格式可能是秒级时间戳,也可能是毫秒,跑之前打印一条确认,别直接比较。

最后说个我自己的习惯:导出脚本我从来不放在项目根目录裸跑,而是包一层main.py,把 Cookie 读取、目录创建、异常汇总都收进去,跑完打印一份「成功多少本、失败多少本、跳过多少本」的简报。这样每次执行完一眼就知道有没有问题,不用去翻日志。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

AI视觉驱动自动化测试:从元素定位到意图驱动,Appium会被取代吗?

AI视觉驱动自动化测试:从元素定位到意图驱动,Appium会被取代吗?

移动端自动化测试圈今天又被一张开源项目截图炸开了锅&#xff0c;消息核心就一句话&#xff1a;谷歌放出了一个主打AI视觉与自然语言驱动的开源自动化框架&#xff0c;不少人的第一反应就是“Appium要没了”。我个人的看法没那么极端&#xff0c;但这个东西确实值得所有写过元…

2026/10/20 19:13:16 阅读更多 →
ADMM算法的MATLAB实现:从原理到LASSO与分布式优化

ADMM算法的MATLAB实现:从原理到LASSO与分布式优化

简介&#xff1a;一套基于MATLAB实现的ADMM&#xff08;交替方向乘子法&#xff09;算法代码包&#xff0c;主要面向机器学习、图像处理、信号处理等领域的算法研究者和工程师&#xff0c;用于求解含等式或不等式约束的凸优化问题。压缩包共383个文件&#xff0c;内容约17.44MB…

2026/10/20 19:13:16 阅读更多 →
从零理解感知机:原理、损失函数与随机梯度下降实战

从零理解感知机:原理、损失函数与随机梯度下降实战

感知机这东西&#xff0c;算是我接触机器学习时第一个让我觉得“原来机器真能自己学东西”的模型。现在回头再看&#xff0c;它确实简单得有点过分——一个线性函数套个符号函数&#xff0c;加一个极其朴素的更新规则&#xff0c;没了。但就是这么一个看似简陋的算法&#xff0…

2026/10/20 19:12:14 阅读更多 →

最新新闻

SST-2评测任务配置逐项拆解:从64条开发样本到可复现评测

SST-2评测任务配置逐项拆解:从64条开发样本到可复现评测

1. 评测任务配置的底层逻辑&#xff1a;为什么一个配置文件值得逐项拆解很多人第一次接触评测框架时&#xff0c;注意力都放在模型本身——参数量、推理速度、准确率曲线。但真正跑过几轮完整评测的人会慢慢意识到&#xff0c;决定评测结果是否可信的&#xff0c;往往不是模型&…

2026/10/20 23:26:59 阅读更多 →
纵向加密装置V1.5资源包实战指南:策略配置、隧道验证与等保合规

纵向加密装置V1.5资源包实战指南:策略配置、隧道验证与等保合规

简介&#xff1a;本资源是科东PSTunnel2000纵向加密装置配套的官方客户端软件V1.5正式版&#xff0c;面向电力系统自动化、网络安全运维及工控安全领域技术人员&#xff0c;专为解决跨区域调度数据在公网或广域网中传输时的机密性、完整性与身份可信问题。压缩包为RAR格式&…

2026/10/20 23:26:59 阅读更多 →
从雷达观测数据到卫星轨道根数:坐标变换、速度拟合与轨道预报全流程解析

从雷达观测数据到卫星轨道根数:坐标变换、速度拟合与轨道预报全流程解析

做雷达数据处理的人大概都经历过这样的时刻&#xff1a;屏幕上明明有一个亮点&#xff0c;测距、测角、多普勒全都报得清清楚楚&#xff0c;但你要说它下一秒飞去哪儿、下一个圈次什么时候再回来&#xff0c;心里却完全没底。把一串雷达观测数据变成一条能够预报的轨道&#xf…

2026/10/20 23:26:59 阅读更多 →
分布式系统容错设计实战:从超时到熔断,全面构建高可用防线

分布式系统容错设计实战:从超时到熔断,全面构建高可用防线

先讲一个我参与过的真实故障。某电商平台大促当晚&#xff0c;核心交易链路依赖的支付渠道接口突然开始变慢&#xff0c;单次调用从平均 60 毫秒涨到 3 秒。由于调用方没有设置合理的超时机制&#xff0c;请求在 Tomcat 线程池里越堆越多&#xff0c;最终整条下单链路都卡在等待…

2026/10/20 23:26:59 阅读更多 →
直插式存储扩展模块:嵌入式设备扩容与数据迁移的实用方案

直插式存储扩展模块:嵌入式设备扩容与数据迁移的实用方案

存储扩容这件事&#xff0c;我这两年聊得最多。不管是给开发板加数据盘&#xff0c;还是给老设备续命&#xff0c;最终的瓶颈往往不是CPU&#xff0c;也不是内存&#xff0c;而是那点不够用的内置存储。AirMICROSD_1000直插式存储扩展模块&#xff0c;准确说是一类方案的代号&a…

2026/10/20 23:26:59 阅读更多 →
T+转U8+数据迁移实战:科目、辅助核算与期初余额的平滑过渡方案

T+转U8+数据迁移实战:科目、辅助核算与期初余额的平滑过渡方案

简介&#xff1a;专门用于T与U8之间数据迁移的辅助工具&#xff0c;面向财务人员、ERP实施顾问及企业信息化运维者&#xff0c;解决T版本数据平滑转换至U8过程中的字段映射与账套整合难题。整个工具包共13个文件&#xff0c;压缩后仅426KB&#xff0c;包含8个SQL脚本&#xff0…

2026/10/20 23:25:58 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →