ZhihuMCP
# zhihu-mcp
以读为主的知乎 MCP 服务器(stdio),统一读取:
- **知乎想法**:`https://www.zhihu.com/pin/{id}`
- **专栏文章**:`https://zhuanlan.zhihu.com/p/{id}`
- **回答**:`https://www.zhihu.com/question/{qid}/answer/{aid}`
- 一级评论(分页)、楼中楼回复(分页)
- 作者、正文、发布时间、赞数、评论数等元信息
- **用户当天动态发现**:按自然日拉取某用户发布的回答/文章/想法列表
- **问题搜索**:按关键词搜问题,定位「回答最多 / 浏览量最热」的那个(可按日期精确过滤)
兼容 **Claude Code CLI** 与 **Codex CLI**。不含点赞/关注等社交写操作;仅有的两个写工具是回答草稿/编辑(`zhihu_draft_answer`、`zhihu_edit_answer`),默认只存草稿或只预览,且受独立限频保护(间隔 ≥60s、每小时 ≤3 次),详见下方工具表。
## 工作原理
- Playwright 管理一个**独立持久化浏览器目录**(默认 `~/.zhihu-mcp/browser-profile`),不读取、不影响你日常浏览器的 Cookie。
- 默认使用系统 Chrome(`channel: "chrome"`,指纹更真实),失败时回退 Playwright 内置 Chromium。
- 数据获取分层:
1. 在已登录页面环境内同源调用知乎接口(评论、想法正文);
2. 接口失效/触发校验时,导航到内容页解析内嵌的 `js-initialData` JSON(文章正文的主路径,因 `/api/v4/articles` 有浏览器签名校验);
3. 最后回退 DOM 选择器解析。
- 评论接口的签名校验是**间歇性**的(实测同一请求时而 200 时而 10003):先自动重试;
一级评论在重试仍失败时会导航到内容页,拦截页面自身发出的带签名请求作为终极回退。
- 内置限速:相邻请求 ≥1.5s(含抖动)、每分钟 ≤20 次,可用环境变量调整。
- **Cookie 安全**:登录态只存在本地浏览器目录;不经由 MCP 返回、不写日志、`.gitignore` 已排除。
## 安装
```bash
cd ZhihuMCP
npm install
npm run build
# 若本机没有 Chrome,需要装内置浏览器:
# npx playwright install chromium
```
## 登录(首次使用)
推荐在终端登录(不受 MCP 工具超时限制):
```bash
npm run login
```
会弹出浏览器窗口,用知乎 App 扫码即可,完成后窗口自动关闭。也可以在对话里调用 `zhihu_login` 工具(需保证客户端工具超时 ≥3 分钟)。
登录过期时,任何工具会返回 `NOT_LOGGED_IN` 错误并提示重新登录。
## 接入 Claude Code CLI
方式一(命令行注册,作用于当前项目):
```bash
claude mcp add zhihu -- node /绝对路径/ZhihuMCP/dist/index.js
# 全局可用:claude mcp add --scope user zhihu -- node /绝对路径/ZhihuMCP/dist/index.js
```
方式二(项目 `.mcp.json`):
```json
{
"mcpServers": {
"zhihu": {
"command": "node",
"args": ["/绝对路径/ZhihuMCP/dist/index.js"]
}
}
}
```
如需在对话内使用 `zhihu_login`(等待扫码约 1-3 分钟),启动时调大工具超时:
```bash
MCP_TOOL_TIMEOUT=300000 claude
```
## 接入 Codex CLI
`~/.codex/config.toml` 增加:
```toml
[mcp_servers.zhihu]
command = "node"
args = ["/绝对路径/ZhihuMCP/dist/index.js"]
# 如需在对话内扫码登录,调大工具超时(秒):
tool_timeout_sec = 300
```
或使用命令行(较新版本):
```bash
codex mcp add zhihu -- node /绝对路径/ZhihuMCP/dist/index.js
```
## MCP 工具
| 工具 | 参数 | 说明 |
| --- | --- | --- |
| `zhihu_login` | 无 | 打开浏览器窗口扫码登录 |
| `zhihu_check_login` | 无 | 返回 `logged_in` 与用户名 |
| `zhihu_get_content` | `url` | 读取想法/文章/回答正文与元信息 |
| `zhihu_list_comments` | `url`, `cursor?`, `limit?`(≤20) | 分页读取一级评论(想法/文章/回答) |
| `zhihu_list_replies` | `comment_id`, `cursor?`, `limit?`(≤20) | 分页读取楼中楼回复 |
| `zhihu_list_followees` | `url_token?`, `cursor?`, `limit?`(≤20) | 分页读取「关注的人」,省略 `url_token` 查当前登录用户 |
| `zhihu_list_user_activity` | `url_token`, `date?`, `tz_offset_hours?`, `types?`, `max_items?` | 发现某用户在指定自然日(默认今天,北京时间)发布的回答/文章/想法列表 |
| `zhihu_search_questions` | `keyword`, `date?`, `limit?`(≤50) | 按关键词搜问题,返回按回答数降序列表并给出「回答最多」`most_answers` 与「浏览量最热」`hottest` 两个候选;传 `date` 按标题精确过滤到当天 |
| `zhihu_draft_answer` | `question_url`, `text`, `publish?` | **写操作**:把纯文本写入问题的回答,默认仅存草稿 |
| `zhihu_edit_answer` | `answer_url`, `text`, `mode?`, `confirm?` | **写操作**:编辑已发布回答,默认只预览、需 `confirm=true` 才改 |
### 写操作:`zhihu_draft_answer`(默认存草稿)
这是唯一的写工具,用于给知乎问题回答。与读工具不同,它会改变账号状态,因此有额外护栏:
- **默认只存草稿**(`publish` 省略或为 `false`):通过页面自动化把文本写入回答编辑器,触发知乎自动保存,**不点发布**。你需要在网页端/App「创作中心 → 草稿箱」或问题页检查后手动发布。`publish: true` 才会直接发布。
- **不逆向签名**:全程走页面自身的编辑器与请求(与真人操作同路径),不构造写接口签名、不对抗验证码。
- **防覆盖**:若该问题已有未发布草稿,或你已回答过该问题,工具会拒绝写入并报错,不会覆盖已有内容。
- **独立严格限频**:默认写操作间隔 ≥60s、每小时 ≤3 次(超限直接报 `RATE_LIMITED`,不排队)。自动化发布比阅读更易触发风控,**强烈建议低频、草稿优先、人工过目后再发**。
- **格式**:文本按换行分段写入,暂不渲染 Markdown(标题/加粗/列表会作为纯文本)。
- 返回 `mode`(`draft`/`published`)、`answer_url`(发布时)、`question_title`、`chars`。
命令行用法(读文本文件存草稿):
```bash
node scripts/draft.mjs "https://www.zhihu.com/question/123456" answer.txt # 存草稿
node scripts/draft.mjs "https://www.zhihu.com/question/123456" answer.txt --publish # 直接发布(谨慎)
```
### 写操作:`zhihu_edit_answer`(编辑已发布回答,两步确认)
编辑**已发布**的回答会改动线上公开内容,风险高于草稿,因此护栏更重:
- **默认只预览**(`confirm` 省略或 `false`):进入编辑器读取并返回当前线上内容和字数,**不做任何修改、不消耗写配额**。核对后带 `confirm: true` 才真正修改并点「提交修改」。
- **两种模式**:`mode: "append"`(默认)在原回答末尾追加,**保留原文与图片**;`mode: "replace"` 整篇替换——注意写入的是纯文本,**原回答的图片/加粗/列表等富文本会丢失**。
- **改前自动本地备份**:覆盖前把原文存到 `~/.zhihu-mcp/backups/answer-{id}-{时间}.txt`,改坏可找回。
- 只能编辑**本人发布**的回答(否则页面无「修改」入口,会报错)。
- 同样走写操作限频。
命令行用法:
```bash
node scripts/edit.mjs "https://www.zhihu.com/answer/123" update.txt # 预览当前内容(不改)
node scripts/edit.mjs "https://www.zhihu.com/answer/123" update.txt --confirm # 末尾追加并提交
node scripts/edit.mjs "https://www.zhihu.com/answer/123" full.txt --replace --confirm # 整篇替换并提交
```
### 统一返回结构
`zhihu_get_content`:
```json
{
"ok": true,
"content_type": "pin | article",
"id": "…",
"url": "…",
"title": "文章标题(想法为 null)",
"body": "正文纯文本",
"images": ["…"],
"author": { "id": "…", "url_token": "…", "name": "…", "headline": "…", "avatar_url": "…" },
"published_at": "ISO8601",
"updated_at": "ISO8601",
"vote_count": 0,
"comment_count": 0,
"source": "api | initial_data | dom"
}
```
`zhihu_list_comments` / `zhihu_list_replies`:
```json
{
"ok": true,
"comments": [
{
"id": "…",
"content": "评论纯文本",
"author": { "…": "…" },
"created_at": "ISO8601",
"like_count": 0,
"reply_count": 3,
"reply_to_author": { "…": "被回复者(楼中楼)" },
"is_author": true
}
],
"next_cursor": "下一页游标,null 表示无",
"has_more": true,
"total": 42
}
```
`zhihu_list_followees`:
```json
{
"ok": true,
"url_token": "被查询用户",
"users": [
{
"id": "…", "url_token": "…", "name": "…", "headline": "…",
"avatar_url": "…", "follower_count": 0, "answer_count": 0, "articles_count": 0
}
],
"next_cursor": "5",
"has_more": true,
"total": 10
}
```
翻页:首页不传 `cursor`;之后把上一页的 `next_cursor` 原样传入,直到 `has_more=false`。
`zhihu_list_user_activity`:
```json
{
"ok": true,
"url_token": "xu-ze-qiu",
"date": "2026-07-13",
"tz_offset_hours": 8,
"items": [
{
"type": "answer | article | pin",
"id": "…",
"url": "…(可直接喂给 zhihu_get_content / zhihu_list_comments)",
"title": "回答=问题标题;文章=标题;想法=null",
"excerpt": "摘要(≤140 字)",
"created_at": "ISO8601",
"voteup_count": 0,
"comment_count": 0
}
],
"counts": { "answer": 4, "article": 0, "pin": 0 },
"truncated": false
}
```
`items` 按发布时间倒序。三类内容按 `created` 降序拉取、翻到早于目标日即停止;某类达到 `max_items` 时 `truncated=true`(可能漏掉更早的当天内容,可调大 `max_items`)。
`zhihu_search_questions`:
```json
{
"ok": true,
"keyword": "如何看待2026年7月13日A股行情",
"date": "2026-07-13",
"questions": [
{
"id": "…",
"title": "…",
"url": "https://www.zhihu.com/question/{id}",
"answer_count": 159,
"follower_count": 174,
"visits_count": 485990,
"comment_count": 0,
"updated_at": "ISO8601"
}
],
"date_matched": 9,
"most_answers": { "…": "answer_count 最大的问题" },
"hottest": { "…": "visits_count 最大的问题" }
}
```
搜索为模糊匹配、会混入其它日期;传 `date` 会按标题(去空白后含「{年}年{月}月{日}日」)精确过滤到当天,`most_answers`/`hottest` 在命中集内选取(无命中回退全集)。`questions` 按回答数降序。「热度」以 `visits_count`(浏览量)为代理。
### 错误返回
```json
{ "ok": false, "error": "NOT_LOGGED_IN", "message": "…", "hint": "…" }
```
| 错误码 | 含义 |
| --- | --- |
| `NOT_LOGGED_IN` | 未登录或登录过期,需 `zhihu_login` / `npm run login` |
| `ANTI_CRAWLER` | 触发知乎风控校验,降低频率稍后重试;可 `ZHIHU_MCP_HEADFUL=1` 手动过校验 |
| `NOT_FOUND` | 内容不存在或已删除 |
| `RATE_LIMITED` | 请求过于频繁 |
| `INVALID_URL` / `INVALID_PARAM` | 链接或参数不合法 |
| `PARSE_ERROR` | 接口与 DOM 解析均失败(页面结构可能已变化) |
| `BROWSER_ERROR` | 浏览器无法启动 |
| `LOGIN_TIMEOUT` | 扫码等待超时或窗口被关闭 |
## 环境变量
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `ZHIHU_MCP_PROFILE_DIR` | `~/.zhihu-mcp/browser-profile` | 浏览器持久化目录 |
| `ZHIHU_MCP_BROWSER_CHANNEL` | `chrome` | 浏览器 channel,失败回退内置 Chromium |
| `ZHIHU_MCP_HEADFUL` | `0` | 置 `1` 始终有头运行(便于手动过风控) |
| `ZHIHU_MCP_MIN_INTERVAL_MS` | `1500` | 相邻请求最小间隔 |
| `ZHIHU_MCP_MAX_PER_MINUTE` | `20` | 每分钟最大请求数 |
| `ZHIHU_MCP_LOGIN_TIMEOUT_MS` | `180000` | 扫码等待超时 |
| `ZHIHU_MCP_NAV_TIMEOUT_MS` | `30000` | 页面导航超时 |
| `ZHIHU_MCP_WRITE_MIN_INTERVAL_MS` | `60000` | 写操作最小间隔 |
| `ZHIHU_MCP_WRITE_MAX_PER_HOUR` | `3` | 写操作每小时上限 |
## 测试
```bash
npm test # 单元测试(URL 解析、HTML 转文本、评论映射、错误分类)
npm run smoke # 冒烟:拉起 MCP 服务器,列工具、查登录态
npm run acceptance # 端到端验收(需已登录):想法/文章正文、评论分页、楼中楼、错误分类
```
验收说明:计划中的首个验收用例 `pin/2060006380807968738` 实测**已被知乎删除**
(接口 404、桌面页回落到首页信息流、评论区关闭仅残留计数 451),服务器对其正确返回
`NOT_FOUND`;验收脚本因此改用存活想法验证完整读取链路,并保留该链接验证错误分类。
## 已知限制
- 想法评论区被关闭/内容被删时,知乎接口会返回空数据但 `is_end` 恒为 false;
本服务器会终止翻页(`has_more=false`)并透出 `notice`(如「评论区已关闭」)。
- 楼中楼回复在知乎持续强制签名校验时只有重试兜底(一级评论有页面拦截回退)。
- 成员内容接口(`zhihu_list_user_activity` 用到的 answers/articles/pins)的签名校验也是间歇性的,已内置签名错误自动重试;若持续触发仍会返回 `ANTI_CRAWLER`。
- 回答/文章正文走页面 `js-initialData` 解析(对应接口有签名校验);`zhihu_list_user_activity` 返回的 `voteup_count` 对回答可能为 `null`(列表接口未下发),需要精确赞数时用 `zhihu_get_content` 读单条。
- `zhihu_draft_answer` 依赖问题页的回答编辑器 DOM;知乎改版可能需要更新按钮/编辑器选择器。文本不渲染 Markdown。
- 尚无「读取问题正文」工具(问题页正文未做成工具;回答正文已由 `zhihu_get_content` 支持)。
- 不做验证码绕过与风控对抗;触发校验时需人工在有头浏览器中处理。
- `zhihu_login` 需要图形界面;纯远程/无头环境请先在本地登录后同步 `~/.zhihu-mcp` 目录(注意其中含登录凭据,请勿提交仓库或外传)。
TDQS
Scored across 10 tools
Each tool has a clearly distinct purpose. zhihu_list_comments explicitly directs users to zhihu_list_replies for nested replies, and zhihu_get_content is separate from activity listing. No two tools appear to perform the same action.
All tool names follow the consistent pattern zhihu_<verb>_<object> (e.g., list_comments, get_content, draft_answer). Even the exception 'login' is a simple verb that fits the overall naming convention.
With 10 tools, the set is well-scoped for a Zhihu client, covering authentication, content retrieval, comment browsing, search, and answer creation. Each tool has a clear role without unnecessary bloat.
The set lacks a tool to read a question's full details (only answers/articles/thoughts are supported by zhihu_get_content), and there are no tools for commenting or publishing drafts directly. Agents can work around some gaps but question context is essential for many workflows.