
简介这是一套面向Python初学者与爬虫爱好者的微信读书数据导出工具解决个人学习场景下难以批量获取已读书籍信息及阅读笔记的痛点。资源包含8个文件总计277KB以3个核心Python脚本GUI界面、主爬虫逻辑、Excel处理、2个文本说明文件依赖清单与使用指引、2张界面演示图及1份Markdown文档构成结构简洁、模块职责明确便于理解爬虫流程与GUI交互设计。已有2168人下载学习适合希望动手实践网页登录模拟、动态内容抓取、笔记结构化导出及PyQt桌面应用集成的学习者。读者可直接运行pyqt_gui.py启动图形界面通过可视化操作完成微信读书账号登录、书架扫描、笔记提取与Excel一键导出配套requirement.txt确保环境快速复现README与注释代码提供清晰的调试入口与排错提示。1. 项目概述与核心价值最近在技术社区和朋友圈里经常看到有朋友在问“微信读书上做了那么多笔记和划线怎么才能完整地导出来” 或者“换设备了之前的读书数据能备份吗”。作为一个重度阅读爱好者和技术从业者我完全理解这种需求。微信读书的云端同步和阅读体验确实不错但它官方并未提供一个便捷的、批量的导出功能尤其是对于个人积累的笔记和想法。手动复制粘贴如果只有几本书还好对于积累了上百本书、数千条笔记的用户来说这无异于一场噩梦。于是“一键导出微信读书的书籍和笔记”这个需求就变得非常具体和强烈。本质上这是一个典型的数据备份与迁移场景核心目标是将存储在微信读书服务器上的、属于用户个人的阅读数据包括书籍元信息、阅读进度、笔记、划线、想法通过技术手段以结构化的格式如Markdown、Excel、HTML保存到本地。这不仅能满足个人的数据主权意识也为后续的笔记整理、知识复盘、跨平台迁移比如导入到Notion、Obsidian等笔记软件提供了可能。实现这个目标最直接的技术手段就是Python爬虫。爬虫在这里扮演了一个“自动化数据搬运工”的角色它模拟用户登录访问个人书架、笔记列表等页面解析返回的数据并最终整理输出。这听起来似乎是个简单的爬虫练习但实际操作中你会遇到登录态维持、反爬机制、数据结构解析、异步处理等一系列挑战。接下来我将结合我多次折腾的经验为你详细拆解如何从零构建一个稳定、可用的微信读书导出工具并分享那些官方文档里绝不会写的“坑”和技巧。2. 核心思路与技术选型解析在动手写代码之前我们必须先理清思路我们要爬什么从哪里爬怎么爬这直接决定了后续的技术方案和代码结构。2.1 目标数据源分析接口还是网页微信读书的数据获取主要有两个入口移动端App和Web网页版。我们的爬虫需要与其中一个进行交互。移动端App接口这是最“正宗”的数据源。通过抓包工具如Charles, Fiddler, Mitmproxy分析App的网络请求可以发现其与后端服务器通信的API接口。这些接口通常是返回结构化的JSON数据非常便于解析。优势在于数据直接、完整、格式规范。劣势在于需要处理登录通常涉及微信或QQ授权token获取复杂且接口可能有签名、加密等反爬措施稳定性依赖微信读书App的更新。Web网页版在浏览器中访问weread.qq.com登录后也能看到书架和笔记。数据是通过网页加载的可能是服务端渲染也可能是前端异步请求接口。优势在于环境简单用浏览器开发者工具就能轻松分析请求Cookie管理相对直观。劣势在于网页版功能可能比App少例如某些想法或笔记的显示方式不同且数据可能嵌套在HTML中需要额外的解析工作。我的选择与理由对于个人使用的、追求稳定和易维护的爬虫我更推荐从Web网页版入手。原因有三第一分析门槛低任何会按F12的开发者都能开始第二登录流程相对标准扫码或密码登录Cookie易于获取和维持第三虽然需要解析HTML但结合其异步加载的接口往往能直接拿到JSON数据事半功倍。本项目的核心思路也将基于Web端展开。2.2 技术栈选型为什么是这些库确定了从Web端抓取我们的Python技术栈就可以明确了HTTP请求库requests行业标准简单易用足以应对大部分HTTP请求。对于需要处理Cookie、Session的场景非常友好。HTML/XML解析库lxml或parsel我们需要从网页中提取数据比如书籍ID、笔记列表的初始数据。lxml解析速度快parselScrapy使用的选择器库语法强大。两者任选其一即可。异步处理可选但推荐aiohttpasyncio如果你的书架书籍很多比如超过50本逐本串行抓取笔记会非常慢。使用异步IO可以同时发起多个网络请求极大提升导出效率。这是一个从“能用”到“好用”的关键升级。数据持久化json,csv,sqlite3用于将爬取的数据保存下来。json适合保存原始API响应csv或pandas适合生成表格sqlite3适合复杂的关系型存储。最终输出格式化jinja2为了生成漂亮的Markdown或HTML报告使用模板引擎jinja2可以很好地分离数据和样式让代码更清晰。注意不要一上来就追求异步或复杂架构。建议先用requestslxml实现核心的单本书籍笔记导出功能验证流程跑通然后再考虑加入异步、数据库等优化。这是避免前期陷入技术细节泥潭的关键。2.3 整体架构设计一个健壮的导出工具应该包含以下模块形成清晰的工作流认证模块负责登录微信读书网页版获取并维护有效的登录状态Cookie。这是所有后续请求的基础。数据抓取模块这是核心。它进一步细分为书架列表抓取器获取用户的所有书籍列表包含每本书的唯一标识bookId。书籍笔记抓取器根据bookId抓取对应书籍的全部笔记、划线和想法。数据解析与清洗模块将抓取到的原始JSON或HTML数据解析成结构化的Python对象如字典、列表并清洗掉无用的信息。存储与输出模块将清洗后的数据按照用户选择的格式如每本书一个Markdown文件、汇总的CSV表格、导入Notion的JSON等保存到本地。调度与日志模块进阶管理整个抓取流程例如遇到网络错误重试并记录详细的日志方便出错时排查。3. 实操步骤详解从登录到导出下面我们进入最关键的实操环节。我会以代码片段加详细解说的方式带你一步步实现。3.1 第一步模拟登录与会话维持登录是第一步也是最容易出错的一步。微信读书Web端通常支持扫码登录。import requests from lxml import etree import time import json class WeReadCrawler: def __init__(self): self.session requests.Session() # 设置一个常见的浏览器User-Agent避免被简单的UA检查拦截 self.session.headers.update({ 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 }) self.base_url https://weread.qq.com def login_by_cookie(self, cookie_str): 通过手动设置的Cookie登录。这是最常用、最稳定的方法。 # 将浏览器中复制出来的Cookie字符串转换为字典 cookies {} for item in cookie_str.split(;): # 处理可能存在的空格和空值 if in item: key, value item.strip().split(, 1) cookies[key] value # 更新会话的Cookies self.session.cookies.update(cookies) # 验证登录是否成功访问个人中心页面 test_url f{self.base_url}/web/user resp self.session.get(test_url) if resp.status_code 200 and login not in resp.url: print(登录状态验证成功) return True else: print(登录失败请检查Cookie是否有效。) return False操作解释与避坑指南如何获取Cookie手动在浏览器中登录weread.qq.com然后打开开发者工具F12切换到Network网络标签。刷新页面点击任意一个请求通常是第一个文档请求在Headers请求头 中找到Cookie字段将其整个字符串复制出来。Cookie的有效期微信读书的登录态Cookie有一定有效期。如果长时间未使用可能会失效需要重新登录获取。因此我们的爬虫程序最好设计成“每次运行前检查或更新Cookie”的模式而不是写死一个Cookie。会话Session的重要性使用requests.Session()可以自动管理Cookie在后续的所有请求中保持登录状态无需手动处理。3.2 第二步抓取书架图书列表登录成功后我们需要获取用户的所有书籍列表关键是拿到每本书的bookId。def get_bookshelf(self): 获取书架上的所有书籍列表 shelf_url f{self.base_url}/web/shelf resp self.session.get(shelf_url) if resp.status_code ! 200: print(f获取书架失败状态码{resp.status_code}) return [] # 使用lxml解析HTML html etree.HTML(resp.text) # 分析网页结构找到包含书籍信息的元素。这里需要根据实际页面结构调整选择器。 # 通常书籍信息会通过JavaScript加载直接藏在HTML的某个script标签的JSON数据中。 # 更可靠的方式是在开发者工具中搜索 bookId 或 booklist找到对应的API接口。 script_data html.xpath(//script[contains(text(), window.__INITIAL_STATE__)]/text()) if script_data: # 提取JSON部分这是一个非常常见的获取初始数据的方法 import re json_str re.search(rwindow\.__INITIAL_STATE__\s*\s*({.*?});, script_data[0], re.DOTALL) if json_str: initial_state json.loads(json_str.group(1)) # 你需要层层解析这个initial_state对象找到bookList # 例如book_list initial_state[shelf][books] # 具体路径需要你实际打印出 initial_state 的结构来探索 print(json.dumps(initial_state, indent2, ensure_asciiFalse)[:1000]) # 打印前1000字符观察结构 # 假设我们找到了路径 books initial_state.get(shelf, {}).get(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), progress: book.get(progress, 0) # 阅读进度 } book_list.append(book_info) print(f成功获取 {len(book_list)} 本书籍。) return book_list print(未能从页面解析出书籍列表尝试寻找API接口...) # 备选方案直接查找网络请求中的API # 通常在书架页面会有一个XHR请求返回书籍列表例如 /web/shelf/books api_url f{self.base_url}/web/shelf/books api_resp self.session.get(api_url) if api_resp.status_code 200: data api_resp.json() books data.get(books, []) # 处理books... return self._parse_api_book_list(books) return []核心技巧与排查动态数据加载现代网页大量使用JavaScript动态加载数据。直接解析初始HTML可能拿不到完整列表。关键动作是在浏览器开发者工具的Network标签中过滤XHR或Fetch请求查看哪些请求返回了booklist格式的数据。找到这个API端点Endpoint是最高效的方法。解析window.__INITIAL_STATE__很多React/Vue应用会将初始数据放在这个全局变量里。这是一个宝藏通常包含了页面初始化所需的所有数据。用正则表达式提取并解析成JSON是获取数据的捷径。勤用打印调试在解析复杂JSON时多用print(json.dumps(data, indent2, ensure_asciiFalse))打印出数据的完整结构再决定如何提取。不要靠猜。3.3 第三步抓取单本书籍的笔记与划线这是最核心的部分。每本书的笔记都有一个独立的接口。def get_book_notes(self, book_id): 根据bookId获取书籍的笔记、划线和想法 # 这个API接口是通过分析网络请求得到的 notes_api_url f{self.base_url}/web/book/bookmarklist?bookId{book_id} resp self.session.get(notes_api_url) if resp.status_code ! 200: print(f获取书籍 {book_id} 的笔记失败状态码{resp.status_code}) return None data resp.json() # 响应结构通常包含一个 updated 时间戳和一个 bookmarks 或 reviews 列表 if not data or bookmarks not in data: print(f书籍 {book_id} 无笔记或接口返回格式异常。) return [] bookmarks data[bookmarks] note_list [] for item in bookmarks: # 笔记类型1-划线2-笔记3-想法 # 需要根据实际API响应字段调整 note { chapter: item.get(chapterTitle, 未知章节), content: item.get(markText, ).strip(), # 划线内容 note: item.get(content, ).strip(), # 写的笔记 createTime: item.get(createTime, 0), # 时间戳 range: item.get(range, ), # 在原文中的位置范围 } # 只有当有内容划线或笔记时才保存 if note[content] or note[note]: note_list.append(note) print(f书籍 {book_id} 共获取到 {len(note_list)} 条有效笔记。) return note_list重要注意事项接口地址与参数/web/book/bookmarklist这个接口是我举例你必须自己在当前版本的微信读书网页版中打开一本书的笔记页面通过开发者工具重新确认这个接口地址和参数。它可能会变。分页与全部数据如果一本书的笔记非常多接口可能分页返回。你需要检查响应中是否有hasMore、next之类的字段并循环请求直到获取全部数据。数据去重与清洗有时接口返回的数据可能包含空内容或重复项比如快速点击划线。在保存前进行简单的清洗和去重根据range或createTime判断是很好的实践。频率限制不要以极高的频率连续请求接口这可能导致IP被暂时限制。在请求间使用time.sleep(random.uniform(0.5, 1.5))增加随机延迟是礼貌且安全的做法。3.4 第四步数据存储与格式化输出获取到数据后我们需要将其保存为有用的格式。这里以生成每本书独立的Markdown文件为例。def save_as_markdown(self, book_info, notes): 将一本书的笔记保存为Markdown文件 if not notes: print(f书籍《{book_info[title]}》无笔记跳过生成。) return # 使用书名作为文件名移除非法字符 import re safe_title re.sub(r[\\/*?:|], _, book_info[title]) filename f{safe_title}.md with open(filename, w, encodingutf-8) as f: # 写入书籍元信息 f.write(f# 《{book_info[title]}》笔记导出\n\n) f.write(f- **作者**: {book_info.get(author, 未知)}\n) f.write(f- **进度**: {book_info.get(progress, 0)*100:.1f}%\n) f.write(f- **导出时间**: {time.strftime(%Y-%m-%d %H:%M:%S)}\n\n) f.write(---\n\n) # 按章节分组笔记可选使结构更清晰 from collections import defaultdict chapters defaultdict(list) for note in notes: chapters[note[chapter]].append(note) for chapter_title, chapter_notes in chapters.items(): f.write(f## {chapter_title}\n\n) for idx, note in enumerate(chapter_notes, 1): f.write(f### 笔记 {idx}\n) if note[content]: f.write(f {note[content]}\n\n) if note[note]: f.write(f{note[note]}\n\n) f.write(f*位置{note[range]}* | *时间{self._format_time(note[createTime])}*\n) f.write(\n---\n\n) print(f笔记已保存至文件{filename}) def _format_time(self, timestamp): 将时间戳格式化为可读字符串 if timestamp and timestamp 0: return time.strftime(%Y-%m-%d %H:%M, time.localtime(timestamp)) return 未知时间输出格式的更多选择CSV/Excel使用pandas库可以轻松将笔记列表转换为DataFrame然后导出为notes.csv或notes.xlsx。适合进行数据分析和统计。HTML报告使用Jinja2模板可以生成一个带有目录、搜索、高亮等功能的精美HTML页面阅读体验更佳。导入到其他平台解析数据后可以按照Notion、Obsidian、Flomo等平台的导入格式要求生成特定的文件实现无缝迁移。3.5 第五步主流程串联与异步优化将上述模块串联起来并加入简单的异步处理以提高多本书籍的导出速度。import asyncio import aiohttp import random class AsyncWeReadCrawler(WeReadCrawler): 异步版本的爬虫用于快速导出大量书籍 async def fetch_book_notes_async(self, session, book_id, semaphore): 异步获取单本书笔记 async with semaphore: # 使用信号量控制并发数避免请求过快 url f{self.base_url}/web/book/bookmarklist?bookId{book_id} try: async with session.get(url) as response: if response.status 200: data await response.json() return book_id, data.get(bookmarks, []) else: print(f书籍 {book_id} 请求失败: {response.status}) return book_id, [] except Exception as e: print(f书籍 {book_id} 请求异常: {e}) return book_id, [] finally: # 随机延迟模拟人类操作 await asyncio.sleep(random.uniform(0.3, 0.8)) async def export_all_books_async(self, book_list, concurrency5): 异步导出所有书籍的笔记 # 限制并发连接数 connector aiohttp.TCPConnector(limitconcurrency) timeout aiohttp.ClientTimeout(total30) semaphore asyncio.Semaphore(concurrency) async with aiohttp.ClientSession( connectorconnector, timeouttimeout, headersself.session.headers, cookiesself.session.cookies.get_dict() # 传递Cookie ) as session: tasks [] for book in book_list: task asyncio.create_task( self.fetch_book_notes_async(session, book[bookId], semaphore) ) tasks.append(task) # 等待所有任务完成 results await asyncio.gather(*tasks, return_exceptionsTrue) # 处理结果 all_notes_map {} for result in results: if isinstance(result, Exception): print(f任务执行出错: {result}) continue book_id, notes result if notes: all_notes_map[book_id] notes # 保存笔记 for book in book_list: book_id book[bookId] if book_id in all_notes_map: self.save_as_markdown(book, all_notes_map[book_id]) else: print(f书籍《{book[title]}》未获取到笔记。) # 使用示例 async def main(): crawler AsyncWeReadCrawler() # 1. 登录这里仍需同步获取Cookie cookie wr_logined1; wr_skeyxxx; ... # 你的Cookie if crawler.login_by_cookie(cookie): # 2. 获取书架同步 books crawler.get_bookshelf() if books: # 3. 异步导出所有笔记 await crawler.export_all_books_async(books, concurrency3) # 控制并发为3 else: print(未获取到书籍列表。) if __name__ __main__: asyncio.run(main())异步改造的核心点替换请求库将requests替换为aiohttp。使用async/await将所有涉及网络IO的函数定义为async并使用await调用。管理并发使用asyncio.Semaphore或aiohttp.TCPConnector(limit)来控制同时发起的请求数量防止对服务器造成过大压力或被封IP。会话传递需要将同步登录获得的Cookie字典传递给异步的ClientSession。4. 常见问题与排查技巧实录在实际操作中你几乎一定会遇到下面这些问题。这里是我的“踩坑”记录和解决方案。4.1 登录态失效或无法登录现象程序运行一段时间后再次请求接口返回401/403错误或直接跳转到登录页。排查检查Cookie有效期微信读书的登录Cookie如wr_logined,wr_skey可能有过期时间例如几天或几周。解决方案是每次运行脚本前手动在浏览器登录并更新Cookie字符串。更自动化的方法是模拟扫码登录流程但这涉及解析二维码、处理回调复杂度陡增对于个人项目手动更新Cookie是性价比最高的选择。检查请求头有些接口会验证Referer来源页或User-Agent。确保你的请求头看起来像一个正常的浏览器。可以在开发者工具中复制完整的请求头信息。IP或行为异常如果短时间内请求过于频繁服务器可能暂时限制你的IP。解决方案是增加请求间隔time.sleep使用随机延迟并降低并发数。4.2 抓取不到数据或数据不全现象get_bookshelf返回空列表或者get_book_notes返回的笔记数量远少于实际数量。排查接口已变更这是最大的可能。微信读书前端更新后API路径或参数可能改变。唯一的方法就是重新用开发者工具分析网络请求。重点看XHR/Fetch类型的请求寻找返回book、shelf、bookmark、review等关键词的接口。数据是分页加载的检查API返回的JSON中是否有total、hasMore、next等字段。如果有你需要构造分页参数如page1pageSize20循环请求直到hasMore为false。笔记类型区分微信读书的“笔记”可能包含“划线”mark、“笔记”note和“想法”review。不同的数据可能来自不同的接口例如/book/bookmarklist和/book/review/list。你需要检查自己需要哪些并调用对应的接口。4.3 异步请求时出现SSL错误或连接关闭现象在使用aiohttp进行大量并发请求时偶尔会报SSL相关错误或ClientConnectorError。解决方案# 在创建ClientSession时可以调整SSL验证和连接池设置 connector aiohttp.TCPConnector( limit10, # 控制总连接数 limit_per_host2, # 控制对同一主机的连接数 sslFalse # 如果SSL问题频繁可暂时关闭验证不推荐长期使用 ) timeout aiohttp.ClientTimeout(total60) # 设置总超时 async with aiohttp.ClientSession(connectorconnector, timeouttimeout) as session: # ...更根本的方法是安装最新的certifi证书包并确保网络环境稳定。4.4 生成的Markdown文件乱码或格式错乱现象打开保存的.md文件中文显示为乱码或换行、标题格式不对。解决方案编码问题确保所有文件操作open都指定了encodingutf-8。内容清洗从网页抓取的内容可能包含HTML标签如br、空白字符\u200b零宽空格等。在保存前可以使用re.sub(r.*?, , text)移除简单HTML标签用text.strip()清理首尾空格。Markdown特殊字符转义如果笔记内容本身包含Markdown语法字符如#,*,_,直接写入可能会破坏格式。可以考虑用反斜杠进行转义或者使用Markdown库如mistune,markdown来安全地生成内容。4.5 法律与道德风险提醒这是最重要的一点。爬虫技术是一把双刃剑。尊重robots.txt检查weread.qq.com/robots.txt了解网站允许和禁止爬取的范围。遵守服务条款查看微信读书的用户协议明确个人数据使用的边界。本项目初衷是备份个人数据请勿用于爬取他人数据、大量爬取公开书籍内容或进行任何商业用途。控制请求频率务必为你的爬虫添加延迟模拟人类浏览速度避免对微信读书服务器造成不必要的负担。数据用途导出的数据请仅用于个人学习、备份和总结。请尊重知识产权。5. 项目扩展与优化方向一个基础版本完成后你可以根据个人需求将它打磨得更加强大和易用图形化界面GUI使用PyQt5、Tkinter或Flet为脚本套一个简单的界面让非技术朋友也能一键导出。界面可以包含Cookie输入框、导出格式选择、书籍勾选列表和进度条。定时自动备份结合系统定时任务如Linux的cronWindows的任务计划程序每周或每月自动运行一次脚本实现读书笔记的定期备份。集成到笔记流将导出功能嵌入到你个人的知识管理流程中。例如导出后自动调用脚本将Markdown文件分类存入Obsidian库并打上标签。数据增强除了笔记还可以尝试抓取书籍的目录信息、他人热门划线等丰富你的个人读书档案。错误恢复与断点续传对于成百上千本书的导出网络波动可能导致中途失败。可以设计一个检查点机制记录已成功导出的书籍ID下次运行时跳过它们。最后我想说的是这个项目最大的价值不在于那几百行代码而在于你通过它重新掌握了自己数据的所有权。在数字时代我们的阅读记录、思考痕迹散落在各个平台。通过这样的技术实践你不仅学会了一项实用的技能更是在构建一个属于你自己的、可迁移、可长期保存的数字知识库。这个过程本身就是一种深刻的学习和积累。本文还有配套的精品资源点击获取