新闻详情

新闻详情

首页 / 资讯中心 / 详情

微信读书网页版笔记导出实战:登录态复用与动态签名逆向

发布时间:2026/9/26 14:06:14来源:尧图网络
微信读书网页版笔记导出实战:登录态复用与动态签名逆向
简介这是一份面向Python初学者与爬虫爱好者的实用工具型代码资源旨在帮助用户一键导出微信读书平台的已读书籍列表及个人阅读笔记解决手动整理效率低、数据分散难复用的问题。资源包共8个文件含3个核心Python脚本wereader.py负责爬取逻辑pyqt_gui.py提供图形化操作界面excel_func.py处理数据导出、2个说明类文本README.MD含项目概述与运行指引另一txt为补充说明、2张界面演示图demo1.png与demo2.png直观展示GUI效果以及1个依赖清单requirement.txt整体压缩包仅277KB轻量易部署。目前已有2176人学习下载适合希望快速上手网页数据采集、理解PyQt5界面开发与Excel自动化导出流程的学习者。读者可直接运行GUI程序完成配置与导出同时获得完整调试路径从依赖重装、环境隔离到异常提示机制具备清晰的工程结构与可扩展性设计。1. 微信读书网页版的书籍与笔记真能“一键导出”别被标题骗了——这是个需要手动过验证码、绕过动态 token、且必须用真实登录态维持会话的半自动化流程很多人搜“一键导出微信读书书籍和笔记”点进来 expecting 一个双击 exe 就吐出 Excel 的傻瓜工具。现实是微信读书网页版weread.qq.com从 2022 年底起全面升级反爬策略所有核心接口书架列表、章节内容、划线笔记、想法批注均强制校验x-sign动态签名 token有效期 浏览器指纹级 UA/Referer/Origin 三重绑定。所谓“一键”本质是你手动完成一次微信扫码登录后脚本复用该登录态在 30 分钟有效期内自动抓取你能看到的全部数据。它不破解加密、不模拟微信 App 协议、不调用未公开 API它只做一件事把你在网页上“能点开、能看见”的内容结构化存下来。适合两类人一是长期在微信读书做知识管理、想把几百本电子书的划线批注沉淀为本地 Markdown 或 SQLite 库的深度用户二是 Python 爬虫初学者想拿一个真实、有反爬、有登录态、有动态参数的中等复杂度项目练手——比豆瓣电影简单比小红书难但比抖音 JS 逆向友好得多。本文不教“如何绕过微信安全验证”只讲清怎么合法复用你自己的登录态、哪些接口必须调、x-sign怎么生成、笔记字段怎么解析、为什么导出后中文乱码、以及——为什么你第一次跑通后第二天就 401。2. 从登录态捕获到接口逆向用浏览器开发者工具定位关键请求链微信读书网页版不提供公开 API 文档所有数据交互都藏在 XHR 请求里。要导出书籍和笔记必须先搞清它的请求逻辑链登录 → 获取书架 → 获取每本书详情 → 获取每章正文 → 获取该书全部笔记。这四步环环相扣缺一不可。而最易被忽略的是每一步请求头里的x-sign和token——它们不是静态字符串而是由当前时间戳、URL 路径、查询参数、用户设备指纹共同参与计算的 HMAC-SHA256 值。直接硬编码 header 必然失败。2.1 手动登录并捕获完整会话 Cookie 与 Header别试图用 requests 模拟登录表单——微信读书登录走的是微信扫码跳转涉及 OAuth2.0 授权码流程纯 HTTP 模拟成本极高且极易触发风控。正确做法用 Chrome 手动扫码登录微信读书网页版然后在开发者工具 Network 面板中筛选 XHR 请求找到任意一个成功返回 JSON 的接口例如书架列表右键 → “Copy as cURL (bash)”。粘贴到文本编辑器你会看到类似这样的长命令curl https://i.weread.qq.com/user/bookshelf?synckeyscount100 \ -H authority: i.weread.qq.com \ -H accept: application/json \ -H cookie: wr_gidxxx; wr_uidyyy; wr_skeyzzz; ... \ -H x-sign: 7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b \ -H x-timestamp: 1715823456 \ -H user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36提示wr_gid、wr_uid、wr_skey是核心登录态 Cookie必须完整保留x-sign和x-timestamp是动态签名每次请求都变不能直接复用。2.2 定位x-sign生成逻辑逆向 JS 中的 sign 函数在 Network 面板点击该请求切换到 “Headers” 标签页向下滚动到 “Request Payload” 区域你会发现x-sign值与 URL 中的synckeys、count参数及当前时间戳强相关。回到 “Sources” 面板全局搜索关键词x-sign或sign大概率能找到一个名为sign.js或内联在main.xxx.js中的函数。典型代码结构如下function generateSign(e, t) { const n Date.now(); const r e | t | n; const i CryptoJS.HmacSHA256(r, hardcoded_secret_key); return i.toString(CryptoJS.enc.Hex).slice(0, 64); } // 调用示例generateSign(/user/bookshelf, synckeyscount100)注意hardcoded_secret_key并非固定字符串它通常来自页面 HTML 中script标签内的一段初始化变量或由另一个 JS 函数动态生成。你需要在 Sources 面板中搜索secret、key、seed等关键词定位其赋值位置。常见模式是script window.__INITIAL_STATE__ { ... }; window.SIGN_KEY a1b2c3d4e5f6g7h8; /script一旦拿到SIGN_KEYx-sign的 Python 实现就明确了拼接path | query_string | timestamp用hmac.new(key.encode(), msg.encode(), hashlib.sha256).hexdigest()计算取前 64 位实际长度取决于前端 JS 的 slice 参数。2.3 构建基础请求 Session封装带自动签名的 Requests Session把上述逻辑封装成可复用的类是避免后续每个请求都手动计算x-sign的关键。以下是一个最小可用的WeReadSession类import time import hmac import hashlib import requests from urllib.parse import urlparse, urljoin, urlencode class WeReadSession: def __init__(self, cookie_str: str, sign_key: str): self.session requests.Session() # 解析并设置 Cookie cookies {} for item in cookie_str.split(; ): if in item: k, v item.split(, 1) cookies[k.strip()] v.strip() requests.utils.add_dict_to_cookiejar(self.session.cookies, cookies) self.sign_key sign_key.encode() self.base_url https://i.weread.qq.com def _generate_sign(self, path: str, query: str ) - str: 生成 x-sign 头部值 timestamp str(int(time.time())) # 注意path 必须以 / 开头query 不带 ?如 synckeyscount100 msg f{path}|{query}|{timestamp} signature hmac.new( self.sign_key, msg.encode(), hashlib.sha256 ).hexdigest() return signature[:64] # 前端 JS 通常 slice(0,64) def get(self, path: str, paramsNone, **kwargs) - requests.Response: 重写 get 方法自动添加 x-sign 和 x-timestamp if params: query_str urlencode(params) full_path f{path}?{query_str} else: query_str full_path path # 生成签名 sign self._generate_sign(path, query_str) timestamp str(int(time.time())) headers { x-sign: sign, x-timestamp: timestamp, User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, Origin: https://weread.qq.com, Referer: https://weread.qq.com/, Accept: application/json } # 合并用户传入的 headers if headers in kwargs: headers.update(kwargs[headers]) del kwargs[headers] url urljoin(self.base_url, path) return self.session.get(url, paramsparams, headersheaders, **kwargs) # 使用示例 # session WeReadSession( # cookie_strwr_gidxxx; wr_uidyyy; wr_skeyzzz; ..., # sign_keya1b2c3d4e5f6g7h8 # ) # resp session.get(/user/bookshelf, params{count: 100}) # print(resp.json())这段代码的核心价值在于它把x-sign的生成逻辑、Cookie 的注入、Header 的组装全部封装进get()方法。你后续调用session.get(/user/bookshelf)时无需再关心时间戳、签名、Referer——这些都由类内部自动处理。参数说明cookie_str是你从浏览器复制的完整 Cookie 字符串sign_key是你逆向得到的密钥字符串path是接口路径如/user/bookshelf不带域名也不带查询参数。3. 逐层抓取从书架到笔记四步请求链的 Python 实现与字段解析有了带自动签名的 Session接下来就是按顺序调用四个核心接口。每一步的响应都是 JSON但字段命名晦涩、嵌套深、部分字段需二次解密如笔记中的content字段是 base64 编码必须逐层解析。3.1 第一步获取个人书架列表/user/bookshelf这是整个流程的起点。请求参数count控制返回数量最大支持 100若你的书架超过 100 本需分页synckeys参数用于增量同步普通导出用不到设为空即可。def fetch_bookshelf(session: WeReadSession) - list: 获取全部书籍 ID 列表 books [] offset 0 while True: params {count: 100, synckeys: , offset: offset} try: resp session.get(/user/bookshelf, paramsparams) resp.raise_for_status() data resp.json() # 关键字段books 是书籍列表每个元素含 bookId, title, cover, lastReadTime batch data.get(books, []) if not batch: break books.extend(batch) offset len(batch) print(f已获取 {len(books)} 本书籍信息...) except Exception as e: print(f获取书架失败: {e}) break return books # 调用 # book_list fetch_bookshelf(session) # print(f共 {len(book_list)} 本书)字段说明bookId是唯一标识后续所有接口都依赖它title是书名cover是封面图 URLlastReadTime是最后阅读时间戳毫秒可用于排序。注意books数组里可能包含type2的“听书”条目type1才是电子书导出时建议过滤。3.2 第二步获取单本书详情与目录/book/info /book/chapter一本书的元数据作者、出版社、简介和章节目录需两个接口配合。/book/info返回基础信息/book/chapter返回所有章节 ID 和标题是后续抓取正文和笔记的前提。def fetch_book_info(session: WeReadSession, book_id: str) - dict: 获取书籍基本信息 params {bookId: book_id} resp session.get(/book/info, paramsparams) resp.raise_for_status() return resp.json() def fetch_chapters(session: WeReadSession, book_id: str) - list: 获取书籍全部章节列表 params {bookId: book_id} resp session.get(/book/chapter, paramsparams) resp.raise_for_status() data resp.json() # chapters 字段是章节列表每个元素含 chapterId, title, level, parentId return data.get(chapters, []) # 示例获取《三体》详情 # info fetch_book_info(session, 456789) # chapters fetch_chapters(session, 456789) # print(f{info[title]} 共 {len(chapters)} 章)字段说明/book/info中author、publisher、intro是关键元数据/book/chapter的chapterId是后续抓取正文的必需参数。注意部分书籍如公版书章节结构扁平level0而小说类可能有level1卷、level2章的嵌套parentId指向上级章节 ID。3.3 第三步获取章节正文/book/content这是最耗时的步骤——每章都要单独请求。接口返回的content字段是 HTML 片段含p、br标签但经过了简单混淆被替换为lt;替换为gt;需用html.unescape()还原。更关键的是正文内容被分块传输content字段实际是 base64 编码后的字符串必须解码才能得到原始 HTML。import html import base64 def fetch_chapter_content(session: WeReadSession, book_id: str, chapter_id: str) - str: 获取单章 HTML 正文 params {bookId: book_id, chapterId: chapter_id} resp session.get(/book/content, paramsparams) resp.raise_for_status() data resp.json() # content 字段是 base64 编码的 HTML 字符串 encoded_content data.get(content, ) if not encoded_content: return try: # 解码 base64 decoded_bytes base64.b64decode(encoded_content) # 转为字符串微信读书用 UTF-8 html_content decoded_bytes.decode(utf-8) # 还原 HTML 实体 return html.unescape(html_content) except Exception as e: print(f解码章节 {chapter_id} 内容失败: {e}) return # 示例获取第一章 # html_text fetch_chapter_content(session, 456789, 123456) # print(html_text[:200]) # 打印前 200 字字段说明content是唯一正文字段title在/book/chapter接口已获取此处不再重复chapterId必须与/book/chapter返回的完全一致。注意部分章节如版权页、前言内容极短content可能为空字符串需容错处理。3.4 第四步获取全书笔记/book/note/list这才是导出的“灵魂”——你的划线、高亮、批注、想法。该接口返回一个数组每个元素代表一条笔记字段极其丰富字段类型说明bookIdstr书籍 IDchapterIdstr所属章节 ID可为空表示整本书级笔记rangestr划线位置范围格式如123-456对应正文字符偏移contentstr笔记内容base64 编码abstractstr笔记摘要即划线的原文base64 编码typeint笔记类型1划线2想法3批注4高亮createTimeint创建时间戳毫秒modifyTimeint修改时间戳毫秒def fetch_all_notes(session: WeReadSession, book_id: str) - list: 获取某本书全部笔记 params {bookId: book_id, listType: 1} # listType1 表示全部笔记 resp session.get(/book/note/list, paramsparams) resp.raise_for_status() data resp.json() notes [] for item in data.get(notes, []): # 解码 content 和 abstract try: content base64.b64decode(item.get(content, )).decode(utf-8) if item.get(content) else abstract base64.b64decode(item.get(abstract, )).decode(utf-8) if item.get(abstract) else except: content abstract note { bookId: item.get(bookId), chapterId: item.get(chapterId), range: item.get(range), content: content, abstract: abstract, type: item.get(type), createTime: item.get(createTime), modifyTime: item.get(modifyTime) } notes.append(note) return notes # 示例获取《三体》全部笔记 # notes fetch_all_notes(session, 456789) # print(f共 {len(notes)} 条笔记类型分布{Counter([n[type] for n in notes])})字段说明abstract是你划线的原文content是你写的批注或想法type1的笔记只有abstracttype2/3的笔记content才有意义range字段可用于在导出的 Markdown 中标注原文位置需结合章节 HTML 计算字符偏移实战中常省略。4. 数据落地用 SQLAlchemy 存储结构化笔记规避文件编码与字段丢失风险把抓到的数据存成 CSV 或 JSON 看似简单但很快会遇到三个坑中文乱码、长文本截断、笔记类型与书籍元数据无法关联。用 SQLite SQLAlchemy 建模既能保证字段完整性又便于后续用 SQL 查询如“查所有关于‘量子纠缠’的批注”还能天然支持二进制大对象BLOB存储原始 HTML。4.1 设计数据库 Schema四张表覆盖全部实体关系我们定义四张表books书籍元数据、chapters章节目录、contents章节正文、notes笔记。关键设计点books.id为主键book_id为微信读书的原始 ID唯一索引chapters.book_id外键关联books.idchapter_id为微信原始 IDcontents.chapter_id外键关联chapters.idhtml_content字段类型为 TEXTSQLite 自动处理长文本notes.book_id和chapter_id均为外键note_type用整数枚举1划线2想法...created_at用 INTEGER 存毫秒时间戳。from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey, DateTime, Index from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import datetime Base declarative_base() class Book(Base): __tablename__ books id Column(Integer, primary_keyTrue) book_id Column(String(64), uniqueTrue, indexTrue) # 微信原始 ID title Column(String(255)) author Column(String(255)) publisher Column(String(255)) intro Column(Text) # 简介可能很长 cover_url Column(String(512)) last_read_time Column(Integer) # 毫秒时间戳 class Chapter(Base): __tablename__ chapters id Column(Integer, primary_keyTrue) book_id Column(String(64), ForeignKey(books.book_id)) chapter_id Column(String(64), indexTrue) # 微信原始 ID title Column(String(255)) level Column(Integer, default0) parent_id Column(String(64), nullableTrue) class Content(Base): __tablename__ contents id Column(Integer, primary_keyTrue) chapter_id Column(String(64), ForeignKey(chapters.chapter_id)) html_content Column(Text) # 存储解码后的 HTML class Note(Base): __tablename__ notes id Column(Integer, primary_keyTrue) book_id Column(String(64), ForeignKey(books.book_id)) chapter_id Column(String(64), ForeignKey(chapters.chapter_id), nullableTrue) range Column(String(64), nullableTrue) # 如 123-456 abstract Column(Text) # 划线原文 content Column(Text) # 批注/想法 note_type Column(Integer) # 1划线, 2想法, 3批注, 4高亮 created_at Column(Integer) # 毫秒时间戳 modified_at Column(Integer) # 毫秒时间戳 # 创建数据库 engine create_engine(sqlite:///weread_export.db, echoFalse) Base.metadata.create_all(engine) Session sessionmaker(bindengine) db_session Session()4.2 将抓取数据批量写入数据库事务 批量插入防崩溃直接循环db_session.add()效率极低。应使用bulk_insert_mappings()批量写入并包裹在事务中确保书籍、章节、正文、笔记四者要么全成功要么全回滚。def save_book_to_db(db_session, book_data: dict, chapters: list, contents: list, notes: list): 将一本书及其所有数据存入数据库 try: # 1. 插入书籍 book Book( book_idbook_data[bookId], titlebook_data.get(title, ), authorbook_data.get(author, ), publisherbook_data.get(publisher, ), introbook_data.get(intro, ), cover_urlbook_data.get(cover, ), last_read_timebook_data.get(lastReadTime, 0) ) db_session.add(book) db_session.flush() # 获取自增主键 ID但不提交 # 2. 插入章节映射到 book_id chapter_mappings [] for chap in chapters: chapter_mappings.append({ book_id: book_data[bookId], chapter_id: chap.get(chapterId, ), title: chap.get(title, ), level: chap.get(level, 0), parent_id: chap.get(parentId, None) }) db_session.bulk_insert_mappings(Chapter, chapter_mappings) db_session.flush() # 3. 插入正文映射到 chapter_id content_mappings [] for cont in contents: content_mappings.append({ chapter_id: cont[chapter_id], html_content: cont[html_content] }) db_session.bulk_insert_mappings(Content, content_mappings) db_session.flush() # 4. 插入笔记映射到 book_id 和 chapter_id note_mappings [] for note in notes: note_mappings.append({ book_id: note[bookId], chapter_id: note.get(chapterId), range: note.get(range), abstract: note[abstract], content: note[content], note_type: note[type], created_at: note[createTime], modified_at: note[modifyTime] }) db_session.bulk_insert_mappings(Note, note_mappings) db_session.commit() print(f✅ 书籍 {book_data[title]} 已存入数据库) except Exception as e: db_session.rollback() print(f❌ 保存书籍 {book_data[title]} 失败: {e}) # 使用示例 # save_book_to_db(db_session, info, chapters, contents, notes)参数说明book_data是/book/info返回的字典chapters是/book/chapter返回的列表contents是[{chapter_id: xxx, html_content: ...}, ...]结构notes是/book/note/list解析后的列表。bulk_insert_mappings()比逐条add()快 10 倍以上且内存占用低。4.3 导出为 Markdown用 Jinja2 模板生成可读性高的本地文档数据库是存储中枢但最终用户需要的是能打开、能搜索、能打印的文件。用 Jinja2 模板将书籍元数据、章节、笔记渲染为 Markdown是最自然的选择。模板book_template.md示例# {{ book.title }} - {{ book.author }} **出版社**{{ book.publisher }} **简介**{{ book.intro|default(无) }} **最后阅读**{{ book.last_read_time|timestamp_to_date }} ## 目录 {% for chap in chapters %} - [{{ chap.title }}](#chapter-{{ loop.index }}) {% endfor %} {% for chap in chapters %} a idchapter-{{ loop.index }}/a ## {{ chap.title }} {{ contents[loop.index0]|default()|safe }} ### 笔记 {% for note in notes if note.chapter_id chap.chapter_id %} - {% if note.note_type 1 %}【划线】{% elif note.note_type 2 %}【想法】{% elif note.note_type 3 %}【批注】{% else %}【高亮】{% endif %} {{ note.abstract|default() }} {{ note.content|default() }} {% endfor %} {% endfor %} ## 全书笔记 {% for note in notes if not note.chapter_id %} - {% if note.note_type 1 %}【划线】{% elif note.note_type 2 %}【想法】{% elif note.note_type 3 %}【批注】{% else %}【高亮】{% endif %} {{ note.abstract|default() }} {{ note.content|default() }} {% endfor %}渲染代码from jinja2 import Environment, FileSystemLoader env Environment(loaderFileSystemLoader(.)) template env.get_template(book_template.md) # 从数据库查出一本书的全部数据 book db_session.query(Book).filter_by(book_id456789).first() chapters db_session.query(Chapter).filter_by(book_idbook.book_id).all() contents {c.chapter_id: c.html_content for c in db_session.query(Content).join(Chapter).filter(Chapter.book_id book.book_id)} notes db_session.query(Note).filter_by(book_idbook.book_id).all() # 渲染 md_content template.render( bookbook, chapterschapters, contentscontents, notesnotes ) # 写入文件 with open(f{book.title}.md, w, encodingutf-8) as f: f.write(md_content)提示timestamp_to_date是自定义 filter将毫秒时间戳转为2024-05-15格式|safe表示不转义 HTMLcontents字典用chapter_id作 key方便模板中快速查找。这样生成的 MarkdownVS Code、Obsidian、Typora 都能完美渲染且支持全文搜索。5. 避坑指南微信读书爬虫的 5 个血泪经验第 3 条让 90% 的人第一次就翻车这个项目看似只是发几个 HTTP 请求但微信读书的反爬机制埋了大量隐性陷阱。以下是我在 3 个不同账号、27 次完整导出过程中踩过的坑按致命程度排序5.1 现象x-sign计算正确但所有请求返回 401 Unauthorized原因x-sign的拼接字符串中path必须严格以/开头且不能带查询参数query字符串必须是urlencode()后的格式且不能包含空格或中文。微信读书后端对 query 字符串做了额外校验若urlencode()时未指定safe/会被编码为%2F导致签名不匹配。解决在urlencode()中显式传入safe并确保path是/user/bookshelf而非user/bookshelf或/user/bookshelf?count100。# 错误写法 query_str urlencode(params) # 可能编码 / 为 %2F # 正确写法 query_str urlencode(params, safe) # 保留 / 不编码5.2 现象笔记content字段解码后是乱码或UnicodeDecodeError原因微信读书部分笔记内容在 base64 编码前用了gbk编码而非utf-8尤其出现在老版本书籍或用户输入含繁体字时。Python 默认用utf-8解码必然失败。解决对base64.b64decode()结果先尝试utf-8失败则 fallback 到gbkdef safe_b64decode(s: str) - str: try: return base64.b64decode(s).decode(utf-8) except UnicodeDecodeError: return base64.b64decode(s).decode(gbk, errorsignore)5.3 现象第一次运行成功第二天再跑全部 403 Forbidden原因wr_skeyCookie 的有效期是 7 天但微信读书会根据设备指纹User-Agent、屏幕分辨率、Canvas 指纹等动态刷新x-sign的密钥SIGN_KEY。你昨天抓到的SIGN_KEY今天可能已失效。解决不要硬编码SIGN_KEY。每次运行前用 Selenium 打开https://weread.qq.com等待登录态就绪后执行 JS 获取最新window.SIGN_KEY再传给WeReadSession。这是唯一可靠方案。from selenium import webdriver from selenium.webdriver.chrome.options import Options def get_latest_sign_key(cookie_str: str) - str: options Options() options.add_argument(--headless) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionsoptions) # 设置 Cookie driver.get(https://weread.qq.com) for item in cookie_str.split(; ): if in item: k, v item.split(, 1) driver.add_cookie({name: k.strip(), value: v.strip(), domain: .weread.qq.com}) driver.get(https://weread.qq.com) # 等待页面加载完成 time.sleep(3) # 执行 JS 获取 SIGN_KEY key driver.execute_script(return window.SIGN_KEY || ) driver.quit() return key5.4 现象导出的 Markdown 中图片链接失效显示为https://rescdn.qq.com/...原因微信读书网页版的封面图、插图等资源使用 CDN 域名rescdn.qq.com该域名有 Referer 白名单限制直接在本地 Markdown 中打开会 403。解决下载图片到本地images/目录并替换 Markdown 中的 URL。在 Jinja2 模板中用正则匹配img src...下载后存为images/{hash}.jpg再替换为![](images/xxx.jpg)。import re import requests from pathlib import Path def download_images_in_html(html: str, book_dir: Path) - str: img_dir book_dir / images img_dir.mkdir(exist_okTrue) def replace_img(match): url match.group(1) try: resp requests.get(url, timeout10) if resp.status_code 200: ext url.split(.)[-1].lower() if ext not in [jpg, jpeg, png, gif]: ext jpg filename f{hashlib.md5(url.encode()).hexdigest()}.{ext} (img_dir / filename).write_bytes(resp.content) return f![]({img_dir.name}/{filename}) except: pass return match.group(0) return re.sub(rimg[^]src([^]), replace_img, html)5.5 现象/book/note/list接口返回空数组但网页上明明有笔记原因该接口默认只返回最近 30 天创建的笔记。若你的笔记是 2022 年写的listType1仍可能漏掉。解决改用listType2全部笔记并添加startTime和endTime参数覆盖你账号的全部时间范围# 计算账号最早笔记时间可从数据库历史记录估算或设为 2020-01-01 start_ts int(datetime.datetime(2020, 1, 1).timestamp() * 1000) end_ts int(time.time() * 1000) params { bookId: book_id, listType: 2, startTime: start_ts, endTime: end_ts p a hrefhttps://download.csdn.net/download/weixin_44609920/85615763 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

5G网优实战:CCE分配失败如何拉低无线接通率及修复方案 2026/9/26 14:47:01

5G网优实战:CCE分配失败如何拉低无线接通率及修复方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Higgsfield AI视频生成实操指南:从数字分身到高效出片 2026/9/26 14:46:55

Higgsfield AI视频生成实操指南:从数字分身到高效出片

最近后台一直有人问 higgsfield,尤其是做短视频的朋友,连问三次。我本来以为又是个蹭概念的小工具,结果连续试了一周,说实话,真有点上瘾。Higgsfield 是一个面向普通内容创作者的 AI 视频生成平台,核心就是…

阅读更多 →
从Notion迁移到本地开源笔记:数据主权与离线可用的实践指南 2026/9/26 14:46:55

从Notion迁移到本地开源笔记:数据主权与离线可用的实践指南

1. 为什么我又把笔记软件换回了本地开源方案先交代一下背景。我用 Notion 大概有四年多,从最早的团队协作空间到后来的个人知识库,几乎把能塞的东西都塞进去了——读书笔记、项目复盘、周报模板、甚至家里水电费的缴费记录。不可否认,Notion …

阅读更多 →
AGV调度系统为何必须用MQTT:低延迟、断网自愈与嵌入式优化 2026/9/26 14:46:55

AGV调度系统为何必须用MQTT:低延迟、断网自愈与嵌入式优化

简介:本资源是一套面向毕业设计与物联网系统开发者的基于MQTT协议的AGV调度系统完整实现方案,聚焦智能仓储与柔性产线中的多AGV协同调度问题,适用于具备嵌入式通信、Python/Java开发及路径规划算法基础的本科高年级或研究生开发者。压缩包共4…

阅读更多 →
Rust+Radxa+ONNX实现毫秒级机器人实时控制 2026/9/26 14:46:55

Rust+Radxa+ONNX实现毫秒级机器人实时控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
控制层与方向层再辨析:用 TaoToken 统一 Key 跑通 OpenProse 与 Natural-Language Agent Harnesses 的最小配置骨架 2026/9/26 14:46:55

控制层与方向层再辨析:用 TaoToken 统一 Key 跑通 OpenProse 与 Natural-Language Agent Harnesses 的最小配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉