ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python爬虫实战:一键导出微信读书笔记,构建个人知识库

2026/9/5 7:25:35 拓冰建站 浏览量
Python爬虫实战:一键导出微信读书笔记,构建个人知识库 简介这是一套面向Python初学者与爬虫爱好者的微信读书数据导出工具解决个人学习场景下书籍列表与阅读笔记难以批量保存的问题。资源包含8个文件总计277KB以3个核心Python脚本GUI界面、主爬虫逻辑、Excel处理、2个文本说明文件依赖清单与使用指南、2张界面演示图及1份Markdown文档构成结构紧凑、即装即用。已有2168人下载学习适合希望快速掌握登录模拟、页面解析、GUI封装与数据导出全流程的实践者。读者可直接运行pyqt_gui.py启动图形化界面通过可视化操作完成账号登录、书架抓取、笔记提取与Excel一键导出配套requirement.txt确保环境一致性README.md与注释清晰标注关键步骤与调试提示降低上手门槛。1. 项目缘起与核心价值作为一个重度阅读爱好者我几乎把所有的碎片时间都泡在了微信读书上。划线、写想法、看别人的批注几年下来积累了上千条笔记。但问题也随之而来这些宝贵的阅读记录和思考碎片全都锁在微信读书的服务器里。想整理成个人知识库想离线保存以防万一或者单纯就是想拥有一个属于自己的、格式整洁的笔记备份官方并没有提供一个完美的导出方案。手动复制粘贴对于动辄几十上百条的笔记来说这无异于一场噩梦。正是这个痛点催生了这个项目用Python写一个爬虫工具实现一键导出微信读书的书籍信息和全部笔记。这件事的价值远不止于“备份”这么简单。首先它关乎数据主权。你的阅读记录、你的思考痕迹理应有一份完全由你掌控的本地副本。其次是知识管理的需要。导出的笔记可以轻松导入到Notion、Obsidian、思源笔记等专业工具中进行二次加工、关联思考构建个人的阅读知识图谱。最后它还是一个绝佳的Python爬虫实战项目涵盖了登录态维持、接口逆向分析、数据清洗整理等爬虫工程师的常见工作流实用性极强。接下来我将彻底拆解这个工具的完整实现过程从原理分析、环境搭建到核心代码逐行解读再到打包成开箱即用的脚本。你会发现整个过程就像侦探破案一步步找到数据藏匿的地址并优雅地把它“请”出来。2. 核心思路与技术方案选型在动手写代码之前我们必须先想清楚我们要拿什么以及从哪里拿我们的目标是获取两类核心数据书籍元信息书名、作者、封面等和用户笔记数据包括章节内的划线、以及独立的想法批注。微信读书作为一个成熟的App其数据交互必然通过API接口进行。因此爬虫的核心思路不是去解析复杂的HTML页面而是直接模拟App或网页端的请求调用其内部的数据接口。2.1 技术栈选择与理由请求库Requests这是Python生态中公认的HTTP库标杆语法简洁功能强大。相比于urllib它的封装更人性化对于这个项目我们不需要aiohttp那样的异步并发能力Requests的同步请求简单可靠完全够用。数据解析内置的json模块微信读书的API返回的数据格式基本都是JSON。Python标准库中的json模块足以完美应对解析和序列化任务无需引入额外的依赖。数据处理与导出Pandas OpenpyxlPandas数据处理的“瑞士军刀”。我们将获取的笔记列表通常是JSON数组转化为DataFrame可以非常方便地进行清洗、筛选、排序等操作。Openpyxl专门用于读写Excel文件的库。选择它而不是Pandas自带的to_excel是因为我们需要对导出的Excel格式进行更精细的控制比如调整列宽、设置单元格样式将笔记内容自动换行提升可读性。登录态维持Session对象Requests库的Session对象是关键。它能够自动处理Cookies在一次会话中保持登录状态。我们只需要在开始时成功“登录”一次后续的所有请求都会自动携带身份凭证。为什么不直接用Selenium这是一个常见的疑问。Selenium模拟浏览器操作对于需要执行JavaScript或应对复杂反爬的网站很有效。但微信读书的API接口清晰数据以结构化JSON返回使用Selenium相当于用大炮打蚊子会引入不必要的复杂度安装浏览器驱动、运行效率低。直接进行HTTP接口请求是更轻量、更高效的选择。2.2 逆向分析找到数据之门这是整个项目最具挑战性也最有趣的部分。我们需要扮演“侦探”找出微信读书请求数据的真实地址。方法一抓包移动端App使用像Fiddler或Charles这样的抓包工具在电脑上设置代理并将手机的网络代理指向电脑。然后在手机上打开微信读书App浏览书籍、查看笔记。此时抓包工具会捕获到所有网络请求。你需要从中筛选出包含books、notes、highlights、bookmarks等关键词的请求分析其URL、请求头特别是Cookie和Authorization类字段以及请求参数。方法二分析网页端直接在电脑浏览器Chrome或Edge中打开微信读书网页版使用开发者工具。操作步骤类似打开“网络”标签页在网页中翻看书籍、点击笔记选项卡。观察发出的XHR或Fetch请求找到返回笔记列表的那个接口。通过分析你会发现核心接口通常形如https://i.weread.qq.com/user/notebooks或https://i.weread.qq.com/book/bookmarklist。 请求头中通常会包含一个至关重要的字段wr_vid、wr_skey或Authorization: Bearer xxx。这就是你的身份令牌。实操心得获取身份凭证最直接稳定的方式是从网页版获取。登录微信读书网页版后在开发者工具的“应用程序”标签页中查看Cookies找到名为wr_vid或类似名称的项其value就是我们需要的东西。这个vid或skey通常有较长的有效期足以完成一次导出任务。3. 环境准备与核心依赖安装工欲善其事必先利其器。我们先来搭建一个干净的项目环境。我强烈建议使用conda或venv创建独立的Python虚拟环境避免与系统或其他项目的包版本冲突。# 创建并激活一个名为 weread-export 的虚拟环境 python -m venv weread-export # Windows 激活 weread-export\Scripts\activate # macOS/Linux 激活 source weread-export/bin/activate激活虚拟环境后安装所需的第三方库pip install requests pandas openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simplerequests: 用于发送HTTP请求。pandas: 用于数据处理和转换。openpyxl: 用于生成格式良好的Excel文件。一个良好的项目结构有助于代码管理建议如下wechat_read_exporter/ ├── config.py # 配置文件存放Cookie等敏感信息 ├── wechat_read_api.py # 核心API请求模块 ├── data_processor.py # 数据处理与导出模块 ├── main.py # 主程序入口 └── requirements.txt # 项目依赖列表在config.py中我们以变量的形式存放凭证# config.py # 请将从浏览器中获取的Cookie值替换到此处 USER_COOKIE wr_vidYOUR_ACTUAL_VID_VALUE; wr_skeyYOUR_ACTUAL_SKEY_VALUE # 或者使用Bearer Token如果抓包得到的是这种形式 BEARER_TOKEN your_bearer_token_here注意事项安全第一config.py文件绝对不能上传到GitHub等公开代码仓库。你应该将它添加到.gitignore文件中。凭证Cookie/Token是你的账户通行证泄露可能导致账户被他人滥用。务必妥善保管。本项目所有代码仅用于个人学习和技术交流请尊重微信读书的用户协议不要进行高频、大量的请求以免对服务器造成压力。4. 核心模块实现从请求到数据接下来我们分模块构建这个爬虫工具。首先从最核心的API交互开始。4.1 构建请求客户端在wechat_read_api.py中我们创建一个类来封装所有与微信读书服务器通信的逻辑。# wechat_read_api.py import requests import json import time from config import USER_COOKIE, BEARER_TOKEN class WeReadClient: def __init__(self): self.session requests.Session() self.headers { 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, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, } # 优先使用Bearer Token如果没有则使用Cookie if BEARER_TOKEN: self.headers[Authorization] fBearer {BEARER_TOKEN} else: self.headers[Cookie] USER_COOKIE self.base_url https://i.weread.qq.com def _make_request(self, method, endpoint, paramsNone, dataNone): 统一的请求发送方法处理异常和重试 url f{self.base_url}{endpoint} max_retries 3 for attempt in range(max_retries): try: response self.session.request(method, url, headersself.headers, paramsparams, jsondata, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 return response.json() # 尝试解析为JSON except requests.exceptions.RequestException as e: print(f请求失败 ({attempt1}/{max_retries}): {e}) if attempt max_retries - 1: wait_time 2 ** attempt # 指数退避策略 print(f等待 {wait_time} 秒后重试...) time.sleep(wait_time) else: print(重试次数用尽请求最终失败。) raise except json.JSONDecodeError: print(响应内容不是有效的JSON格式。) return response.text # 返回文本内容 return None def get_bookshelf(self): 获取书架上的书籍列表 # 实际接口可能需要参数如排序方式、分页等需根据抓包结果调整 endpoint /user/books params {type: 1, count: 1000} # 假设参数 return self._make_request(GET, endpoint, paramsparams) def get_book_detail(self, book_id): 获取指定书籍的详细信息元数据 endpoint f/book/info params {bookId: book_id} return self._make_request(GET, endpoint, paramsparams) def get_book_notes(self, book_id): 获取指定书籍的全部笔记划线和想法 # 这个接口可能是分页的需要循环获取 all_notes [] page_num 1 page_size 100 # 每页数量根据接口支持调整 while True: endpoint /book/bookmarklist params {bookId: book_id, count: page_size, currentPage: page_num} data self._make_request(GET, endpoint, paramsparams) if not data or updated not in data or not data.get(updated): # 根据接口实际返回结构判断是否还有数据 break notes data.get(updated, []) if not notes: break all_notes.extend(notes) # 如果返回数量小于请求数量说明是最后一页 if len(notes) page_size: break page_num 1 time.sleep(0.5) # 礼貌性延迟避免请求过快 return all_notes关键点解析Session对象self.session保证了在一次运行中所有请求共享Cookies维持登录状态。请求头User-Agent模拟浏览器Accept声明接受JSON格式这些都是让服务器认为请求来自合法客户端的常见操作。错误处理与重试_make_request方法封装了请求过程加入了简单的重试机制指数退避和异常捕获增强了程序的健壮性。分页处理get_book_notes方法中实现了循环请求直到获取所有笔记。这是处理列表接口的通用模式。4.2 数据处理与导出引擎获取到原始的JSON数据后我们需要将其转换成结构清晰、便于阅读和存档的格式。这里选择Excel作为导出格式因为它普及率高且能很好地处理表格数据。在data_processor.py中我们创建数据处理类# data_processor.py import pandas as pd from openpyxl import load_workbook from openpyxl.styles import Alignment, Font import os class DataExporter: staticmethod def export_notes_to_excel(notes_list, book_info, output_diroutput): 将笔记列表导出为格式化的Excel文件 if not notes_list: print(f书籍《{book_info.get(title, 未知)}》没有笔记。) return None # 创建输出目录 os.makedirs(output_dir, exist_okTrue) # 将数据转换为Pandas DataFrame # 原始笔记数据结构复杂需要展平提取关键字段 processed_notes [] for note in notes_list: # 根据实际接口返回的字段名调整 processed_note { 章节: note.get(chapterTitle, ), 位置: note.get(range, ), # 可能是location-1或第x章-第y节 标记内容: note.get(markText, ).replace(\n, ), # 替换换行方便显示 我的想法: note.get(abstract, ), # 想法/批注 标记时间: note.get(createTime, ), 更新时间: note.get(updateTime, ), } processed_notes.append(processed_note) df pd.DataFrame(processed_notes) # 处理时间戳如果接口返回的是Unix时间戳 if 标记时间 in df.columns: df[标记时间] pd.to_datetime(df[标记时间], units, errorscoerce).dt.strftime(%Y-%m-%d %H:%M:%S) if 更新时间 in df.columns: df[更新时间] pd.to_datetime(df[更新时间], units, errorscoerce).dt.strftime(%Y-%m-%d %H:%M:%S) # 生成文件名 safe_title .join([c for c in book_info.get(title, 未命名书籍) if c.isalnum() or c in ( , _, -)]).rstrip() filename f{safe_title}_笔记导出.xlsx filepath os.path.join(output_dir, filename) # 使用Pandas先写入Excel with pd.ExcelWriter(filepath, engineopenpyxl) as writer: df.to_excel(writer, indexFalse, sheet_name我的笔记) # 获取workbook和worksheet对象进行格式调整 workbook writer.book worksheet writer.sheets[我的笔记] # 设置列宽自适应粗略估计 column_widths {} for column in df: # 获取列字母 column_letter openpyxl.utils.get_column_letter(df.columns.get_loc(column) 1) # 以列标题和内容的最大长度为基础设置宽度 max_length max(df[column].astype(str).map(len).max(), len(column)) adjusted_width min(max_length 2, 50) # 设置最大宽度为50 column_widths[column_letter] adjusted_width for col_letter, width in column_widths.items(): worksheet.column_dimensions[col_letter].width width # 设置“标记内容”和“我的想法”列自动换行 wrap_columns [标记内容, 我的想法] for col_name in wrap_columns: if col_name in df.columns: col_idx df.columns.get_loc(col_name) 1 col_letter openpyxl.utils.get_column_letter(col_idx) for row in range(2, len(df) 2): # 从第2行开始第1行是标题 cell worksheet[f{col_letter}{row}] cell.alignment Alignment(wrapTextTrue, verticaltop) # 设置标题行加粗 for col in range(1, len(df.columns) 1): cell worksheet.cell(row1, columncol) cell.font Font(boldTrue) print(f笔记已成功导出至{filepath}) return filepath staticmethod def export_bookshelf_to_csv(books_list, output_diroutput): 将书架列表导出为CSV文件便于查看 if not books_list: print(书架为空或获取失败。) return None os.makedirs(output_dir, exist_okTrue) processed_books [] for book in books_list: processed_book { 书名: book.get(title, ), 作者: book.get(author, ), 封面URL: book.get(cover, ), 书籍ID: book.get(bookId, ), 最后阅读时间: book.get(lastReadTime, ), 是否付费: 是 if book.get(isPaid) else 否, } processed_books.append(processed_book) df pd.DataFrame(processed_books) filepath os.path.join(output_dir, 我的微信读书书架.csv) df.to_csv(filepath, indexFalse, encodingutf-8-sig) # utf-8-sig解决Excel打开中文乱码 print(f书架列表已导出至{filepath}) return filepath关键点解析数据清洗原始API返回的笔记数据嵌套可能较深。processed_notes循环的目的就是提取我们关心的核心字段章节、划线内容、想法等将其扁平化方便放入表格。时间格式转换API返回的时间往往是Unix时间戳秒级。pd.to_datetime(units)可以将其转换为可读的日期时间字符串。文件名安全处理safe_title这行代码移除了书名中可能对文件系统不友好的字符如/ \ : * ? |避免保存文件时出错。格式美化这是使用openpyxl的直接价值。我们不仅导出数据还调整了列宽、设置了自动换行对于长文本的笔记至关重要、加粗了标题行让生成的Excel文件开箱即用体验更佳。编码问题导出CSV时使用utf-8-sig编码可以确保用Excel直接打开时中文字符正常显示无需手动选择编码。5. 主程序串联与使用流程现在我们将各个模块组合起来形成一个完整的、用户友好的脚本。在main.py中# main.py import argparse from wechat_read_api import WeReadClient from data_processor import DataExporter import json def main(): parser argparse.ArgumentParser(description微信读书笔记一键导出工具) parser.add_argument(--book-id, typestr, help指定要导出笔记的书籍ID如不指定则导出书架列表) parser.add_argument(--all-notes, actionstore_true, help导出书架中所有书籍的笔记慎用可能请求过多) parser.add_argument(--output, typestr, defaultoutput, help导出文件存放目录默认为当前目录下的output文件夹) args parser.parse_args() client WeReadClient() exporter DataExporter() # 测试连接和登录状态 try: # 尝试获取书架验证凭证是否有效 bookshelf client.get_bookshelf() if not bookshelf or books not in bookshelf: print(错误无法获取书架信息请检查Cookie/Token配置是否正确且未过期。) return print(登录状态验证成功) except Exception as e: print(f初始化客户端失败{e}) return if args.book_id: # 模式1导出指定单本书的笔记 print(f开始处理书籍ID: {args.book_id}) book_detail client.get_book_detail(args.book_id) if not book_detail: print(f未找到书籍ID为 {args.book_id} 的详细信息。) return notes client.get_book_notes(args.book_id) exporter.export_notes_to_excel(notes, book_detail, args.output) elif args.all_notes: # 模式2导出书架所有书的笔记警告可能耗时较长 confirm input(警告此操作将尝试导出书架所有书籍的笔记可能产生大量请求。是否继续(y/N): ) if confirm.lower() ! y: print(操作已取消。) return books bookshelf.get(books, []) print(f检测到 {len(books)} 本书开始批量导出...) for idx, book in enumerate(books): book_id book.get(bookId) book_title book.get(title, f未知书籍{idx1}) print(f[{idx1}/{len(books)}] 正在处理《{book_title}》...) notes client.get_book_notes(book_id) if notes: exporter.export_notes_to_excel(notes, book, args.output) else: print(f 《{book_title}》暂无笔记跳过。) # 每处理完一本书稍作停顿 import time time.sleep(1) print(批量导出完成) else: # 模式3默认行为仅导出书架列表 print(未指定书籍ID默认导出书架列表。) exporter.export_bookshelf_to_csv(bookshelf.get(books, []), args.output) print(请查看生成的CSV文件中的‘书籍ID’然后使用 --book-id 参数导出特定书籍的笔记。) if __name__ __main__: main()使用方式获取Cookie/Token按前述方法从微信读书网页版获取wr_vid等Cookie值填入config.py。导出书架列表在命令行运行python main.py。程序会验证登录状态然后生成一个我的微信读书书架.csv文件里面包含所有书的ID和基本信息。导出单本书笔记从CSV中找到你想导出的那本书的书籍ID运行python main.py --book-id [书籍ID]。程序会生成一个以书名命名的Excel文件。谨慎批量导出运行python main.py --all-notes程序会询问确认后遍历书架上的书并逐一导出笔记。6. 常见问题、排查技巧与进阶优化在实际操作中你几乎一定会遇到一些问题。下面是我在开发和多次使用中踩过的坑和总结的经验。6.1 常见问题速查表问题现象可能原因排查与解决思路运行脚本立即报错KeyError或无法导入configconfig.py文件不存在或格式错误1. 确认项目根目录下有config.py文件。2. 检查config.py中变量名是否与代码中调用的一致USER_COOKIE。3. 确认Cookie值字符串被正确引号包裹。提示“登录状态验证失败”或返回空数据Cookie/Token已过期或无效1.这是最常见的问题。Cookie特别是wr_vid有有效期。重新登录微信读书网页版在开发者工具中复制新的Cookie值替换config.py中的旧值。2. 检查复制的Cookie是否完整包含了wr_vid和wr_skey如果有。3. 尝试使用完整的请求头包括Referer等字段模拟得更像真实请求。能获取书架但获取笔记时返回空数组或错误1. 书籍ID不正确。2. 接口地址或参数已更新。3. 该书籍确实没有笔记。1. 确认书籍ID来自有效的接口如书架接口而非肉眼猜测。2.进行抓包对比。手动在网页版打开那本书的笔记页面抓取真实的笔记接口请求与代码中的endpoint和params进行对比更新代码。3. 在网页版确认该书是否有笔记。导出Excel文件打开乱码CSV文件编码问题确保使用utf-8-sig编码写入CSV文件代码已处理。Excel文件乱码较少见如遇到可尝试用WPS或文本编辑器打开检查。请求一段时间后突然失败返回异常响应IP或账号被临时限制1.最重要的原则礼貌爬取。在循环请求中加入time.sleep()模拟人工操作间隔。我设置的0.5-1秒是比较安全的。2. 不要短时间内高频请求所有书籍。批量导出--all-notes功能请谨慎使用最好分批次进行。3. 如果被限制等待几个小时或第二天再试。笔记内容不全只导出了一部分接口是分页的代码分页逻辑有误检查get_book_notes方法中的分页逻辑。根据抓包结果确认接口返回数据中是否有has_more、total等字段来判断是否还有下一页并调整循环终止条件。6.2 进阶优化与扩展思路一个基础可用的工具已经完成但我们可以让它更强大、更智能导出为Markdown对于使用Obsidian、思源笔记等Markdown编辑器的用户将笔记导出为.md文件可能更直接。可以扩展DataExporter类增加一个export_notes_to_markdown方法按照“书名.md”生成文件每条笔记以引用的格式 划线内容和段落我的想法...来组织。集成到工作流使用schedule库或系统级的cron/Task Scheduler让脚本每周自动运行一次实现笔记的定期自动备份。增加图形界面使用PyQt、Tkinter或更现代的Flet框架为脚本包装一个简单的GUI。用户可以直接输入Cookie从下拉列表选择书籍点击按钮导出体验更友好。数据去重与合并如果你多次导出同一本书笔记可能会有重复。可以在导出前根据“位置”或“标记内容”对DataFrame进行去重。解析章节结构更高级的玩法是不仅导出笔记还尝试获取书籍的目录结构章/节并将笔记归属到具体的章节标题下生成一个带层级结构的读书笔记大纲。这个项目的代码虽然不长但完整走通了一个“分析-获取-处理-输出”的爬虫实战流程。它解决了一个真实的需求过程中涉及的接口分析、状态维持、数据处理、异常处理等知识点都是爬虫乃至后端开发中的通用技能。希望这份详细的拆解不仅能帮你导出笔记更能让你理解背后每一步的“所以然”。本文还有配套的精品资源点击获取