# zreaderpy02 **Repository Path**: jjyyxx975/zreaderpy02 ## Basic Information - **Project Name**: zreaderpy02 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-10-10 - **Last Updated**: 2026-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ZReader · 知乎内容浏览器 基于 **PySide6 + QtWebEngine** 的桌面软件:内嵌浏览器浏览知乎,自动在每张回答/文章卡片上注入「下载」按钮, 一键把结构化内容下载到本地 SQLite,支持整页批量下载、图片本地化、离线阅读、全文检索、 Markdown/HTML/PDF 导出,以及收藏后自动生成 **AI 摘要**、并按标签与摘要 **自动分类(DeepSeek / 豆包)**。 技术方案详见 [`知乎内容浏览器-技术实现方案.md`](知乎内容浏览器-技术实现方案.md)。 ## 环境管理(uv) 本项目使用 [uv](https://docs.astral.sh/uv/) 管理虚拟环境与依赖,Python 版本固定为 3.12(见 `.python-version`)。 ```bash # 1) 安装 uv(已安装可跳过) powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # 2) 创建 .venv 并安装全部依赖(含 PySide6 6.8) uv sync # 3) 运行 uv run python main.py ``` Windows 下也可以直接跑启动脚本(会自动定位 uv 并在首次运行时装依赖): ```powershell .\run.bat # 正常启动 .\run.bat --debug # 启动并开放 9222 调试端口 .\run.bat --sync # 先重新安装依赖再启动 ``` 常用命令: | 命令 | 说明 | |---|---| | `uv sync` | 按 `pyproject.toml` 同步虚拟环境 | | `uv add ` | 新增依赖并写入 `pyproject.toml` | | `uv run python main.py` | 在虚拟环境中运行 | | `uv run python -c "import PySide6; print(PySide6.__version__)"` | 校验环境 | ## 功能 | 功能 | 入口 | |---|---| | 浏览知乎(登录态持久化) | 主窗口左侧浏览器,Cookie 存于 `~/.zreader/profile` | | 下载当前卡片 | 页面卡片操作栏的「下载」按钮 / `Ctrl+S`(视口内最大卡片) | | 批量下载整页 | 工具栏「批量下载本页」/ `Ctrl+Shift+S`:把当前页面的回答 / 文章一次下载到本地 | | 收藏评论 | 评论操作栏的「收藏」按钮(正文评论区与「评论回复」弹窗里都有) | | AI 摘要(DeepSeek / 豆包) | 收藏后自动生成要点摘要;工具栏「AI 设置」或 `Ctrl+Shift+A` 配置 | | AI 自动分类 | 依据标签 + 摘要自动归入「文件夹」;列表右键可重新分类 | | 文件夹 / 标签管理 | 右侧面板顶部下拉(文件夹)+ 标签 chip 筛选 + 批量移动 | | 本地收藏列表 + 全文检索 | 主窗口右侧面板(SQLite FTS5,trigram 中文分词) | | 离线阅读 | 双击列表条目,加载 `~/.zreader/offline/.html`(正文下方带已收藏评论) | | 导出 | 列表「导出」按钮 / 右键「导出」子菜单:选中一条或多条导出为 Markdown / 单文件 HTML / PDF;阅读窗口也可导出当前条目 | 关于「收藏评论」的几点行为: - **评论操作栏多一个「收藏」**:正文评论区和「评论回复」弹窗里的每条评论都会注入一个轻量的 「收藏」按钮,与知乎自带的「回复 / 喜欢」同排。 - **自动带上所属回答/文章**:若这条评论所属的回答/文章**还没有收藏**,点「收藏」会先把它 整条抓下来(折叠的先展开)再保存评论 —— 评论表对 `items` 有外键,父级缺失时无法单独存在; 成功后会提示「并已自动收藏所属回答/文章」。父级已收藏时只增量保存评论,不会覆盖正文。 - **同一条评论只存一份**:评论 id 由「所属内容 + 作者 + 时间 + 正文」的内容指纹生成, 所以同一条评论在正文内联列表与回复弹窗里收藏,命中的是同一条记录(不会重复)。 - **弹窗里的评论靠上下文定位**:评论弹窗是挂在 `body` 下的 portal,不在任何 `.ContentItem` 里, 注入脚本会记住「打开弹窗时点击的那张卡片」作为评论归属;因此请在内容页点击评论后再收藏。 关于「下载」的几点行为: - **折叠的回答会自动展开**:知乎折叠卡片在 DOM 里只有服务端下发的截断预览(纯文本 + 省略号), 点下载时会先自动点「阅读全文」,等 React 渲染出完整段落再抓取;万一没展开成功,会提示 「未能展开全文,内容可能不完整」,不会静默存半截。 - **重新下载即覆盖**:按 `item_id` upsert,对同一条内容再点一次即可刷新为最新/完整版本,离线页会一并重建。 - **不弹新窗口**:点问题卡片、QQ/微信登录等 `target=_blank` 请求一律在当前视图打开, 历史正常累加,前进/后退全程可用。 - **每张卡片只有一个「下载」**:折叠态与展开态可能同时存在多个操作栏,注入脚本只在 **可见的那一个**里放按钮(优先底部 sticky 栏),并清掉其余操作栏里的残留。 - **懒加载也能补上按钮**:DOM 变更批次先累积再统一处理(React 常分多批渲染,先插卡片骨架 再插操作栏),另有 2 秒兜底巡检,卡片被重建或漏批时都会自动补回。 - **公式离线可读**:知乎公式是 MathJax 生成的内联 SVG(字形定义在文档别处,片段一脱离页面就失效), 下载时会转成**原生 MathML 并保留 LaTeX**:离线阅读直接显示公式(Chromium 内建支持,零依赖)。 - **行内 / 段间公式分开处理**:知乎两种公式都写在 `` 里,**靠标签名分不出来**, 下载时多路判定(`mjx-container[display="true"]` / `mode=display` 脚本 / `\displaystyle` 前缀 / 节点标签名), **行内公式**随文排(导出 `$…$`),**段间公式**独占一行居中、过宽可横向滚动(导出 `$$…$$`)。 ## 批量下载整页 工具栏「批量下载本页」(`Ctrl+Shift+S`)会把**当前页面上的回答 / 文章一次性下载到本地**, 不用再一张张点「下载」。点开后会先问三件事:跳过已收藏、先滚动加载、数量上限(默认 200 条)。 - **只吃浏览器已经渲染出来的内容**:不请求知乎接口、不并发抓取,与「明确不做」的合规约定一致; 卡片之间顺序处理并留出间隔,节奏刻意压到接近人工浏览。 - **先滚动、再下载**:知乎是懒加载的,不滚动只能拿到首屏那几张。勾选后按 1.2 秒一轮滚到底 (最多 60 轮),连续两轮既没有新卡片、页面也没变高就停,下载完自动回到你原来的滚动位置。 - **折叠的回答照样展开**:与单条下载同一套逻辑,逐张点「阅读全文」等 React 渲染完整再取; 没展开成功的那几条单独计数,并在结果提示里说明。 - **默认跳过已收藏**:只下载页面上还没有的内容;取消勾选才会连已收藏的一起重新下载(覆盖本地那一份)。 - **中途可以停**:下载期间工具栏按钮变成「取消批量下载」,**已下载的部分分片落库**,不会白费。 - **落库即排队 AI**:每一片写完都会刷新右侧列表,并逐条排队摘要 / 分类(前提是开了对应开关)。 - **页面被导航走会自动收尾**:若下载期间跳转或刷新,注入脚本随页面一起销毁, 工具栏状态会自动复位而不是卡在「取消」。 ## 导出 在右侧收藏列表选中**一条或多条**,点工具栏「导出」按钮或右键「导出」子菜单,选择格式: | 格式 | 产物 | 说明 | |---|---|---| | Markdown | `*.md` | 正文转 Markdown,公式还原为 `$…$` / `$$…$$`,文首带元信息、AI 摘要与分类 | | 单文件 HTML | `*.html` | 图片内联为 base64,单个文件即可离线打开、单独分发 | | PDF | `*.pdf` | 由 QWebEngine 打印 A4,图片本地化后一并入档 | - **单条**:弹「另存为」,可自选文件名与位置。 - **多条**:弹「选择文件夹」,逐条写入;同名文件自动追加 `(1)`/`(2)` 后缀,互不覆盖。 - **PDF 串行打印**:QWebEngine 同一时刻只能打印一个页面,多条 PDF 会排队逐个渲染并打印,状态栏显示进度。 - 导出默认落在 `~/.zreader/exports`,导出内容始终以**本地已下载的那一份**为准(不是重新联网抓取)。 ## AI 摘要与自动分类(DeepSeek / 豆包) 收藏一条回答 / 文章后,后台线程按「**先摘要、后分类**」的顺序处理: 1. **摘要**:生成一段 60~150 字的要点摘要,显示在右侧列表条目下方,并写进离线阅读页与导出; 2. **自动分类**:拿「标签 + 摘要」判断归属,把条目归进一个**文件夹**(分类即文件夹), 列表里以 `🤖 分类名` 显示,离线页 / 导出显示为分类徽标。 配置入口:主工具栏「AI 设置」,或按 `Ctrl+Shift+A`。各家都走 OpenAI 风格的 `/chat/completions`,切换「服务商」会自动填好地址与默认模型,也可手动改。 | 设置项 | 说明 | |---|---| | 服务商 | `DeepSeek`(默认)或 `豆包(火山方舟)`;切换后地址 / 模型自动填写 | | API Key | DeepSeek 用 `sk-…`;豆包填火山方舟的 API Key | | 接口地址 | DeepSeek:`https://api.deepseek.com`;豆包:`https://ark.cn-beijing.volces.com/api/v3` | | 模型 | DeepSeek:`deepseek-chat`;豆包:`doubao-pro-32k`(也可填具体版本或推理接入点 `ep-…`) | | 正文上限 | 送进模型的正文截断长度(默认 6000 字,按 token 计费;正文本身仍完整存本地) | | 自动摘要 | 关掉后收藏不再自动排队,可用列表右键「生成 AI 摘要」逐条触发 | | 自动分类 | 关掉后不再自动排队,可用列表右键「AI 分类」逐条触发 | | 允许新建分类 | 关掉后 AI 只能从已有分类里挑,分类体系完全由你掌控 | | 分类上限 | 分类总数上限(默认 30),超出后 AI 不再新建,避免「一条一个分类」 | ### 分类是怎么落到「文件夹」的 - **分类复用 `folders` 表**,不是另造一套体系:所以分类天然支持顶部下拉筛选、 右键「移动到文件夹」、以及删除文件夹时把条目退回默认收藏夹。 - **优先复用已有分类**:提示词里会把现有分类列表给模型,并明确要求「已有分类合适就必须复用, 不要另造近义分类」,避免出现「编程 / 软件开发 / 写代码」三个同义分类。 - **人工归类优先,且会被锁定**:你手动把条目移到某个文件夹后该条打上 `category_locked`, AI 之后**不会再改动它**(右键「重新 AI 分类(解除人工归类)」可显式解锁)。 - **老库不会被搅乱**:升级后首次启动时,凡是不在「默认收藏夹」里的条目都会被自动视为 人工归类并上锁,避免 AI 一上来就改写你攒了很久的分类。 - **超过分类上限就只做选择**:达到上限后 AI 不再新建,只在已有分类里挑; 万一仍给出新名字,就只记录判定结果而不挪动文件夹。 ### 关于摘要 / 分类的共同行为 - **Key 存在哪**:`~/.zreader/settings.json` 的 `ai` 段(明文,仅本机,不入库、不外传)。 环境变量优先级更高,随服务商不同:DeepSeek 用 `DEEPSEEK_API_KEY` / `DEEPSEEK_BASE_URL` / `DEEPSEEK_MODEL`,豆包用 `ARK_API_KEY` / `ARK_BASE_URL` / `ARK_MODEL`,适合临时试用或 CI。 - **没配 Key 完全可用**:整条 AI 链路静默跳过,收藏、图片本地化、离线阅读、导出全部照常。 - **批量补历史**:设置对话框里「补全缺失的摘要」「分类未归类的收藏」两个按钮,只处理还没有 结果且**未被人工归类**的条目。 - **失败不重试、不覆盖**:Key 失效 / 额度不足等会标记失败并在状态栏说明,右键可手动重试; 失败**不会**清掉已有的摘要或分类结果。 - **内容过短自动跳过**:正文少于 30 字直接标记跳过,不消耗额度。 - **摘要失败时不排队分类**:Key 失效这类是全局故障,再打一次只会翻倍失败。 - **重启后继续**:两条队列分别落在数据库的 `summary_status` / `category_status` 上 (不是内存队列),程序退出时没做完的会在下次启动后接着做。 ## 快捷键 | 快捷键 | 行为 | |---|---| | `Ctrl+S` | 下载视口内最大的内容卡片(折叠回答先展开再抓取) | | `Ctrl+Shift+S` | 批量下载本页所有回答 / 文章;下载中再按一次即取消 | | `Ctrl+Shift+A` | 打开 AI 设置(服务商 / 摘要 / 分类) | | `Ctrl+K` | 聚焦本地收藏搜索框 | | `Ctrl+L` | 聚焦地址栏 | | `Ctrl+=` / `Ctrl+-` / `Ctrl+0` | 放大 / 缩小 / 重置缩放 | | `Ctrl+Shift+D` | 打印注入脚本自检信息(卡片数 / 按钮数 / 桥接状态)到控制台 | | `Alt+←` / `Alt+→` / `F5` | 后退 / 前进 / 刷新 | ## 数据目录 ```text ~/.zreader/ ├─ profile/ QWebEngineProfile 持久化(Cookie / LocalStorage / IndexedDB) ├─ cache/ HTTP 磁盘缓存 ├─ zreader.db SQLite(items / comments / assets / folders / tags / items_fts) ├─ assets//.jpg 本地化图片 ├─ offline/.html 离线阅读页面 ├─ exports/ 导出产物 └─ settings.json AI 设置(服务商 / Key / 模型),含明文 API Key,仅本机 ``` 可用环境变量 `ZREADER_HOME` 重定向整个数据目录。 ## 调试 ```powershell $env:ZREADER_DEBUG_PORT = "9222" uv run python main.py ``` 然后用 Chrome 打开 `http://127.0.0.1:9222`,在 DevTools 里直接调试 `window.__ZREADER__`。 终端默认只输出页面的 warning / error 级日志,以及 ZReader 自己的日志 (第三方脚本的 info 级 `console.log` 会淹没终端,例如 QQ 登录探测本机客户端端口失败的输出)。 需要看全部时: ```powershell $env:ZREADER_VERBOSE_JS = "1" ``` > QQ 网页登录会尝试连接本机 QQ 客户端的本地端口(`localhost.ptlogin2.qq.com:4301/4303/...`), > 没装或没启动 QQ 客户端时必然全部失败,随后自动降级为扫码登录。这是腾讯登录页自身的行为, > Chrome/Edge 上同样会出现,与本软件无关。 ## 知乎改版时的维护点 所有 DOM 选择器集中在 `app/resources/injector.js` 顶部的 `SEL` 常量对象中, 知乎改版只需改这一处,Python 侧零改动。 ## 打包(可选) ```powershell uv run pyinstaller --noconfirm --windowed --name ZReader ` --collect-all PySide6 --collect-all shiboken6 ` --add-data "app/resources;app/resources" ` main.py ```