
简介这是一套面向Python初学者与爬虫爱好者的微信读书数据导出工具解决个人学习场景下书籍列表与阅读笔记难以批量保存的问题。资源包共8个文件含3个核心Python脚本GUI界面、主爬虫逻辑、Excel处理、2个文本配置文件、2张界面演示图及1份Markdown说明文档整体仅277KB轻量易部署。已有2168人下载学习适合希望快速上手网页数据抓取、理解登录模拟与API逆向分析的实践者。读者可直接运行pyqt_gui.py启动图形化界面通过内置流程完成微信读书账号登录、书架拉取、笔记提取及Excel一键导出配套requirement.txt确保依赖环境可复现README详述调试要点与法律声明强调仅限学习交流使用。1. 项目缘起为什么我们需要一个自己的“书库”作为一个重度阅读爱好者我几乎把所有的碎片时间都泡在了微信读书上。它确实方便书多社区氛围也好划线、写想法、看别人的批注体验很流畅。但时间一长问题就来了我辛辛苦苦划下的几百条笔记、写下的几十条想法全都“寄存”在微信读书的服务器里。哪天我想离线整理、想用其他笔记软件做深度关联、或者单纯就是想备份一份属于自己的数据时就发现非常被动。平台提供的导出功能往往有限要么格式单一要么一次只能导出一本书对于我这种笔记大户来说效率太低。更现实的一个场景是很多书是有版权期限的或者你读的是“无限卡”免费读的书一旦下架或者你的会员到期这些笔记就可能再也看不到了。那种感觉就像自己盖的房子钥匙却在别人手里。所以自己动手把数据拿回来建立一个本地备份甚至是一个可以随时检索、编辑的个人知识库就成了一个很实际的需求。这就是我动手写这个爬虫脚本最直接的动机拿回数据的自主权。市面上有一些现成的工具或者浏览器插件但要么已经失效微信读书前端改个版可能就用不了了要么功能不符合我的需求比如我只想导出笔记它却把整本书都下载了。所以最好的办法就是自己写。用Python爬虫来实现灵活、可控而且整个过程本身就是一次非常好的学习实践你能彻底搞清楚微信读书的数据接口、认证机制以及如何优雅地处理反爬策略。今天我就把这个经过多次迭代、相对稳定的一键导出脚本的实现思路、核心代码以及我踩过的所有坑毫无保留地分享出来。2. 核心思路拆解我们到底在“爬”什么在动手写代码之前我们必须先想明白我们要从微信读书获取什么以及它可能以什么形式提供。盲目地去抓取网页HTML是效率最低、也最容易被反爬虫机制拦截的方式。正确的方法是模拟真实用户操作直接请求其背后为手机App或网页端提供数据的JSON API接口。通过浏览器开发者工具F12的“网络”Network选项卡我们在微信读书网页版进行操作比如翻到某本书的笔记页面可以清晰地看到一系列XHRFetch请求。我们的目标就在这些请求里。经过分析整个导出流程可以分解为以下几个核心环节身份认证如何让服务器认为我们是合法的用户这通常依赖于Cookie。我们需要先登录微信读书网页版从浏览器中提取出关键的Cookie信息。获取书籍列表我们得知道自己的书架上有哪些书。有一个接口可以返回用户所有的图书列表包含每本书的唯一IDbookId、书名、作者等信息。获取单本书籍的笔记这是最核心的一步。根据bookId请求获取这本书下所有用户自己的划线笔记和想法评论。这个接口会返回一个结构化的JSON数据包含了笔记所在的章节、文本内容、创建时间、位置等信息。数据解析与存储将获取到的JSON数据解析成我们想要的格式比如纯文本、Markdown或者CSV然后保存到本地文件。整个脚本的骨架就是围绕这四个步骤搭建的。下面我们就一步步来看具体怎么实现以及每个环节有哪些需要特别注意的“坑”。3. 环境准备与核心工具选型工欲善其事必先利其器。这个项目对Python环境的要求并不高核心是几个非常常用的库。3.1 Python与库安装建议使用Python 3.7及以上版本。我们需要安装的库主要有三个requests用于发送HTTP请求这是爬虫的基石。它比Python自带的urllib更简洁易用。jsonPython标准库用于解析和生成JSON数据。time用于在请求间添加延时避免请求过快被服务器限制。安装命令非常简单pip install requests其他两个是标准库无需安装。这里为什么不选用更“重型”的框架如Scrapy因为我们的目标很明确就是针对微信读书这一个特定站点的几个特定API。Scrapy更适合构建大规模、复杂的爬虫项目用它来抓几个API接口有点杀鸡用牛刀还会引入不必要的复杂度。requests库轻量、直接完全够用。3.2 获取身份凭证Cookie这是整个流程的钥匙也是最容易出错的一步。微信读书目前主要依靠Cookie来维持登录状态。操作步骤用Chrome、Edge或Firefox浏览器打开 微信读书网页版 。使用微信扫码登录。登录成功后按F12打开开发者工具切换到Network网络选项卡。刷新页面在网络请求列表中找到任何一个指向weread.qq.com域名的请求比如第一个document请求或任何一个api请求。点击该请求在右侧的Headers标头标签页中向下找到Request Headers请求头部分。在其中找到Cookie这一行后面那一长串字符串就是我们需要的东西。把它完整地复制下来。重要提示Cookie是个人隐私包含了你的登录会话信息。千万不要将你的真实Cookie分享给他人或上传到公开的代码仓库如GitHub。我们后续在代码中会将其保存在本地的配置文件或环境变量中。Cookie的格式它看起来像wr_vidxxx; wr_skeyyyy; wr_ridzzz; ...这样。我们需要的是整个字符串。4. 核心代码实现与逐行解析接下来我们进入核心的代码部分。我会把完整的脚本拆解开逐一解释每个函数的作用和关键代码行。4.1 脚本框架与配置首先我们引入必要的库并定义一些配置。我强烈建议将Cookie等敏感信息放在代码之外。import requests import json import time import os from typing import List, Dict, Optional # 配置区域 # 方式1直接写死在代码里不推荐仅用于测试 # COOKIE 你的完整Cookie字符串 # 方式2从环境变量读取推荐 COOKIE os.getenv(WEREAD_COOKIE) if not COOKIE: # 方式3从本地文件读取次推荐 try: with open(weread_cookie.txt, r, encodingutf-8) as f: COOKIE f.read().strip() except FileNotFoundError: print(错误未找到Cookie。请设置环境变量 WEREAD_COOKIE 或创建 weread_cookie.txt 文件。) exit(1) # 请求头模拟浏览器 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, Cookie: COOKIE # 将Cookie放在请求头中 } # 基础URL BASE_URL https://i.weread.qq.com代码解读我们提供了三种设置Cookie的方式优先级是环境变量 本地文件 代码硬编码。这是工程上的好习惯保证安全性和灵活性。HEADERS字典模拟了一个普通Chrome浏览器的请求头其中最关键的一行就是‘Cookie’: COOKIE。服务器就是通过这个字段来识别我们的身份。BASE_URL是微信读书主要API接口的域名。4.2 获取所有书籍列表我们需要先知道要导出哪些书。def get_book_list() - List[Dict]: 获取用户书架上的所有书籍列表 url f{BASE_URL}/user/books # 添加一个时间戳参数防止缓存 params {vendor: web, synckey: 0, lectureSynckey: 0, _: int(time.time() * 1000)} try: response requests.get(url, headersHEADERS, paramsparams, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 data response.json() # 调试可以打印一下data的结构方便理解 # print(json.dumps(data, indent2, ensure_asciiFalse)) if data and books in data: books data[books] print(f成功获取到 {len(books)} 本书籍。) # 返回一个简单的书籍信息列表 book_list [] for book in books: book_info { bookId: book.get(bookId), title: book.get(title), author: book.get(author), cover: book.get(cover) } book_list.append(book_info) return book_list else: print(获取书籍列表失败响应数据格式异常。) return [] except requests.exceptions.RequestException as e: print(f网络请求失败: {e}) return [] except json.JSONDecodeError as e: print(f解析JSON响应失败: {e}) return []代码解读接口地址是/user/books。params里的参数是我通过观察网络请求总结的其中_参数是一个当前毫秒级时间戳常用于避免浏览器缓存。response.raise_for_status()是一个好习惯它能帮我们快速发现401未授权、404未找到等HTTP错误。response.json()直接将响应内容解析为Python字典或列表。返回的data[‘books’]是一个列表里面包含了每本书的详细信息。我们只提取出最关键的bookId、title、author等字段构造一个新的简洁列表返回。完整的异常处理 (try…except) 是爬虫脚本健壮性的关键。网络可能不稳定接口可能变化良好的错误处理能让脚本在遇到问题时给出明确提示而不是直接崩溃。4.3 获取单本书的笔记详情这是最核心的函数负责获取一本书里所有的划线和想法。def get_book_notes(book_id: str) - Optional[Dict]: 根据bookId获取指定书籍的全部笔记划线和想法 url f{BASE_URL}/book/bookmarklist params { bookId: book_id, chapterId: 0, # 0 表示获取全部章节的笔记 synckey: 0, _: int(time.time() * 1000) } try: response requests.get(url, headersHEADERS, paramsparams, timeout15) response.raise_for_status() data response.json() # 检查是否包含有效数据 if not data or updated not in data: print(f书籍 {book_id} 可能没有笔记或接口返回空。) return None # 笔记数据通常在 updated 字段下是一个列表 notes data.get(updated, []) print(f书籍 {book_id} 找到 {len(notes)} 条笔记。) return {bookId: book_id, notes: notes} except requests.exceptions.RequestException as e: print(f获取书籍 {book_id} 笔记失败网络错误: {e}) return None except json.JSONDecodeError as e: print(f获取书籍 {book_id} 笔记失败JSON解析错误: {e}) return None代码解读接口地址是/book/bookmarklist。关键参数是bookId和chapterId0代表全部章节。返回的data[‘updated’]是一个列表每一条笔记是一个字典。这个字典结构非常丰富通常包含markText: 划线的文本内容。content: 如果是想法这里就是想法内容。chapterTitle: 笔记所在的章节标题。range: 笔记在章节中的位置范围。createTime: 创建时间戳。style: 划线样式0-普通1-重点。函数返回一个字典包含bookId和notes列表方便后续处理。4.4 数据解析与导出为Markdown获取到原始数据后我们需要将其转换成易读、易用的格式。Markdown是一个非常好的选择因为它结构清晰兼容性强可以直接导入到Obsidian、Notion、Typora等大多数笔记软件中。def parse_notes_to_markdown(notes_data: Dict) - str: 将笔记数据解析为Markdown格式的字符串 if not notes_data or notes not in notes_data: return book_id notes_data.get(bookId, 未知) notes notes_data[notes] if not notes: return f# 书籍ID: {book_id}\n\n 本书暂无笔记。\n # 我们可以从第一条笔记里获取书名如果接口返回了的话 # 但更可靠的是在调用此函数时传入书名这里我们先假设没有 md_content f# 微信读书笔记导出\n\n**书籍ID:** {book_id}\n**笔记总数:** {len(notes)}\n**导出时间:** {time.strftime(%Y-%m-%d %H:%M:%S)}\n\n---\n\n # 按章节对笔记进行分组这样结构更清晰 notes_by_chapter {} for note in notes: chapter_title note.get(chapterTitle, 未分类章节) if chapter_title not in notes_by_chapter: notes_by_chapter[chapter_title] [] notes_by_chapter[chapter_title].append(note) # 按章节生成Markdown for chapter_title, chapter_notes in notes_by_chapter.items(): md_content f## {chapter_title}\n\n # 对该章节的笔记按位置range排序使其保持阅读顺序 chapter_notes.sort(keylambda x: x.get(range, )) for note in chapter_notes: mark_text note.get(markText, ).strip() content note.get(content, ).strip() abstract note.get(abstract, ).strip() style note.get(style, 0) # 处理划线 if mark_text: # 根据划线样式添加不同格式 if style 1: md_content f **{mark_text}**\n\n else: md_content f {mark_text}\n\n # 处理想法评论 if content: md_content f**我的想法:** {content}\n\n elif abstract: # 有时想法会在abstract字段 md_content f**我的想法:** {abstract}\n\n # 可以添加一个小的分隔符让每条笔记更清晰 md_content ---\n\n return md_content代码解读这个函数做了几件重要的事分组按章节、排序按书中位置、格式化。按章节分组能让导出的笔记结构化和原书保持一致复习时更有上下文。按range字段排序保证了笔记在章节内的顺序就是你在书中阅读的顺序。对于style1的笔记重点划线我们将其加粗在视觉上予以区分。每条笔记之间用---分隔提高可读性。4.5 主流程一键导出的逻辑现在我们把所有功能串联起来形成“一键导出”的主函数。def export_all_notes(output_dirweread_notes): 主函数导出所有书籍的笔记到指定目录 # 1. 创建输出目录 if not os.path.exists(output_dir): os.makedirs(output_dir) print(f创建输出目录: {output_dir}) # 2. 获取书籍列表 print(正在获取书籍列表...) books get_book_list() if not books: print(未获取到任何书籍请检查Cookie是否有效。) return # 3. 遍历每本书获取并导出笔记 total_books len(books) for idx, book in enumerate(books, 1): book_id book[bookId] book_title book.get(title, f未知标题_{book_id})[:50] # 书名可能很长截取一下 print(f\n[{idx}/{total_books}] 正在处理: 《{book_title}》 ({book_id})) # 3.1 获取笔记数据 notes_data get_book_notes(book_id) if not notes_data: print(f - 跳过未获取到笔记数据。) continue # 3.2 解析为Markdown md_text parse_notes_to_markdown(notes_data) # 3.3 保存文件。文件名使用书名和ID避免特殊字符问题 safe_title .join([c for c in book_title if c.isalnum() or c in ( , _, -)]).rstrip() filename f{safe_title}_{book_id}.md filepath os.path.join(output_dir, filename) try: with open(filepath, w, encodingutf-8) as f: f.write(md_text) print(f - 已导出至: {filepath}) except IOError as e: print(f - 文件保存失败: {e}) # 3.4 礼貌性延时避免请求过快 time.sleep(1) print(f\n导出完成所有笔记已保存至 {output_dir} 目录。) if __name__ __main__: export_all_notes()代码解读主函数export_all_notes定义了完整的流水线创建目录 - 获取书单 - 遍历每本书 - 获取笔记 - 解析保存 - 延时。在文件名处理上我们过滤掉了非字母数字的字符防止因书名包含\/:*?|等非法字符导致无法创建文件。time.sleep(1)是至关重要的“礼貌性延时”。虽然微信读书的API没有非常严格的反爬但连续、高频地请求服务器是不好的行为可能导致你的IP或账号被临时限制。加上1秒间隔既能保证效率也显得更“像人”一些。使用if __name__ ‘__main__’:是标准做法使得这个脚本既可以作为模块被导入也可以直接运行。5. 实战中的坑与进阶优化把上面的代码跑起来你基本就能导出笔记了。但根据我的实战经验还有以下几个关键点和潜在问题需要特别注意。5.1 Cookie失效与更新Cookie不是永久有效的。微信读书的登录会话有一定有效期可能几天也可能几周。当你的脚本突然报错401 Unauthorized或返回的数据为空时第一反应就应该是Cookie失效了。解决方案重新登录微信读书网页版按第3.2节的步骤重新获取一次Cookie并更新你的weread_cookie.txt文件或环境变量。5.2 接口变更与适配这是爬虫项目永恒的挑战。微信读书的API接口并非公开协议它可能随时变更路径、参数或返回的数据结构。如果你的某天脚本突然不能用了除了检查Cookie就要用开发者工具重新抓包看看get_book_list和get_book_notes对应的接口地址和参数是否发生了变化。如何应对养成观察网络请求的习惯。脚本的核心其实就是对这两个接口的封装。一旦失效就重新抓取正确的接口信息然后更新代码中的url和params。5.3 笔记数据不完整或格式异常有时你会发现导出的笔记比App里看到的少或者想法内容不见了。这可能是因为分页如果一本书的笔记非常多接口可能采用了分页机制。我们上面的代码只请求了第一页。需要检查接口返回是否有hasMore、next之类的字段并实现翻页逻辑。字段名变化笔记的正文可能在markText也可能在content或abstract。想法内容也可能在不同字段。我们的parse_notes_to_markdown函数已经做了一些兼容处理但可能需要根据实际情况调整。5.4 增加导出格式选项Markdown虽好但有人可能更喜欢CSV方便用Excel分析或纯文本。我们可以很容易地扩展脚本。def export_notes(notes_data: Dict, formatmarkdown, output_dir.): 根据格式导出笔记 if format markdown: content parse_notes_to_markdown(notes_data) ext .md elif format csv: content parse_notes_to_csv(notes_data) # 需要实现这个函数 ext .csv elif format txt: content parse_notes_to_txt(notes_data) # 需要实现这个函数 ext .txt else: raise ValueError(f不支持的格式: {format}) # ... 保存文件逻辑5.5 增量导出与定时任务我们不需要每次都导出全部书籍。可以记录上次导出的时间或者每本书最后笔记的更新时间 (createTime)然后只获取新的笔记。这需要将已导出的书籍ID和最后同步时间持久化存储比如在一个JSON文件里。然后在get_book_list或get_book_notes时带上时间参数只请求特定时间之后的更新。这能大大减少请求量实现真正的“同步”。更进一步你可以结合系统的定时任务如Linux的cronWindows的任务计划程序让这个脚本每天或每周自动运行一次实现笔记的自动备份。6. 安全、合规与伦理边界在享受技术带来的便利时我们必须清醒地认识到边界在哪里。尊重版权与用户协议我们导出的笔记是自己创作的划线和个人想法。这个脚本的用途应严格限定于个人数据备份与管理。绝对禁止用于批量下载书籍全文、盗版传播或任何侵犯版权的行为。微信读书的用户协议通常禁止自动化程序访问我们的行为处于灰色地带因此更要克制仅满足个人合理使用需求。控制请求频率务必在代码中设置合理的延时 (time.sleep)。不要试图并发大量请求去“轰炸”服务器这既不道德也极易导致你的IP或账号被封禁。我们的目的是拿回数据不是攻击服务。保护个人隐私你的Cookie就是你的账号凭证。妥善保管weread_cookie.txt文件不要将其上传至GitHub等公开平台。如果误上传了应立即在平台上删除并视为密码泄露最好在微信读书上检查一下登录设备必要时可以“下线”所有设备让旧Cookie失效。数据用途导出的数据建议仅用于个人学习、研究和知识管理。不要将其用于商业用途或公开大量分发。这个脚本是一个工具它放大了你管理个人数据的能力但如何使用它取决于你的责任心。保持善意和克制技术才能更好地为我们服务。最后完整的脚本代码已经贯穿在上述讲解中。你可以将它们组合到一个.py文件里准备好你的Cookie运行python weread_notes_exporter.py就能在weread_notes文件夹下看到一本本以你命名的Markdown笔记文件了。这个过程不仅是获取了笔记更是你对自己数字资产的一次郑重声明。本文还有配套的精品资源点击获取