# zreaderpy **Repository Path**: Rthree/zreaderpy ## Basic Information - **Project Name**: zreaderpy - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2026-10-10 - **Last Updated**: 2026-10-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ZReader —— 知乎阅读器(收藏 + AI 摘要/分类 + 公式 + 导出) 基于 **Python + PySide6(Qt) + QtWebEngine** 的桌面软件:内置浏览器浏览知乎, **自动向页面注入「收藏」按钮**,一键把回答 / 文章 / 想法的**完整正文**保存到本地收藏库; 收藏时调用 **DeepSeek / 豆包(火山方舟)(或任意 OpenAI 兼容接口)** 自动生成摘要、自动分类、补充标签; 支持**行内 / 段间公式**渲染,并可导出 **PDF / HTML / Markdown**。 ## 功能 - **内嵌浏览器**:基于 `QWebEngineView`,独立的持久化 Profile,登录状态、Cookie、 缓存都会被保存,下次打开无需重新登录。 - **SPA 友好的前进/后退**:知乎站内跳转几乎全是 `history.pushState`(不刷新页面), Qt 自带的 `back()/forward()` 对这类同文档历史条目不可靠。程序改为优先调用页面自身的 `history.back()/forward()`(与真实浏览器一致,SPA 路由能正确响应 `popstate`), 仅在 URL 确实未变化时才退回 Qt 的会话历史。 - **自动注入「收藏 / 下载」按钮**:页面加载后自动识别所有内容卡片 (`.ContentItem.AnswerItem` / `.ArticleItem` / `.PinItem` / `.List-item[data-zop]`), 在卡片操作栏插入「收藏」按钮;已收藏的内容显示为「已收藏」,再次点击可取消。 - 「收藏」旁还有一个「下载」按钮,点击即把该内容(完整正文 + 公式)保存为 独立 HTML 文件到下载目录,方便直接带走 / 分享。 - 使用 `MutationObserver` + 定时兜底扫描,兼容知乎的无限滚动、虚拟列表与前端路由; - 只在 `*.zhihu.com` 生效,不会污染其它网站; - **单页内容同样支持**:打开单篇文章(`zhuanlan.zhihu.com/p/xxx`)或单条回答 (`/question/xxx/answer/xxx`)时,右下角会出现悬浮「收藏 / 下载」按钮,走同样的完整正文提取。 - **完整正文提取**:收藏时先自动点掉「阅读全文」展开折叠内容,再用站内接口 (`/api/v4/answers|articles/{id}?include=content`)取回完整正文; 接口不可用时静默回退到页面 DOM,不会因为长回答只保存一半。 - **AI 摘要与自动分类**(DeepSeek / 豆包 / OpenAI 兼容): - 收藏后后台自动生成中文摘要(字数可配),并写入收藏库; - 根据**摘要 + 标签**从分类体系中挑选最合适的分类(只会输出候选分类之一,避免分类爆炸); - 同时提炼 3-6 个标签;摘要、分类、标签一次请求完成,省 token; - 可在「AI → AI 与公式设置…」里配置 Key / 模型 / 温度 / 提示词 / 分类体系,并一键测试连接; - 也可以对历史收藏批量补处理(`AI → 批量处理尚未摘要的内容…`)。 - **自动分类(规则 + AI 兜底,无需联网也能用)**: - 双路径设计:**规则路径**默认走(离线、零成本),按「标签 > 摘要 > 标题」打分, 标签恰好等于分类名时给最强加成;命中不到时再用 **AI 路径**兜底,避免消耗 token; - 内置 7 大类词表(技术 / 职场 / 学习 / 财经 / 健康 / 生活 / 社会),可一键在 「设置 → 自动分类」里换成自己的 JSON 词表,也可以用环境变量 `ZREADER_CATEGORY_TAXONOMY=path/to/taxonomy.json` 覆盖; - 收藏后自动跑一次;阅读视图顶部的「自动分类」按钮可对单条强制重判; 菜单 **分类** 下提供「为所选重新分类」「批量为尚未分类的内容补分类…」 「强制重新分类全部内容…」,可单独开关 **强制只用规则**; - 列表与阅读视图会标注分类来源(`AI` / `规则` / `手工`),Markdown 导出 的 front-matter 也会带上 `category_source` 字段。 - **公式支持(行内 / 段间)**:知乎把公式渲染成图片或 `data-tex` 节点,保存时会还原成 LaTeX: - **行内公式**输出 `\( ... \)`(Markdown 为 `$...$`),**段间公式**输出 `\[ ... \]`(`$$ ... $$`); - 阅读视图与 HTML / PDF 导出用 **MathJax** 排版,离线时自动降级为等宽 LaTeX 源码; - 正文里的公式数量会记录到数据库(`has_math`),列表与详情都会标记「含公式」。 - **本地收藏库(SQLite)**:保存标题、问题、作者、作者简介、头像、原文链接、 正文 HTML/纯文本、AI 摘要、分类、标签、赞同数、评论数、发布时间、收藏时间、收藏夹、备注等字段。 - **图片本地化**:收藏时后台线程下载正文图片到本地,断网也能看图(可关闭)。 - **整页快照**(可选):额外保存收藏时的整页 HTML。 - **阅读视图**:用 WebEngine 渲染收藏正文(支持 MathJax 公式),顶部展示 AI 摘要、 分类与标签,可就地修改分类、重新生成摘要、导出当前条。 - **收藏库面板**:关键词搜索(含摘要)、按收藏夹 / 类型 / **分类**筛选、查看详情、移动收藏夹、 删除、复制链接、在内置浏览器打开原文,右键菜单可直接「生成 AI 摘要与分类」「修改分类」。 - **导出**:所选(或全部筛选结果)导出为 **Markdown / HTML / PDF**,单条一个文件或合并成合集, 已本地化的图片会一起复制到导出目录;PDF 走 WebEngine 打印,公式先排版再打印。 - **下载保存**:内置下载队列(进度 / 大小 / 取消 / 清除已完成),保存位置可固定为 下载目录或每次询问;右键菜单提供「图片另存为…」「链接另存为…」「复制图片/链接地址」 「用系统浏览器打开链接」「保存本页为 HTML…」。 ## 环境要求 - Python 3.9+(推荐 3.10+) - [uv](https://docs.astral.sh/uv/)(用于管理虚拟环境与依赖) - Windows / macOS / Linux 桌面环境 安装 uv(任选其一): ```bash powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux ``` ## 安装与运行(uv) ```bash uv sync # 创建 .venv 并按 uv.lock 精确安装依赖(含 dev 分组) uv run zreader # 启动程序 ``` ### Windows 一键启动 双击项目根目录的 **`run.bat`** 即可(也可在命令行调用,参数会原样传给程序): | 场景 | 命令 | | --- | --- | | 启动 | `run.bat` | | 白屏 / 无 GPU | `run.bat --software-render` | | 自定义数据目录 | `run.bat --data-dir D:\Data` | | 查看全部参数 | `run.bat --help` | | 环境变量开启软件渲染 | `set ZREADER_SOFTWARE_RENDER=1` 后执行 `run.bat` | 脚本行为:自动切换到自身所在目录 → 检测 `uv`(首次运行自动执行 `uv sync` 安装依赖)→ 启动程序;若机器上没有 uv,会依次尝试 `.venv` 中的 Python、 系统 Python 运行;启动失败时保留窗口并给出提示。 等价的其它启动方式: ```bash uv run python -m zreader # 以模块方式启动 uv run main.py # 运行源码入口 ``` 无 GPU 的虚拟机 / 远程桌面环境下若出现白屏,使用软件渲染启动: ```bash uv run zreader --software-render ``` 其它参数: ```bash uv run zreader --data-dir D:\ZReaderData # 自定义数据目录 uv run zreader --url https://www.zhihu.com/question/621836265 uv run zreader --force-gpu # 虚拟机/黑名单显卡:强制硬件加速,改善滑动流畅度 # Windows PowerShell 设置环境变量后打开开发者工具窗口 $env:ZREADER_DEBUG_TOOLS=1; uv run zreader ``` ## 开发环境(uv) | 场景 | 命令 | | --- | --- | | 同步/更新环境 | `uv sync` | | 添加运行时依赖 | `uv add httpx` | | 添加开发依赖 | `uv add --dev pytest` | | 升级锁定版本 | `uv lock --upgrade` | | 执行任意命令 | `uv run python -c "import zreader"` | | 代码检查 | `uv run ruff check .` | | 代码格式化 | `uv run ruff format .` | | 导出传统 requirements.txt | `uv export --no-dev -o requirements.txt` | | 构建发行包 | `uv build` | 依赖声明集中在 `pyproject.toml`,锁定结果在 `uv.lock`(应提交到版本库); 虚拟环境位于 `.venv/`(已加入 `.gitignore`)。 > 依赖源:`pyproject.toml` 中的 `[[tool.uv.index]]` 默认指向清华 PyPI 镜像, > 网络可直连 PyPI 时删除该段即可。 ## 使用说明 1. 启动后在地址栏打开知乎(首次建议点「首页」后登录一次,登录状态会持久保存)。 2. 打开问题页 / 专栏页,等待页面渲染完成,每条回答右下角操作栏会出现蓝色的「收藏」按钮。 3. 点击即可保存(会自动展开「阅读全文」并取回完整正文);按钮显示「已收藏」时,再点一次会询问是否取消收藏。 4. 右侧「收藏库」面板可以搜索、按分类筛选、打开阅读视图、导出 PDF / HTML、删除。 5. 配置好 AI 服务商(DeepSeek 或豆包)的 API Key 后,收藏会自动补齐摘要与分类。 ### AI 摘要与自动分类(DeepSeek / 豆包) 1. 打开 **设置 → AI 与公式设置…**(或菜单 **AI → AI 与公式设置…**): - 勾选「启用 AI」; - 服务商选 **DeepSeek**(`https://api.deepseek.com`,模型 `deepseek-chat`)或 **豆包(火山方舟)**(`https://ark.cn-beijing.volces.com/api/v3`,模型如 `doubao-1-5-pro-32k-250115`), 切换服务商时 API 地址与模型候选会自动填充; 也可以选「自定义(OpenAI 兼容)」填自己的 `base_url` / 模型; - 填入对应平台的 **API Key**(豆包到火山方舟控制台创建),点「测试连接」确认可用; > 豆包也支持填写推理接入点 ID(形如 `ep-2025xxxx-xxxxx`)作为模型名。 - 需要时修改摘要字数、温度、超时、送入模型的最大字数; - 「分类体系」每行一个分类,AI 只会从中挑选(默认 16 类,含兜底「未分类」)。 2. 勾选「收藏后自动生成摘要 / 自动分类 / 自动补充标签」后,收藏动作会触发后台任务, 完成后自动写回数据库并刷新列表(鼠标悬停标题可预览摘要)。 3. 手动处理:选中若干条 → 右键「生成 AI 摘要与分类」,或菜单 **AI → 为所选生成摘要 / 分类**;历史数据可用 **AI → 批量处理尚未摘要的内容…** 补齐。 4. 失败会在状态栏提示,并把该条标记为 `ai_status = error`,可随时重试。 > 请求走标准库 `urllib`,不引入额外依赖;接口协议与 OpenAI `/chat/completions` 一致。 > 模型只需返回 `{"summary": "...", "category": "...", "tags": [...]}` 的 JSON, > 程序对代码块、前后废话都有容错解析。 ### 自动分类(规则 + AI 兜底) 新引入的「自动分类」在「AI 摘要与分类」的基础上**多了一条不依赖网络的规则路径**, 收藏后立即生效,对历史收藏也能批量补齐。 **判定顺序**:标签 > 摘要 > 标题;标签恰好等于分类名(如 `tags: "技术"`)时给最强加成 (+20 分),这样手工打的标签能反过来「指导」分类。同分时按词表里的先后顺序取, 保证结果可复现。 **两条路径的关系**: | 路径 | 触发条件 | 机制 | 成本 | | --- | --- | --- | --- | | 规则(默认) | 始终可用(离线兜底) | 内置词表按字段权重打分;命中不足记为「未分类」 | 零成本、纯离线 | | AI 兜底(可选) | 已配置 DeepSeek / 豆包 / 兼容服务 **且** 规则判不到 | 一次调用取回 `{"category", "tags"}`,覆盖规则结果 | 需 API | **入口**: - **菜单「分类」**: - 为所选重新分类(规则 + AI 兜底) - 批量为尚未分类的内容补分类… - 强制重新分类全部内容… - **阅读视图** 顶部:**「自动分类」** 按钮(对单条强制重判) - **收藏库面板** 顶部 / 右键:**「自动分类(基于 tag / 摘要)」** **自定义词表**:在「设置 → 自动分类」里贴 JSON 即可覆盖内置词表,例: ```json { "美食": ["火锅", "烘焙", "咖啡", "餐厅"], "旅行": ["攻略", "签证", "民宿"], "数码": ["手机", "笔记本", "显卡", "相机"], "未分类": [] } ``` 或者用环境变量 `ZREADER_CATEGORY_TAXONOMY=path/to/taxonomy.json` 指向文件(与 「设置」里的 JSON 等价)。词表里没有「未分类」时程序会自动补一个兜底。 **查看分类来源**:收藏库的「分类」列会显示 `分类·来源`(如 `技术·rule`、 `健康·ai`),阅读视图的标签栏有 `AI` / `规则` / `兜底` 颜色小标签, Markdown 导出的 front-matter 也会带 `category_source` 字段。 > 与「AI 摘要与分类」是**两套独立的设置**: > 关闭 AI(`ai_enabled=False`)**不影响自动分类**,规则路径始终可用; > 如果想完全跳过 AI 兜底,勾上「自动分类 → 强制只用规则路径」即可。 ### 公式(行内 / 段间) | 场景 | 表现 | | --- | --- | | 收藏保存 | 公式图片 / `data-tex` 节点还原成 LaTeX,行内 `\(…\)`、段间 `\[…\]` | | 阅读视图 | MathJax 排版;离线时降级为等宽字体显示的 LaTeX 源码 | | Markdown 导出 | 行内 `$…$`、段间 `$$ … $$` | | HTML / PDF 导出 | 内嵌 MathJax(可配置 CDN 或本地目录),PDF 会等排版完成后再打印 | 离线完全渲染:把 MathJax 的 `tex-mml-chtml.js` 所在目录填进「本地 MathJax 目录」即可, 不填则使用 CDN(jsDelivr,失败自动切 unpkg)。 ### 导出 PDF / HTML / Markdown | 目标 | 操作 | | --- | --- | | 选中的几条 | 选中 → 「导出」→ 选目录 → 选格式(HTML / PDF / Markdown) | | 整个筛选结果 | 不选中任何行 → 「导出」→ 同上 | | 单条快速导出 | 双击打开阅读视图 → 顶部「导出 PDF」/「导出 HTML」 | | 合并方式 | 多于一条时会询问「合并成一个文件」还是「每条一个文件」 | PDF 由 `PdfExporter` 串行渲染(等公式排版 → `printToPdf`),页面大小 / 页边距 / 等待时间可在设置里改。 ### 下载与保存 | 场景 | 操作 | | --- | --- | | 页面里的附件 / 文件链接 | 直接点击,自动保存到下载目录(或弹出保存对话框) | | 图片 / 视频 | 右键 → **图片另存为…** / 复制图片地址 | | 任意链接 | 右键 → **链接另存为…** / 复制链接地址 / 用系统浏览器打开链接 | | 当前页面 | 右键 → **保存本页为 HTML…** | | 查看进度 | 右侧「下载」标签页(有新任务时会自动切过去) | | 改下载位置 | 「下载」标签页 → 下载目录;或勾选「设置 → 下载时询问保存位置」 | - 下载目录默认 `<数据目录>/downloads`,可在设置里改到任意路径。 - 同名文件不会覆盖,会自动加序号(`data.bin` → `data (1).bin`)。 - 下载走浏览器自身的下载通道(同一个持久化 Profile),因此需要登录才能访问的文件也能正常下载。 ### 滑动流畅度 注入脚本对滚动做了专门优化,**滑动期间完全不做 DOM 查询与插入**: 1. 滚动中(`scroll` / `wheel` / `touchmove`)只把新增节点入队,等停止滑动后再处理; 2. 不再每次都对整页 `querySelectorAll`,只处理 MutationObserver 给出的新增子树; 3. 队列按 **≤6ms 的时间片**分批执行,避免长任务掉帧; 4. 已注入判断改为「按钮是否仍在文档中」,兼容知乎虚拟列表的节点复用。 实测(72k 节点 / 540 张卡片的合成页面,滚动中持续追加卡片):DOM 变化 1638 批次, 脚本实际只处理 9 批,单次任务最大占用 **6.9ms**(一帧预算 16.7ms),滚动期间注入开销 ≈ 6.7ms。 若在虚拟机 / 被 Chromium 列入 GPU 黑名单的机器上仍感觉卡顿,可尝试 `uv run zreader --force-gpu`(忽略 GPU 黑名单,强制硬件加速)。 ### 登录与第三方授权窗口 知乎的 QQ / 微信 / 微博 登录走的是 `window.open` 弹窗,程序对此做了区分处理: | 请求来源 | 处理方式 | | --- | --- | | 站内链接(`*.zhihu.com`,含 `target="_blank"`) | 在当前视图打开,历史记录完整,后退/前进可用 | | 站外链接(`open.weixin.qq.com`、`graph.qq.com` 等) | 打开独立弹窗窗口,与主视图共用同一 Profile(登录态共享) | 弹窗窗口自带 **前进 / 后退 / 刷新 / 关闭** 按钮,并且支持第三方登录页自身的 `window.close()` 与「返回」操作;授权成功回跳到知乎域名时,弹窗会自动把地址交还 主视图并关闭自己。 ## 数据目录 | 平台 | 默认路径 | | --- | --- | | Windows | `%APPDATA%\ZReader` | | macOS | `~/Library/Application Support/ZReader` | | Linux | `~/.local/share/ZReader` | 目录结构: ``` ZReader/ ├── library.db # SQLite 收藏库(含 summary / category / ai_status / has_math 等字段) ├── settings.json # 用户设置(含 AI 配置) ├── profile/ cache/ # QtWebEngine 持久化 Profile 与缓存(含登录态) ├── assets/images/ # 离线图片 ├── assets/snapshots/ # 整页快照 ├── assets/_preview/ # 阅读视图 / PDF 导出的临时 HTML ├── downloads/ # 下载的文件(附件 / 图片 / 保存的页面) └── export/ # 默认导出目录 ``` > 旧版本升级:`library.db` 会自动 `ALTER TABLE` 补上新增列,不需要重建收藏库。 ## 项目结构 ``` pyproject.toml 项目元数据 / 依赖 / ruff 配置(uv) uv.lock 依赖锁定文件 main.py 便捷入口(等价于 python -m zreader) zreader/ ├── __main__.py 启动入口(`uv run zreader` / `python -m zreader`) ├── config.py 路径 / 设置(含 AI、公式、导出默认值) ├── db.py SQLite 存储层(含字段迁移、分类统计) ├── utils.py HTML 清洗、HTML→Markdown(含公式)、文件名处理 ├── mathhtml.py ★ 公式还原:公式图片 / data-tex → LaTeX(行内 / 段间) ├── classifier.py ★ 自动分类:双路径(规则 + AI 兜底),按 tag/摘要/标题打分 ├── renderer.py ★ 阅读 / 导出共用的 HTML 渲染(样式 + MathJax) ├── ai.py ★ DeepSeek / 豆包 / OpenAI 兼容客户端 + 后台任务调度 ├── pdf.py ★ PDF 导出(WebEngine 渲染 → printToPdf) ├── websettings.py WebEngine 设置兼容封装 ├── bridge.py QWebChannel 桥接对象(JS ↔ Python) ├── inject.py 把注入脚本注册到 QWebEnginePage ├── browser.py QtWebEngine 浏览器视图 ├── images.py 图片本地化(线程池下载) ├── exporter.py 导出 Markdown / HTML ├── reader_dialog.py ★ 阅读视图(WebEngine + 公式 + AI 摘要) ├── settings_dialog.py ★ 设置对话框(AI / 公式 / 导出) ├── downloads.py 下载管理(保存位置策略 + 进度跟踪) ├── download_panel.py 下载面板(队列 / 进度 / 操作) ├── library_panel.py 收藏库面板(分类筛选 / AI 操作 / 导出) ├── main_window.py 主窗口 └── web/assets/ ├── inject.js ★ 页面注入脚本(收藏按钮 + 完整内容提取) └── inject.css 注入按钮样式 ``` ## 注入实现要点 `inject.js` 通过 `QWebEngineScript`(`DocumentCreation` 注入点、主世界)在页面脚本之前执行: 1. 加载 Qt 的 `qwebchannel.js`,用 `qt.webChannelTransport` 连接桌面端对象 `zreader`; 2. 扫描内容卡片,向 `.ContentItem-actions` 插入「收藏」按钮; 3. 点击时从 `data-zop` / `data-za-extra-module` / `meta[itemprop]` / `.RichText.ztext` 中提取结构化内容,`JSON.stringify` 后经 `bridge.save_item()` 发送; 4. 通过 `bridge.saved_keys()` 同步本地已收藏列表,渲染「已收藏」状态; 5. 保存 / 删除结果通过回调返回,并用页面内 Toast 提示。 > 说明:知乎前端类名会不定期调整,若某类卡片识别不到,只需在 > `zreader/web/assets/inject.js` 的 `CARD_SELECTOR` 中补充选择器即可。 ## 已知限制 - 只对桌面版知乎页面有效;需要在页面渲染完成后注入(脚本会自动重试)。 - 图片下载依赖网络,失败时保留原始远程地址,不影响正文保存。 - 整页快照为静态 HTML,外部资源仍需联网查看。 - 公式渲染默认从 CDN 拉 MathJax;完全离线时公式会以 LaTeX 源码显示 (可在设置里指定本地 MathJax 目录实现离线排版)。 - 完整正文依赖站内接口:未登录或接口变更时会自动回退到页面 DOM 内容。