你的老板从身后走过。你的屏幕上没有五彩斑斓的网页,没有鬼鬼祟祟最小化的浏览器窗口 — 只有一个全屏终端,黑底白字,卷动着密密麻麻的日志和命令行输出。你眉头微皱,时而敲几下键盘,一副 debug 到关键处的样子。
实际上你在刷知乎热榜。
Sylva 把知乎完整地搬进终端,让每一次摸鱼都像一次严肃的工程工作。
启动后是一个干净的单列 Feed 列表:顶部标题栏,中间条目,底部状态栏。热榜和推荐流各有 50 条内容,每一条显示标题和摘要;选中高亮,Enter 进入回答正文;Tab 一键切换热榜和推荐流;c 展开评论区树形阅读。全程手指不离键盘,眼睛不离开屏幕 — 从远处看,就像在 vim 里编辑配置文件。
| 模块 | 说明 |
|---|---|
| 热榜 | 知乎实时热榜,50 条全量展示,匿名可浏览 |
| 推荐流 | 个性化推荐,登录后自动识别,支持翻页加载 |
| 不感兴趣 | 推荐流中按 d 屏蔽当前条目,自动去重,下次刷新不再出现 |
| 回答阅读 | 全文分页展示,支持上下条切换、翻页加载更多 |
| 评论区 | 树形递归展开根评论和子评论,自动分页拉取 |
| 全文搜索 | 支持问题、回答、文章三种类型检索 |
| Cookie 登录 | 登录态本地持久化(SQLite),不出机器 |
| 深色配色 | 终端原生深色方案,长时间阅读不刺眼 |
需要 Rust 1.80 及以上版本。
git clone https://github.com/iiabc/Sylva.git
cd Sylva
cargo build --release编译后在终端直接运行:
./target/release/sylva或安装到系统路径:
cargo install --path .
sylva开启调试日志(写入 ~/.local/share/sylva/debug.log):
sylva -d热榜可匿名浏览。推荐流需要登录态。
- 在 Chrome 中打开 zhihu.com 并登录
- 按
F12打开开发者工具,进入 Application → Cookies → zhihu.com - 找到
z_c0这个 Cookie,复制它的值 - 在 Sylva 中按
L,粘贴如下格式:
z_c0=2|1:0|10:10abcdef...
Cookie 只存在你本地的 SQLite 数据库里(路径:~/.local/share/sylva/sylva.db),不会离开你的机器。
| 按键 | 功能 |
|---|---|
j / ↓ |
向下移动 |
k / ↑ |
向上移动 |
Enter |
打开选中项 |
q / Esc |
返回上级;首页则退出 |
Ctrl+C |
强制退出 |
/ |
搜索知乎 |
r |
刷新当前页面 |
L |
Cookie 登录设置 |
? |
帮助 |
| 按键 | 功能 |
|---|---|
Tab |
热榜 / 推荐流切换 |
l |
加载更多(推荐流翻页) |
d |
不感兴趣(屏蔽当前推荐条目) |
| 按键 | 功能 |
|---|---|
n / → |
下一条回答 |
p / ← / h |
上一条回答 |
c |
查看评论区 |
g |
跳到顶部 |
G |
跳到底部 |
l |
加载更多回答 |
| 按键 | 功能 |
|---|---|
j / ↓ |
向下滚动 |
k / ↑ |
向上滚动 |
从外到内四层,遵循 herdr 模式 — 命令式渲染循环 + crossterm 输入轮询 + 事件驱动状态变更:
main.rs CLI 入口,clap 参数解析,tracing 日志初始化
↓
app.rs 应用核心:App 状态机、模式栈、事件循环、异步 fetch task
↓
┌──────────────┬────────────────┬───────────────┐
│ ui/ │ zhihu/ │ store/ │
│ TUI 渲染 │ 知乎数据层 │ SQLite 持久化│
│ │ │ │
│ ratatui │ reqwest HTTP │ rusqlite │
│ crossterm │ scraper 解析 │ WAL 模式 │
│ 自定义组件 │ auth/cookie │ 单连接 │
└──────────────┴────────────────┴───────────────┘
- TUI 层 (
ui/) — ratatui + crossterm。命令式渲染循环每 250ms tick 一次,poll 键盘/鼠标事件后直接修改 App 状态,再调用terminal.draw()全量重绘。键盘绑定支持 TOML 配置文件覆盖。 - 数据层 (
zhihu/) — reqwest HTTP 客户端封装。处理 User-Agent 伪装、频率控制(随机间隔避免触发反爬)、429 自动退避重试、JSON 格式兼容(知乎 API 的字段类型不稳定)。 - 存储层 (
store/) — rusqlite 单文件数据库,WAL 模式,单连接。管理 Cookie 持久化、API 响应缓存、用户设置、屏蔽条目列表。 - 事件通道 (
event.rs) — tokio mpsc channel 连接异步 fetch task 和主渲染循环。Fetch task 在独立协程中跑 HTTP 请求,完成后通过 channel 发送AppEvent给主循环消费。
src/
main.rs CLI 入口,tokio runtime 引导
app.rs 应用核心:模式栈、状态机、事件处理、fetch task
config.rs TOML 配置管理(user_agent、超时、键位映射)
event.rs 事件通道定义(AppEvent、FetchCommand、mpsc channel)
session.rs 会话管理
renderer/ 内容渲染(HTML → 终端文本)
store/ SQLite 持久化
db.rs 连接管理
cookies.rs Cookie 存取
cache.rs 响应缓存
dismiss.rs 屏蔽条目
settings.rs 用户设置
ui/ TUI 层
render.rs 渲染主入口
layout.rs 布局计算
theme.rs 配色方案
keybindings.rs 键位绑定(支持配置文件覆盖)
screens/ 视图定义(Feed、Answer、Comment、Search)
widgets/ 可复用组件(feed_list、status_bar、title_bar)
zhihu/ 知乎数据层
client.rs HTTP 客户端(User-Agent、频率控制、429 重试)
models.rs 数据模型
parser.rs HTML → 文本 / Markdown 转换
feed.rs 热榜 & 推荐流 API
answer.rs 回答 & 问题 API
comment.rs 评论 & 子评论 API
search.rs 搜索 API
auth.rs 登录态验证
cargo build # 编译 debug 版
cargo build --release # 编译 release 版(优化体积,LTO + strip)
cargo run # 编译并运行
cargo test # 运行测试
cargo clippy # 静态检查
cargo fmt --all -- --check # 格式检查- 在
src/ui/screens/新建屏幕文件,实现渲染函数 - 在
src/app.rs的AppMode枚举中注册新模式 - 在
handle_event()的 match 分支中添加路由 - 在
src/ui/render.rs中添加渲染分发
- 在
src/zhihu/中添加对应的Client方法 - 如需新响应格式,在
models.rs中定义结构体(标注#[derive(Deserialize)]) - 添加
To*()转换方法,将 API 响应转为内部模型
欢迎提交 PR。动手之前建议先开 Issue 讨论,避免白费力气。
代码规范:
cargo fmt标准格式化cargo clippy零警告- 公开 API 必须有文档注释(
///) - 错误使用
thiserror派生或anyhow::Context附加上下文 - 数据库查询用
Option/Result区分"未找到"和真正的错误 - 资源(handle、guard)在
Drop实现中释放 - 正则表达式提取为
static/Lazy,不在循环内重复编译
提交信息格式:
<type>: <简短描述>
<详细说明(可选)>
类型:feat / fix / refactor / docs / style / test / chore
Sylva 是一个个人学习项目,与知乎公司无任何关联。请合理使用,遵守知乎用户协议,不要用于批量抓取或任何破坏性行为。
另外,摸鱼有风险,演技需精湛。