# zreaderpy **Repository Path**: Yiqiuyu/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-09-28 - **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`)时,右下角会出现悬浮「收藏」按钮,走同样的完整正文提取。 - **每条评论 / 楼中楼回复都支持**:评论区展开后,会在每条评论操作栏的 **「喜欢 / 回复」前面**补一个「**收藏评论**」按钮(收藏后变「已收藏」); 外形与正文里的「收藏」按钮完全一致(同书签图标 / 同高度内边距圆角 / 同悬停与已收藏配色), 只有文案区分,一眼能看出收藏的是评论而不是整篇; - 识别方式:先按 `.CommentItem` / CSS Module 类名扫描;**如果一个都没匹配到** (知乎整站改类名),再用每条评论的「回复」按钮反推评论节点,因此不依赖类名; 反推按「作者区和这个操作栏同属一层」来判定归属,因此父评论下面**已展开楼中楼**时 同样能认出来——每条评论(含楼中楼回复)都会有一个收藏按钮; - 正文节点认不出时取「文字最多的子节点」当正文,保证照样能拿到完整评论内容; **纯图片 / 表情评论**(没有文字)取带图的那一层当正文、用图片地址做内容指纹, 所以照样有按钮、能收藏,标题显示为「作者 的评论:(图片 / 表情评论)」; - 排版跟随原生操作栏(有 flex gap 用 gap,否则沿用锚点外边距): - 收藏后以「评论」类型入库,标题为「作者 + 正文摘要」,并记录所属回答 / 文章 作为「问题」字段,点开可跳回原文上下文;作者区若有「IP 属地」也会一并记下, 本地列表显示为「作者 · 属地」(如 `void · 北京`); - 收藏时和文章一样把该条评论(含图片与公式)自动存成**独立 HTML 文件**到下载目录, 同时另存一份原文 `.txt`,一步完成本地留存; - **连同上下文一起收藏**(默认开启,可用设置里的同名开关关闭): - 收藏某条**评论** → 连同它所属的**回答 / 文章**同步收藏; - 收藏某条**楼中楼回复** → 连同它所在的那条**评论**和**整篇原文**同步收藏; - 每一层都单独入库(记录自己的标题、作者、完整正文),并各自生成一个 HTML 与 `.txt`,取消收藏时也只影响那一条; - 已经在收藏库里的层级会自动跳过,不会重复下载、也不会反复保存同一篇文章; - 收藏完成后页面上父评论 / 原文卡片的「收藏」按钮会一起变成「已收藏」。 - 评论编号依次尝试节点属性、React props、内容指纹,保证「已收藏」状态可回填、 再次点击可取消; - **按钮没出现时**:页面上一个评论按钮都没有时,脚本每 8 秒会自动按「回复」按钮 重推一次;也可以手动点菜单 **视图 → 重新识别评论收藏按钮**, 或用 **视图 → 诊断页面注入情况…** 生成一份报告(含选择器命中数、前几条「回复」 按钮的祖先链),便于快速定位是知乎又改了结构还是别的原因。 - **原生「更多」菜单也已接入**:点击评论 / 回答 / 文章右上角的「更多(⋯)」弹出的菜单 (`.more-action-card` → `.action-item`)里,原生「收藏」项会被接管为 「收藏到 ZReader 本地」,直接完成「收藏 + 自动落盘」(完整正文提取与内联按钮一致); - 菜单项按**图标**识别(新版知乎写成 `` / `#collect`),所以「取消收藏 / 已收藏」不会被误接管,也能据此区分是评论还是整篇; - 归属定位不依赖弹层挂在哪:弹层在元素内部就向上找最近的评论 / 卡片;被挂到 `body` 时依次用「`aria-controls` ↔ 弹层 id」「弹层位置反查上方元素」「最近点击的元素」定位; - 连卡片类名都认不出时(知乎大改版),回答 / 文章页还能按页面 URL 兜底收藏。 - **完整正文提取**:收藏时先自动点掉「阅读全文」展开折叠内容,再用站内接口 (`/api/v4/answers|articles/{id}?include=content`)取回完整正文; 接口不可用时静默回退到页面 DOM,不会因为长回答只保存一半。 - **AI 摘要与自动分类**(豆包 / DeepSeek / 免费接口 / OpenAI 兼容): - 收藏后后台自动生成中文摘要(字数可配),并写入收藏库; - 根据**摘要 + 标签**从分类体系中挑选最合适的分类(只会输出候选分类之一,避免分类爆炸); - 同时提炼 3-6 个标签;摘要、分类、标签一次请求完成,省 token; - 可在「AI → AI 与公式设置…」里配置 Key / 模型 / 温度 / 提示词 / 分类体系,并一键测试连接; - 也可以对历史收藏批量补处理(`AI → 批量处理尚未摘要的内容…`)。 - **本地关键词兜底分类**:未启用 AI、请求失败或模型返回「未分类」时, 按 **标签 > 标题 > 正文** 的加权关键词规则自动归类(离线可用,可在设置里关闭)。 - **公式支持(行内 / 段间)**:知乎把公式渲染成图片或 `data-tex` 节点,保存时会还原成 LaTeX: - **行内公式**输出 `\( ... \)`(Markdown 为 `$...$`),**段间公式**输出 `\[ ... \]`(`$$ ... $$`); - 阅读视图与 HTML / PDF 导出用 **MathJax** 排版,离线时自动降级为等宽 LaTeX 源码; - 正文里的公式数量会记录到数据库(`has_math`),列表与详情都会标记「含公式」。 - **本地收藏库(SQLite)**:保存标题、问题、作者、作者简介、头像、原文链接、 正文 HTML/纯文本、AI 摘要、分类、标签、赞同数、评论数、发布时间、收藏时间、收藏夹、备注等字段。 - **图片本地化**:收藏时后台线程下载正文图片到本地,断网也能看图(可关闭)。 - **原文另存本地**(默认开启):收藏时把完整正文纯文本另存为 `.txt` (目录为数据目录下的 `raw_text`,可在设置里关闭或自定义)。 - **收藏即落盘 HTML**(默认开启):收藏成功后自动把该条内容(完整正文 + 公式 + AI 摘要) 渲染成独立 HTML 存到下载目录,因此页面上不再有单独的「下载」按钮; 收藏评论 / 回复时,同步收藏的父评论与原文也各自生成一份 HTML。 - **整页快照**(可选):额外保存收藏时的整页 HTML。 - **阅读视图**:用 WebEngine 渲染收藏正文(支持 MathJax 公式),顶部展示 AI 摘要、 分类与标签,可就地修改分类、重新生成摘要、导出当前条。 - **收藏库面板**:关键词搜索(含摘要)、按收藏夹 / 类型 / **分类**筛选(另有「只看评论」 一键查看收藏的评论)、**「导出评论」一键把收藏的评论合并导出为一个 HTML**(落到下载目录)、 查看详情、移动收藏夹、删除、复制链接、在内置浏览器打开原文, 作者列显示「作者 · 属地/简介」;右键菜单可直接「生成 AI 摘要与分类」「修改分类」 「导出收藏评论为 HTML」。 - **导出**:所选(或全部筛选结果)导出为 **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. 点击即可保存(会自动展开「阅读全文」并取回完整正文,同时把这份内容自动存成 HTML 落到下载目录);按钮显示「已收藏」时,再点一次会询问是否取消收藏。 4. 评论区展开后每条评论 / 回复前面都有「收藏评论」(样式与正文的「收藏」按钮一致),点一下即把该条连同它的父评论 与所属回答 / 文章一起收藏并落盘(可在 **设置 → 常规** 里关闭「收藏评论 / 回复时, 连同它的父评论与所属文章一起收藏」)。 5. 右侧「收藏库」面板可以搜索、按分类筛选、打开阅读视图、导出 PDF / HTML、删除。 6. 配置好豆包 / DeepSeek 的 API Key 后,收藏会自动补齐摘要与分类。 ### 评论按钮没出现? 注入脚本会按类名扫描评论区,扫不到时(知乎改版)自动改用「回复」按钮反推: 1. 先等 1-2 秒,或点菜单 **视图 → 重新识别评论收藏按钮**; 2. 还是没有:点菜单 **视图 → 诊断页面注入情况…**,把报告整段复制发给开发者 (里面包含各选择器的命中数、前 5 个「回复」按钮的祖先链,能直接看出结构差异)。 ### AI 摘要与自动分类(豆包 / DeepSeek / 免费接口 / OpenAI 兼容) 1. 打开 **设置 → AI 摘要与分类**(或菜单 **AI → AI 与公式设置…**): - 勾选「启用 AI」; - 服务商四选一(切换时会自动填充对应的 API 地址与模型): - **DeepSeek**(默认 `https://api.deepseek.com`,模型 `deepseek-chat`); - **豆包(火山方舟 Ark)**(默认 `https://ark.cn-beijing.volces.com/api/v3`, 模型可填控制台的**推理接入点 ID**(`ep-xxxxxx`)或官方模型名如 `doubao-1-5-pro-32k-250115`); - **免费接口(OpenAI 兼容)**:填任意兼容接口的 `base_url` / 模型,**可不填 API Key**; - **自定义(OpenAI 兼容)**:填自己的 `base_url` / 模型; - 填入 **API Key**(免费接口可选),点「测试连接」确认可用; - 需要时修改摘要字数、温度、超时、送入模型的最大字数; - 「分类体系」每行一个分类,AI 只会从中挑选(默认 16 类,含兜底「未分类」)。 2. 勾选「收藏后自动生成摘要 / 自动分类 / 自动补充标签」后,收藏动作会触发后台任务, 完成后自动写回数据库并刷新列表(鼠标悬停标题可预览摘要)。 3. 手动处理:选中若干条 → 右键「生成 AI 摘要与分类」,或菜单 **AI → 为所选生成摘要 / 分类**;历史数据可用 **AI → 批量处理尚未摘要的内容…** 补齐。 4. 失败会在状态栏提示,并把该条标记为 `ai_status = error`,可随时重试。 5. 不接 AI 也能分类:勾选「AI 不可用时用关键词规则自动分类」后, 未启用 AI / 请求失败 / 模型给出「未分类」时,会按**标签 > 标题 > 正文** 的加权关键词匹配自动归类(关键词表见 `zreader/config.py` 的 `CATEGORY_KEYWORDS`, 可随分类体系一起改)。 > 请求走标准库 `urllib`,不引入额外依赖;接口协议与 OpenAI `/chat/completions` 一致。 > 模型只需返回 `{"summary": "...", "category": "...", "tags": [...]}` 的 JSON, > 程序对代码块、前后废话都有容错解析。 ### 公式(行内 / 段间) | 场景 | 表现 | | --- | --- | | 收藏保存 | 公式图片(`equation?tex=`)/ `data-tex` 节点还原成 LaTeX,行内 `\(…\)`、段间 `\[…\]` | | 阅读视图 | MathJax 排版(同时支持 `\(…\)`/`$…$` 与 `\[…\]`/`$$…$$`);离线时降级为等宽字体显示的 LaTeX 源码 | | Markdown 导出 | 行内 `$…$`、段间 `$$ … $$` | | HTML / PDF 导出 | 内嵌 MathJax(可配置 CDN 或本地目录),PDF 会等排版完成后再打印 | > 正文里如果**原本就写着** `$x^2$` / `$$y''=y$$`(作者手写的 LaTeX),阅读视图与 > HTML / PDF 导出会按行内 / 段间公式渲染。为避免把「价格 $5」这类金额当成公式, > 排版前会自动保护:单个 `$` 必须「紧跟非空白、收尾前非空白、不跨行」才当作公式, > 否则会被转义成 `\$` 原样显示(也可自己写 `\$` 表示字面量)。 **段间公式的判定依据**(按优先级,命中任一即为段间;否则按行内处理): 1. LaTeX 自带段间分隔符(`$$…$$` / `\[…\]`); 2. 内联样式 `display:block`;`display:inline-block` 且带居中提示(`margin:auto` / `text-align:center`); 3. 知乎公式图片的 `eeimg="2"`(1 = 行内,2 = 段间); 4. 独占一个 `

` / `

` / `
` 容器(整段只有一个公式); 5. 节点本身就是块级标签(`
`)或类名含 `block` / `display` / `centered`; 6. 公式里出现 `\begin{}` / `\displaystyle` / `\tag{}` 等只可能独立成行的结构。 其余情况(夹在文字中间、`display:inline`、类名含 `inline`、`eeimg="1"`)一律按**行内公式**保存。 离线完全渲染:把 MathJax 的 `tex-mml-chtml.js` 所在目录填进「本地 MathJax 目录」即可, 不填则使用 CDN(jsDelivr,失败自动切 unpkg)。 ### 导出 PDF / HTML / Markdown | 目标 | 操作 | | --- | --- | | 选中的几条 | 选中 → 「导出」→ 在弹出的选项框选格式(PDF / HTML / Markdown)与是否合并 → 选目录 | | 整个筛选结果 | 不选中任何行 → 「导出」→ 同上 | | 单条快速导出 | 双击打开阅读视图 → 顶部「导出 PDF」/「导出 HTML」 | | 合并方式 | 多于一条时会询问「合并成一个文件」还是「每条一个文件」 | PDF 由 `PdfExporter` 串行渲染(等公式排版 → `printToPdf`),页面大小 / 页边距 / 等待时间可在设置里改。 ### 下载与保存 - **收藏即自动下载**:点「收藏」后完整内容会自动渲染成 HTML 落到下载目录 (设置 → 常规 → 「收藏时自动下载完整内容到本地」)。下面的表格是**浏览器自身** 的下载通道(附件 / 图片 / 保存页面),与收藏落盘互不影响。 | 场景 | 操作 | | --- | --- | | 页面里的附件 / 文件链接 | 直接点击,自动保存到下载目录(或弹出保存对话框) | | 图片 / 视频 | 右键 → **图片另存为…** / 复制图片地址 | | 任意链接 | 右键 → **链接另存为…** / 复制链接地址 / 用系统浏览器打开链接 | | 当前页面 | 右键 → **保存本页为 HTML…** | | 查看进度 | 右侧「下载」标签页(有新任务时会自动切过去) | | 改下载位置 | 「下载」标签页 → 下载目录;或勾选「设置 → 下载时询问保存位置」 | - 下载目录默认 `<数据目录>/downloads`,可在设置里改到任意路径。 - 同名文件不会覆盖,会自动加序号(`data.bin` → `data (1).bin`)。 - 下载走浏览器自身的下载通道(同一个持久化 Profile),因此需要登录才能访问的文件也能正常下载。 ### 滑动流畅度 注入脚本对滚动做了专门优化,**滑动期间完全不做 DOM 查询与插入**: 1. 滚动中(`scroll` / `wheel` / `touchmove`)只把新增节点入队,等停止滑动后再处理; 2. 不再每次都对整页 `querySelectorAll`,只处理 MutationObserver 给出的新增子树; 3. 队列按 **≤6ms 的时间片**分批执行,避免长任务掉帧; 4. 已注入判断改为「按钮是否仍在文档中」,兼容知乎虚拟列表的节点复用; 5. 同时监听「节点被移除」:点「阅读全文」/「展开」后知乎会重渲染操作栏,把注入的按钮 一起删掉(这类变化不会出现在 `addedNodes` 里),脚本会按被删位置回到最近的卡片 / 评论节点重新注入一次,因此展开后按钮不会消失;整张卡片被虚拟列表回收时不做多余补注入。 实测(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/ # 下载的文件(收藏自动落盘的 HTML / 附件 / 图片 / 保存的页面) └── 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(行内 / 段间) ├── 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()` 发送 (评论 / 回复则用 `bridge.save_items()` 一次提交「该条 + 父评论 + 所属原文」); 4. 通过 `bridge.saved_keys()` 同步本地已收藏列表,渲染「已收藏」状态; 5. 保存 / 删除结果通过回调返回,并用页面内 Toast 提示。 > 说明:知乎前端类名会不定期调整,若某类卡片识别不到,只需在 > `zreader/web/assets/inject.js` 的 `CARD_SELECTOR` 中补充选择器即可; > 评论同理,见同文件的 `COMMENT_SELECTOR` / `COMMENT_EXACT_RE`。 ## 已知限制 - 只对桌面版知乎页面有效;需要在页面渲染完成后注入(脚本会自动重试)。 - 图片下载依赖网络,失败时保留原始远程地址,不影响正文保存。 - 整页快照为静态 HTML,外部资源仍需联网查看。 - 公式渲染默认从 CDN 拉 MathJax;完全离线时公式会以 LaTeX 源码显示 (可在设置里指定本地 MathJax 目录实现离线排版)。 - 完整正文依赖站内接口:未登录或接口变更时会自动回退到页面 DOM 内容。