逆向解析微信读书API:从抓包到实现个人数据同步与自动化

📅 2026/8/5 9:00:16
逆向解析微信读书API:从抓包到实现个人数据同步与自动化
1. 项目缘起与核心价值最近在折腾墨水屏设备想在上面实现一个沉浸式的阅读体验自然就想到了微信读书。但官方App在墨水屏上的适配尤其是那个1.5.2版本功能上总有些掣肘。比如我想把书架同步到自己的服务器上做个备份或者想写个小工具自动整理我的阅读笔记和划线发现官方并没有提供现成的开放接口。这让我萌生了一个想法能不能通过技术手段逆向分析微信读书App的网络请求整理出一套可用的“非官方”API从而解锁更多个性化的玩法这不仅仅是“抓个包”那么简单。它涉及到对现代移动应用通信协议如HTTP/2、Protobuf的理解对授权认证机制如Token、Cookie的维护以及对服务端反爬策略的应对。更重要的是通过分析这些API我们能一窥一个千万级日活产品背后的核心数据模型和业务逻辑设计比如书籍信息如何组织、阅读进度如何同步、社交互动如何实现。这对于开发者理解一个复杂C端应用的数据流转有着极高的学习价值。无论你是想开发一个第三方阅读客户端还是想构建个人知识管理系统来自动化处理微信读书的数据亦或是单纯对大型应用架构感兴趣这个分析过程都是一次绝佳的实战。2. 前期准备与环境搭建工欲善其事必先利其器。分析移动端API首要任务是能够捕获并解密其网络流量。由于微信读书App普遍使用了HTTPS加密我们需要一些特定的工具来搭建中间人攻击MitM环境。2.1 核心工具选型与配置我的分析环境主要基于以下工具链它们各自扮演着关键角色抓包代理工具Charles Proxy我选择Charles而非Fiddler主要是因为Charles对HTTP/2和JSON格式化的支持更友好界面也更直观。关键在于安装Charles的根证书并让移动设备信任它。在手机上配置Wi-Fi代理将服务器指向运行Charles的电脑IP端口通常为8888。这样手机的所有HTTP/HTTPS流量都会先经过Charles。注意很多App包括微信读书会启用证书绑定SSL Pinning即App只信任自己内置的证书拒绝系统证书。这会直接导致Charles无法解密HTTPS流量。为此我们需要一个额外的步骤来绕过它。SSL Pinning绕过工具FridaFrida是一个动态代码插桩框架它可以在App运行时注入脚本修改其行为。我们通过Frida脚本可以Hook住App内部校验证书的代码逻辑使其接受Charles的证书。通常需要一台已Root的Android设备或越狱的iOS设备来运行Frida Server。对于微信读书社区已有一些现成的反Pinning脚本可供参考或修改使用。辅助分析与调试工具Postman / Insomnia用于将捕获到的API请求导入并重放、修改参数进行测试。Chrome开发者工具对于某些WebView或H5页面发起的请求可以直接用电脑浏览器打开并调试。Wireshark作为底层抓包补充当遇到非HTTP协议如自定义TCP时使用但本次分析中未涉及。2.2 设备与账号准备测试设备建议使用一台备用安卓手机并获取Root权限。这为安装Frida、使用Xposed模块等高级操作提供了便利。如果没有Root设备可以尝试使用安卓模拟器如夜神、MuMu部分模拟器支持Root模式但需注意模拟器环境可能与真机有差异可能触发风控。测试账号强烈建议使用一个全新的、不重要的微信小号来登录微信读书进行测试。因为频繁的异常请求、登录行为可能导致账号被临时限制功能使用主账号存在风险。配置好Charles和Frida后打开微信读书App进行登录、浏览书架、阅读、划线等操作。此时Charles的会话列表中应该会逐步出现大量来自weread.qq.com、i.weread.qq.com等域名的HTTPS请求。如果请求内容显示为乱码或“Unknown”说明SSL Pinning绕过成功但解密可能遇到其他加密如RequestBody使用Protobuf这是我们下一步要攻克的重点。3. 核心接口分析与鉴权机制成功捕获流量后面对满屏的请求我们需要像侦探一样从中梳理出核心的API脉络。微信读书的API设计遵循了典型的RESTful风格但夹杂着一些GraphQL的影子用于复杂数据查询。3.1 登录与会话维持一切始于登录。微信读书的登录严格依赖微信的OAuth2.0授权。登录流程App启动后会向微信发起授权请求。授权成功后会获得一个code。App将这个code提交给微信读书的后端例如https://weread.qq.com/login后端与微信服务器验证后会返回一个关键的登录凭证。核心凭证wr_skey与wr_rt分析请求头你会发现两个至关重要的Cookiewr_skey和wr_rt名称可能随版本迭代变化但功能类似。wr_skey相当于会话Token是后续绝大多数API请求的身份凭据。它被设置在Cookie头中也有时出现在自定义的X-Requested-With或Authorization头里需要仔细比对。wr_rt刷新Token。当wr_skey过期后可以使用wr_rt调用特定的刷新接口来获取新的wr_skey而无需用户重新登录。请求签名为了防止重放攻击重要的写操作如添加笔记、发表想法API往往还带有签名机制。签名算法通常是将请求参数、时间戳和一个固定盐值Salt按特定顺序拼接后进行MD5或SHA256运算。这个签名值会以x-sign或类似名称的请求头发送。服务器端会以同样的算法验签不一致则拒绝请求。实操心得在Postman中测试API时首要任务就是模拟这个登录流程或者更简单地直接从Charles捕获的某个成功请求中将完整的Cookie请求头复制出来作为环境变量。这样在测试其他API时直接引用该环境变量即可模拟已登录状态。务必注意Cookie的过期时间。3.2 关键业务接口解析以下是我梳理出的部分核心接口及其用途。请注意接口地址和参数可能随版本更新而变化此处仅为示例。功能模块疑似接口地址 (示例)请求方法核心参数/请求体返回数据说明书架与书籍https://i.weread.qq.com/user/booksGETtype0(0:书架, 1:已购)返回用户书架列表包含书籍ID、封面、最新阅读进度等。书籍详情https://i.weread.qq.com/book/infoGETbookIdxxx返回书籍的元数据标题、作者、出版社、简介、目录等。阅读进度https://i.weread.qq.com/read/readinfoGETbookIdxxx返回本书的阅读进度百分比、最后阅读章节、阅读时长等。获取章节内容https://i.weread.qq.com/book/chapterGETbookIdxxxchapterIdxn返回指定章节的纯文本或带格式的HTML内容。这是核心资源接口。笔记与划线https://i.weread.qq.com/web/book/bookmarklistGETbookIdxxx返回用户在该书中的所有划线bookmark和笔记note。添加想法/笔记https://i.weread.qq.com/review/addPOSTJSON Body:{bookId, chapterUid, range, content...}发布一条想法或笔记到指定段落。需要签名。发现与推荐https://weread.qq.com/web/book/list-in-booklistGETbooklistIdxxx获取某个书单的详情和书籍列表。接口分析要点数据格式响应体大多是JSON但书籍章节内容等可能采用更高效的二进制协议如Protobuf编码返回的Content-Type可能是application/protobuf。这时你需要找到对应的.proto协议定义文件可能通过反编译App获得或使用工具尝试反序列化。分页与参数列表接口通常有count和maxIdx参数来控制分页。maxIdx一般是上一批返回的最后一个项目的ID或索引。GraphQL端点部分复杂查询如同时获取书籍详情、进度、笔记可能会请求一个统一的GraphQL端点如/graphql请求体是一个包含查询语句和变量的JSON。这需要分析App源码中的查询语句。4. 数据获取与处理实战掌握了接口下一步就是如何稳定、高效地获取和处理数据。这里会遇到几个典型的挑战。4.1 应对反爬策略微信读书的后端不是毫无防备的频繁、规律的请求会很快被识别并拦截。频率限制这是最常见的。直接表现为返回429 Too Many Requests或自定义的错误码。解决方案在代码中为每个请求之间加入随机延迟例如time.sleep(random.uniform(1, 3))模拟人类阅读的不规律操作。对于批量获取书籍内容更要“慢工出细活”。User-Agent与设备指纹服务器会检查请求头中的User-Agent甚至通过一些JavaScript收集设备信息生成指纹。解决方案完全模拟官方App的请求头。从Charles中复制一套完整的Headers包括User-Agent如WeRead/6.3.2 (iPhone; iOS 15.4; Scale/3.00)、X-Requested-With、Referer等并在整个会话中保持一致。IP限制如果一个IP在短时间内产生过多请求可能会被暂时封禁。解决方案对于大规模数据采集需要考虑使用代理IP池。但对于个人用途控制请求频率通常已足够。参数校验与签名如前所述写操作接口有签名。你必须逆向出签名算法否则无法成功调用。这通常需要静态分析App的Java/KotlinAndroid或Objective-C/SwiftiOS代码。4.2 数据解析与存储示例假设我们的目标是同步所有已读图书的划线笔记到本地Markdown文件。下面是一个简化的Python流程框架import requests import json import time import random # 1. 配置会话从Charles复制 SESSION_COOKIES wr_skeyxxx; wr_rtxxx HEADERS { User-Agent: WeRead/6.3.2 ..., Cookie: SESSION_COOKIES, Referer: https://weread.qq.com/ } BASE_URL https://i.weread.qq.com # 2. 获取书架列表 def get_bookshelf(): resp requests.get(f{BASE_URL}/user/books, params{type: 0}, headersHEADERS) if resp.status_code 200: return resp.json().get(books, []) else: print(f获取书架失败: {resp.status_code}) return [] # 3. 遍历每本书获取笔记 def get_notes_for_book(book_id, book_title): # 先获取阅读进度确认已读 read_info requests.get(f{BASE_URL}/read/readinfo, params{bookId: book_id}, headersHEADERS).json() if read_info.get(readingProgress, 0) 0.01: # 进度小于1%视为未读 print(f跳过未读书籍: {book_title}) return [] # 获取笔记列表 notes_resp requests.get(f{BASE_URL}/web/book/bookmarklist, params{bookId: book_id}, headersHEADERS) notes notes_resp.json().get(updated, []) # 处理笔记可能还需要根据noteId获取详细内容 processed_notes [] for note in notes: # 解析划线内容、章节、时间等信息 # ... processed_notes.append(note) time.sleep(random.uniform(1, 2)) # 关键延迟避免请求过快 return processed_notes # 4. 主流程 def main(): all_books get_bookshelf() all_notes [] for book in all_books[:5]: # 先测试前5本 book_id book[bookId] book_title book[title] print(f处理书籍: {book_title}) notes get_notes_for_book(book_id, book_title) all_notes.extend(notes) # 5. 导出为Markdown with open(weread_notes.md, w, encodingutf-8) as f: for note in all_notes: f.write(f## {note[bookTitle]}\n) f.write(f {note[markText]}\n\n) f.write(f*我的笔记*: {note.get(content, )}\n\n) f.write(---\n\n) if __name__ __main__: main()这个脚本只是一个起点。在实际操作中你需要处理网络异常、登录态过期自动刷新、更复杂的数据结构等问题。5. 常见问题与排查实录在分析和使用的过程中我踩过不少坑。这里把一些典型问题和解决思路记录下来。5.1 抓包与解密问题问题Charles抓不到微信读书的包或者抓到全是Tunnel to ...:443。排查首先检查手机Wi-Fi代理设置是否正确。然后确认Charles的根证书是否已在手机系统证书库中安装并启用。如果仍不行大概率是SSL Pinning。解决必须使用Frida等工具绕过。确保Frida-server在手机上运行并且电脑端的frida-ps能列出手机进程。运行反pinning脚本后再尝试抓包。问题请求能抓到但响应体是乱码或二进制。排查查看响应的Content-Type头。如果是application/protobuf则需要Proto定义文件来解码。解决尝试搜索开源项目如GitHub上的weread-api相关项目看是否有人已经逆向出了.proto文件。或者使用protoc工具和猜测的结构进行试错解析。5.2 API请求错误问题请求返回400 Bad Request错误信息类似type must be in [enabled, disabled, auto]。排查这是一个典型的参数校验错误。错误信息很友好直接告诉你type字段的值只能是列表中的三个。解决检查你的请求体或查询参数确保type字段的值是enabled、disabled或auto之一。仔细比对从Charles捕获的正确请求的格式。问题返回401 Unauthorized或403 Forbidden。排查登录凭证Cookie/Token已过期或无效。解决重新登录获取新的Cookie。如果是自动化脚本需要实现一个检测到401错误后自动调用Token刷新接口或重新登录的逻辑。问题返回429 Too Many Requests。解决立即停止请求延长请求间隔时间。在代码中加入指数退避算法即每次遇到429后等待时间加倍。问题返回500 Internal Server Error或502 Bad Gateway。排查可能是你发送的请求参数格式完全错误导致服务器端处理异常也可能是服务端临时故障。解决首先核对参数格式。如果格式无误可能是服务端问题等待一段时间再重试。5.3 数据解析与使用限制问题获取到的书籍内容不全或者只有前几章。排查部分热门或版权严格的书籍接口可能只会返回试读部分。或者章节列表接口和内容接口需要特定的权限标识。解决检查返回数据中是否有hasFullContent之类的字段。对于付费书籍非付费用户通过API通常也无法获取全文这是正常的版权限制。问题想将笔记导出为PDF或EPUB。思路API本身不提供导出功能。你需要自己实现一个渲染引擎。步骤是1. 获取书籍元数据和目录。2. 按顺序获取所有章节内容。3. 获取所有笔记并定位到对应章节和段落。4. 使用像weasyprintHTML转PDF或ebooklib生成EPUB这样的库将文本、样式和笔记合并生成最终文件。这是一个工程量不小的项目。最后一点体会分析和使用非官方API始终行走在灰色地带。务必遵守以下原则仅用于个人学习、数据备份和效率提升不要进行大规模爬取、商业用途或干扰微信读书正常服务。你的请求行为应该尽可能地“像”一个真实的App用户尊重服务器的负载能力。技术是用来创造和便利的而不是破坏。通过这次分析我不仅实现了墨水屏上的优雅阅读方案更深刻理解了现代App前后端交互的复杂性这比单纯拿到数据更有价值。