Python爬虫实战:从微信读书API导出个人笔记与书架数据
2026/9/5 11:19:57 网站建设 项目流程

简介:这是一套面向Python初学者与爬虫爱好者的微信读书数据导出工具,解决个人学习场景下书籍列表与阅读笔记难以批量保存的问题。资源包共8个文件,含3个核心Python脚本(GUI界面、主爬虫逻辑、Excel处理)、2个文本配置文件、2张界面演示图及1份Markdown说明文档,整体仅277KB,轻量易部署。已有2168人下载学习,适合希望快速上手网页数据抓取、理解登录模拟与API逆向分析的实践者。读者可直接运行pyqt_gui.py启动图形化界面,通过内置流程完成微信读书账号登录、书架拉取、笔记提取及Excel一键导出;配套requirement.txt确保依赖环境可复现,README详述调试要点与法律声明,强调仅限学习交流使用。

1. 项目缘起:为什么我们需要一个自己的“书库”?

作为一名重度阅读爱好者,我几乎把所有的碎片时间都泡在了微信读书上。它确实方便,书库庞大,社区氛围也不错。但时间久了,问题就来了:我做的几百条笔记、划的上千条重点,都牢牢地锁在平台的服务器里。有一天,我突然想离线整理一下某本书的读书笔记,或者想用自己习惯的笔记软件(比如 Obsidian、Notion)来管理这些知识时,发现除了手动一条条复制粘贴,几乎没有别的办法。这太不“数字游民”了。

更现实的是,万一哪天某本书因为版权或其他原因下架了,或者平台规则变动,我们辛苦积累的阅读痕迹就可能面临风险。数据,只有掌握在自己手里,才是最安心的。于是,自己动手,丰衣足食——用 Python 写一个爬虫,把我在微信读书上的书籍信息和笔记“搬”出来,就成了一个非常实际的需求。这不仅仅是技术上的折腾,更是对个人数字资产的一种主动管理。

2. 核心思路拆解:爬虫如何与微信读书“对话”

在动手写代码之前,我们必须先搞清楚目标对象。微信读书是一个成熟的 App,其数据交互主要依靠手机客户端与后端 API(应用程序编程接口)进行。我们写的爬虫,本质上就是模拟一个微信读书 App,去和后端服务器“对话”,请求那些原本展示在 App 界面上的数据。

这个过程可以分解为几个关键步骤:

  1. 身份认证:这是第一道,也是最关键的一道坎。服务器需要知道你是谁,才能决定给你看什么数据。微信读书通常使用基于 Cookie 或 Token 的认证机制。我们需要先登录,获取到代表我们身份的“通行证”(Cookie)。
  2. 数据定位:我们需要找到存储“我的书架”和“我的笔记”的 API 地址。这些地址不会明晃晃地写在网页上,需要通过抓包工具(如 Fiddler、Charles,或浏览器开发者工具的 Network 面板)来捕获手机 App 或网页版发出的网络请求。
  3. 请求与解析:用 Python 的requests库,携带上一步获取的“通行证”,向找到的 API 地址发送 HTTP 请求。服务器会返回数据,通常是 JSON 格式。我们需要用 Python 的json库解析这些结构化的数据,提取出我们需要的书名、作者、笔记内容、划线段落等信息。
  4. 数据存储与导出:将解析出来的数据,按照我们想要的格式(如 Markdown、HTML、Excel)保存到本地文件,完成“导出”的最终步骤。

整个流程的核心在于“模拟”“解析”。下面,我们就一步步来实现它。

2.1 环境准备与工具选择

工欲善其事,必先利其器。这里列出本次项目所需的核心工具和库,并解释为什么选择它们。

  • Python 3.7+:这是我们的编程语言。选择较新的版本可以避免一些库的兼容性问题。
  • requests:Python 社区最受欢迎的 HTTP 库,用于发送网络请求,简单易用且功能强大。我们将用它来模拟浏览器/App 访问 API。
    pip install requests
  • json:Python 标准库,无需安装。用于解析服务器返回的 JSON 格式数据。
  • pandas (可选但推荐):强大的数据分析库。在我们需要将书籍和笔记整理成表格(如 Excel)时,它能极大地简化操作。
    pip install pandas
  • 抓包工具:这是逆向分析 API 的“眼睛”。我强烈推荐使用Charles ProxyFiddler。它们可以截获手机或电脑上所有应用程序的网络流量,让我们清晰地看到微信读书 App 具体向哪些网址发送了请求,以及请求和响应的具体内容。配置抓包工具需要将手机和电脑设置在同一网络,并在手机端安装并信任抓包工具的 CA 证书,这是一个标准操作,网上教程很多。

注意:使用爬虫获取数据必须遵守法律法规和网站的robots.txt协议。本项目仅用于个人学习、研究和对自身数据的备份,严禁用于商业用途、大量抓取或对服务器造成压力。请合理使用,尊重平台。

2.2 关键突破口:如何获取身份凭证(Cookie)

没有合法的身份,服务器不会搭理我们。获取 Cookie 有多种方法,这里介绍两种最实用的:

方法一:通过网页版微信读书获取(推荐,更稳定)

  1. 在电脑浏览器(Chrome/Firefox)中打开微信读书网页版(https://weread.qq.com)。
  2. 使用微信扫码登录。
  3. 登录成功后,按下F12打开开发者工具,切换到Network(网络)标签页。
  4. 刷新页面,在 Network 面板中找到任意一个来自weread.qq.com域名的请求(通常是第一个document类型的请求)。
  5. 点击该请求,在右侧的Headers选项卡中,找到Request Headers(请求头)部分,里面有一行叫做Cookie。这一长串字符就是我们的“通行证”。
  6. 复制整个Cookie字符串的值。它看起来像这样:wr_vid=xxx; wr_skey=yyy; wr_rt=zzz; ...

方法二:通过抓包工具获取手机 App 的请求

  1. 按照抓包工具(如 Charles)的教程配置好手机代理。
  2. 在手机上打开微信读书 App,进行任意操作(如刷新书架)。
  3. 在 Charles 的抓包记录中,找到主机名(Host)为i.weread.qq.comweread.qq.com的请求。
  4. 查看该请求的Headers,同样可以找到Cookie字段。

获取到 Cookie 后,我们需要在 Python 代码中用它来构建请求头。

import requests # 将你复制的 Cookie 字符串粘贴在这里 MY_COOKIE = 'wr_vid=xxx; wr_skey=yyy; wr_rt=zzz; ...' # 构建请求头,模拟一个真实的浏览器请求 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', 'Cookie': MY_COOKIE, 'Referer': 'https://weread.qq.com/', # 表明请求来源,有时服务器会检查 'Accept': 'application/json, text/plain, */*' # 声明我们接受 JSON 格式的响应 }

实操心得:Cookie 是有有效期的。如果长时间未使用,或者你在其他地方修改了密码、退出了登录,这个 Cookie 就会失效。代码运行时报错401403,或者返回的数据为空,首先就要检查 Cookie 是否还有效。一个简单的验证方法是,用这个 Cookie 在浏览器中访问微信读书网页版,看是否仍处于登录状态。

3. 逆向工程:定位核心数据 API

有了“通行证”,我们还需要知道“金库”的地址。这就需要分析微信读书的网络请求。

打开抓包工具或浏览器开发者工具,登录微信读书网页版,然后进行以下操作并观察网络请求:

  1. 获取书架书籍列表:刷新“我的书架”页面。你应该能看到一个类似https://i.weread.qq.com/user/books的请求。它的响应体就是一个 JSON,里面包含了书架上的所有书籍基本信息,如bookId,title,author,cover等。
  2. 获取某本书的笔记和划线:在网页版打开一本书,点击侧边栏的“笔记”图标。网络面板中会出现一个关键请求,其 URL 模式通常为https://i.weread.qq.com/book/bookmarklist?bookId=xxxxxx。其中bookId对应具体的书籍。这个接口返回的就是这本书里你所有的笔记、划线和想法。

通过这种方式,我们可以确定两个最核心的 API:

  • 书架API:https://i.weread.qq.com/user/books
  • 书籍笔记API:https://i.weread.qq.com/book/bookmarklist?bookId={bookId}

其中{bookId}需要从书架 API 的响应中获取。这就是我们爬虫的数据流:先调用书架 API 拿到所有书的 ID,再遍历这些 ID,逐个调用笔记 API 获取每本书的详细笔记。

3.1 编写代码:获取书架列表

让我们开始编写第一段核心代码,获取完整的书架信息。

import requests import json import time # 使用之前构建的 headers def get_bookshelf(headers): """获取微信读书书架列表""" url = 'https://i.weread.qq.com/user/books' try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 如果状态码不是200,抛出异常 data = response.json() # 打印一下原始数据结构,方便理解 # print(json.dumps(data, indent=2, ensure_ascii=False)) books = [] # 根据实际API返回结构解析,这里是一个常见结构示例 if 'books' in data: for book in data['books']: book_info = { 'bookId': book.get('bookId'), 'title': book.get('title'), 'author': book.get('author'), 'cover': book.get('cover'), 'category': book.get('category'), 'readUpdateTime': book.get('readUpdateTime') # 最后阅读时间 } books.append(book_info) print(f'成功获取到 {len(books)} 本书籍信息。') return books except requests.exceptions.RequestException as e: print(f'请求书架失败: {e}') return [] except json.JSONDecodeError as e: print(f'解析JSON失败: {e}') return [] # 调用函数 books = get_bookshelf(headers) if books: for book in books[:3]: # 打印前3本看看 print(f"书名:{book['title']}, ID: {book['bookId']}")

这段代码会向书架 API 发送请求,并将返回的 JSON 数据解析成一个 Python 字典列表。每个字典代表一本书的基本信息。bookId是后续获取笔记的关键。

3.2 编写代码:获取单本书的笔记与划线

拿到bookId后,我们就可以获取这本书里所有的“宝藏”了。

def get_book_notes(headers, book_id): """获取指定书籍的所有笔记和划线""" url = f'https://i.weread.qq.com/book/bookmarklist?bookId={book_id}' try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() data = response.json() # 笔记和划线数据通常在 `updated` 字段下的 `bookmarks` 列表中 notes = [] if 'updated' in data and isinstance(data['updated'], list): for item in data['updated']: # item 可能包含多种类型:划线(highlight)、笔记(note)、想法(abstract) note_type = item.get('type', '') content = item.get('markText', '').strip() # 划线的文本 note_content = item.get('content', '').strip() # 写的想法/笔记 chapter_title = item.get('chapterTitle', '未知章节') create_time = item.get('createTime', 0) # 只收集有内容的条目 if content or note_content: note_info = { 'type': note_type, 'highlight': content, 'note': note_content, 'chapter': chapter_title, 'createTime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(createTime)) if create_time else '未知时间' } notes.append(note_info) print(f'书籍ID {book_id} 获取到 {len(notes)} 条笔记/划线。') return notes except requests.exceptions.RequestException as e: print(f'请求笔记失败 (BookId: {book_id}): {e}') return [] except json.JSONDecodeError as e: print(f'解析笔记JSON失败 (BookId: {book_id}): {e}') return [] # 示例:获取第一本书的笔记 if books: first_book_id = books[0]['bookId'] notes = get_book_notes(headers, first_book_id) if notes: for note in notes[:2]: print(f"章节:{note['chapter']}") print(f"划线:{note['highlight'][:50]}...") # 只打印前50字符 print(f"笔记:{note['note']}") print("-" * 30)

这段代码解析了笔记 API 的返回数据。需要注意的是,数据结构可能随着微信读书的更新而变化。上述代码基于一个常见的结构编写,你可能需要根据实际抓包看到的数据格式进行微调,比如字段名可能是abstract而不是content,或者数据藏在另一个嵌套层级里。

踩坑实录:API 返回的数据结构不是一成不变的。我在不同时期抓包发现,笔记列表的路径有时在data.updated,有时在data.chapters下的每个章节对象里。最可靠的方法是,在编写代码前,先用抓包工具捕获一次真实的请求响应,然后用json.dumps(data, indent=2, ensure_ascii=False)打印出来,仔细研究其结构。这是爬虫开发中至关重要的一步。

4. 数据整合与导出:打造个人知识库

现在我们有了两个核心函数:一个获取所有书,一个获取某本书的所有笔记。接下来,我们需要将它们串联起来,并把数据保存成有用的格式。

4.1 构建完整的数据流程

一个健壮的脚本应该能处理整个书架,并考虑到网络请求的礼貌性(避免请求过快被封)。

import time import json def export_all_notes(headers, books, delay=1): """导出所有书籍的笔记""" all_data = {} for i, book in enumerate(books): book_id = book['bookId'] book_title = book['title'] print(f'正在处理 [{i+1}/{len(books)}] {book_title}...') notes = get_book_notes(headers, book_id) if notes: all_data[book_id] = { 'bookInfo': book, 'notes': notes } # 礼貌性延迟,避免请求过于频繁 time.sleep(delay) print('所有书籍笔记获取完成!') return all_data # 执行导出 all_notes_data = export_all_notes(headers, books, delay=0.5) # 每本书请求间隔0.5秒

4.2 导出为多种格式

数据在内存里不算拥有,保存到本地硬盘才算。下面提供几种常见的导出格式。

格式一:JSON(结构化,便于程序后续处理)

def save_as_json(data, filename='weread_notes_backup.json'): """保存为JSON文件""" with open(filename, 'w', encoding='utf-8') as f: # ensure_ascii=False 确保中文正常显示 json.dump(data, f, indent=2, ensure_ascii=False) print(f'数据已保存为 {filename}') save_as_json(all_notes_data)

格式二:Markdown(便于阅读和导入笔记软件)

Markdown 格式非常灵活,我们可以设计成每本书一个章节,每条笔记一个列表项。

def save_as_markdown(data, filename='weread_notes.md'): """保存为Markdown文件""" with open(filename, 'w', encoding='utf-8') as f: f.write('# 微信读书笔记导出\n\n') f.write(f'导出时间:{time.strftime("%Y-%m-%d %H:%M:%S")}\n\n') for book_id, book_data in data.items(): book_info = book_data['bookInfo'] notes = book_data['notes'] if not notes: continue f.write(f'## {book_info["title"]}\n') f.write(f'**作者:** {book_info.get("author", "未知")}\n\n') current_chapter = None for note in notes: # 按章节分组 if note['chapter'] != current_chapter: current_chapter = note['chapter'] f.write(f'### {current_chapter}\n\n') # 写入划线 if note['highlight']: f.write(f'> {note["highlight"]}\n\n') # 写入笔记/想法 if note['note']: f.write(f'{note["note"]}\n\n') f.write(f'*时间:{note["createTime"]}*\n\n') f.write('---\n\n') f.write('\n\n') print(f'Markdown文件已保存为 {filename}')

格式三:CSV/Excel(便于表格化管理和分析)

使用pandas库可以非常方便地生成表格。

import pandas as pd def save_as_excel(data, filename='weread_notes.xlsx'): """保存为Excel文件,每个Sheet是一本书""" # 创建一个Excel写入器 with pd.ExcelWriter(filename, engine='openpyxl') as writer: for book_id, book_data in data.items(): book_info = book_data['bookInfo'] notes = book_data['notes'] if not notes: continue # 将笔记列表转换为DataFrame df_notes = pd.DataFrame(notes) # 添加书籍信息作为列 df_notes['书名'] = book_info['title'] df_notes['作者'] = book_info.get('author', '') # 调整列顺序 cols = ['书名', '作者', 'chapter', 'createTime', 'highlight', 'note', 'type'] df_notes = df_notes[cols] # 将书名作为Sheet名(Excel Sheet名有长度和字符限制,需要处理) sheet_name = book_info['title'][:30] # 截取前30个字符 # 移除Sheet名中的非法字符 sheet_name = ''.join(char for char in sheet_name if char.isalnum() or char in (' ', '_')).strip() if not sheet_name: sheet_name = f'Book_{book_id[:5]}' df_notes.to_excel(writer, sheet_name=sheet_name, index=False) print(f'Excel文件已保存为 {filename}') # 如果安装了pandas则调用 # save_as_excel(all_notes_data)

你可以根据需求选择一种或多种格式进行导出。我个人最喜欢的是 Markdown + JSON 组合:Markdown 用于日常翻阅,JSON 作为原始数据备份,方便未来进行其他处理。

5. 进阶优化与实战避坑指南

一个能跑通的脚本只是开始,一个健壮、好用、可持续的脚本才是目标。以下是基于我多次实战后总结的进阶技巧和常见问题。

5.1 处理分页与大量数据

上面的示例假设笔记 API 一次返回所有数据。但有些接口(特别是对于笔记非常多的书)可能会分页。你需要检查 API 响应中是否有hasMorenexttotal这样的字段,以及是否可以通过pagelimit参数来翻页。如果遇到分页,就需要写一个循环,直到hasMoreFalse为止。

def get_book_notes_with_pagination(headers, book_id): """处理可能存在的分页""" all_notes = [] page = 1 limit = 100 # 假设每页100条 has_more = True while has_more: url = f'https://i.weread.qq.com/book/bookmarklist?bookId={book_id}&page={page}&limit={limit}' # ... 发送请求并解析 ... # 假设响应中有 `hasMore` 和 `items` 字段 # data = response.json() # all_notes.extend(data.get('items', [])) # has_more = data.get('hasMore', False) # page += 1 # time.sleep(0.5) # 页间延迟 return all_notes

5.2 异常处理与重试机制

网络请求充满不确定性。必须添加完善的异常处理和重试逻辑。

import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session_with_retry(): """创建一个带重试机制的requests Session""" session = requests.Session() retries = Retry(total=3, # 总重试次数 backoff_factor=0.5, # 重试间隔因子 status_forcelist=[500, 502, 503, 504]) # 遇到这些状态码才重试 session.mount('https://', HTTPAdapter(max_retries=retries)) return session # 使用 session 代替 requests.get session = create_session_with_retry() response = session.get(url, headers=headers, timeout=15)

5.3 Cookie 失效的自动检测与提醒

在脚本开始或请求失败时,可以添加一个简单的检测。

def check_cookie_validity(headers): """检查Cookie是否有效""" test_url = 'https://i.weread.qq.com/user/books' try: response = requests.get(test_url, headers=headers, timeout=5) if response.status_code == 200: data = response.json() # 有效响应通常包含用户信息或书籍列表 if 'books' in data or 'user' in data: return True return False except: return False if not check_cookie_validity(headers): print('警告:Cookie可能已失效,请重新获取!') # 可以在这里尝试自动打开浏览器指引用户,或者直接退出 exit(1)

5.4 增量导出与数据去重

如果你定期运行这个脚本,不希望每次都从头导出所有数据,可以实现增量导出。思路是:每次导出后,记录下每本书最后一条笔记的createTime。下次运行时,只获取这个时间点之后的新笔记。这需要你将之前导出的数据(如JSON)也读入内存进行比对。

5.5 关于“爬虫”与“API”的伦理思考

最后,必须再次强调技术伦理。我们调用的是微信读书提供给其官方客户端使用的 API。虽然我们模拟了客户端的行为,但这依然处于一个灰色地带。因此,务必做到:

  1. 仅用于个人:导出的数据仅供自己备份、整理、学习使用。
  2. 控制频率:在代码中设置合理的延迟(time.sleep),不要以极快的频率请求,避免对服务器造成不必要的负担。
  3. 尊重版权:导出的书籍内容(划线文本)是用于个人复习,切勿公开传播或用于商业用途。
  4. 关注变化:平台 API 随时可能变更。如果某天脚本突然不能用了,请首先检查 Cookie 是否有效,然后重新抓包分析 API 结构是否发生了变化。

这个项目不仅仅是一段爬虫代码,它更是一个桥梁,连接了你与那些散落在云端的思想碎片。通过技术手段将它们汇聚到本地,你才能真正开始消化、连接、构建属于自己的知识体系。希望这份详细的指南和代码,能帮你踏出建立个人数字图书馆的第一步。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询