zhihu — search, read & publish on Zhihu
SkillSearchInteract with Zhihu (zhihu.com) — browse hot questions, read answers, search content, view topics, check user profiles. Use this skill whenever the user mentions Zhihu, 知乎, zhihu.com, wants to browse Chinese Q&A content, read Zhihu answers, search Zhihu questions, check Zhihu hot list, or look up Zh
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the zhihu — search, read & publish on Zhihu skill
What this skill tells your AI
The instructions your AI receives, as published by sigcli/sigcli in skills/zhihu/SKILL.md and read by ahel’s review.
Two authentication layers, two scripts:
| Script | Auth | Capabilities |
|---|---|---|
scripts/search.py | ZHIHU_DEVELOPER_TOKEN (Bearer) | Search Zhihu, search the web, get hot topics |
scripts/blog.py | ZHIHU_COOKIES (login cookie) | Read/write own articles & answers |
No browser, no third-party deps — just urllib.
The connector injects credentials as env vars:
ZHIHU_DEVELOPER_TOKEN— Zhihu Developer Platform access secret (Bearer token). Used for search and hot-list queries. Secret — never echo or print it.ZHIHU_COOKIES— a JSON array of{name, value, domain, path, ...}cookies. Used for reading/writing the user's own content. Secret — never echo or print it.
Locate the scripts first (every Bash block)
The connector sets $SKILL_DIR to this skill's directory, so the scripts live at
$SKILL_DIR/scripts/. Do NOT hard-code python3 $SKILL_DIR/scripts/… directly.
If more than one skill was loaded in the same turn, $SKILL_DIR can point at the
other skill and the call fails with No such file or directory (this is the #1
cause of a Zhihu run silently not finishing). Resolve the path defensively at the
top of every Bash block — each Bash call is a fresh shell, so the variable does
not carry over:
# blog.py is unique to this skill, so it anchors zhihu's dir even if $SKILL_DIR is wrong.
ZDIR="$SKILL_DIR"; [ -f "$ZDIR/scripts/blog.py" ] || ZDIR=$(find /tmp -maxdepth 8 -path '*/skills/*/scripts/blog.py' 2>/dev/null | head -1 | sed 's#/scripts/blog.py##')
[ -f "$ZDIR/scripts/blog.py" ] || { echo "zhihu scripts not found (SKILL_DIR=$SKILL_DIR) — is the skill loaded?" >&2; exit 1; }
SEARCH="$ZDIR/scripts/search.py"; BLOG="$ZDIR/scripts/blog.py"
Then use "$SEARCH" / "$BLOG" (always quoted) as shown below.
Search CLI (search.py)
scripts/search.py — search Zhihu and the web. Requires
only ZHIHU_DEVELOPER_TOKEN (no cookies needed).
# Resolve the scripts first (see "Locate the scripts first" above) — robust to $SKILL_DIR.
ZDIR="$SKILL_DIR"; [ -f "$ZDIR/scripts/blog.py" ] || ZDIR=$(find /tmp -maxdepth 8 -path '*/skills/*/scripts/blog.py' 2>/dev/null | head -1 | sed 's#/scripts/blog.py##')
SEARCH="$ZDIR/scripts/search.py"
# Search Zhihu content (站内搜索) — questions, answers, articles
python3 "$SEARCH" search "Python 爬虫"
python3 "$SEARCH" search "Python 爬虫" --count 5
# Search the entire web (全网搜索) — all indexed sites
python3 "$SEARCH" global "AI Agent"
python3 "$SEARCH" global "AI Agent" --count 15
# Filter by site or time
python3 "$SEARCH" global "React" --filter 'host=="github.com"'
python3 "$SEARCH" global "新闻" --filter 'publish_time>=1720000000'
python3 "$SEARCH" global "技术" --filter 'host=="github.com" AND publish_time>=1720000000'
python3 "$SEARCH" global "实时新闻" --db realtime
# Get Zhihu trending topics (热榜)
python3 "$SEARCH" hot
python3 "$SEARCH" hot --limit 10
Search commands
| Goal | Command |
|---|---|
| Search Zhihu (max 10 results) | python3 "$SEARCH" search "<query>" --count N |
| Search entire web (max 20) | python3 "$SEARCH" global "<query>" --count N |
| Filter by site | --filter 'host=="example.com"' |
| Filter by time | --filter 'publish_time>=<unix_ts>' |
| Search only realtime/static index | --db realtime or --db static |
| Zhihu trending topics (max 30) | python3 "$SEARCH" hot --limit N |
Search result fields
zhihu_search returns: title, type (Article/Answer), content_id, url, excerpt, vote_up (赞同), comments, author, authority level, edit_time.
global_search returns the same fields plus has_more indicator. The url
includes utm tracking params from Zhihu's platform.
hot_list returns: rank, title, url, summary, thumbnail.
global_search Filter syntax
host=="example.com"— filter by domain (note:host=="zhihu.com"not supported — usesearchcommand instead)publish_time>=1720000000— filter by publish time (unix seconds)- Logical operators:
AND,OR(must be uppercase) - Parentheses for grouping:
(host=="a.com" OR host=="b.com") AND publish_time>=T
Blog CLI (blog.py)
The skill ships scripts/blog.py — self-contained, stdlib only.
Requires ZHIHU_COOKIES (login cookie).
# Resolve the scripts first (see "Locate the scripts first" above) — robust to $SKILL_DIR.
ZDIR="$SKILL_DIR"; [ -f "$ZDIR/scripts/blog.py" ] || ZDIR=$(find /tmp -maxdepth 8 -path '*/skills/*/scripts/blog.py' 2>/dev/null | head -1 | sed 's#/scripts/blog.py##')
BLOG="$ZDIR/scripts/blog.py"
# Read (run directly)
python3 "$BLOG" whoami # who is logged in
python3 "$BLOG" articles --limit 20 # my published articles + stats
python3 "$BLOG" article <article-id> # one article's details + stats
python3 "$BLOG" answers --limit 20 # my published answers + stats
python3 "$BLOG" answer <answer-id> # one answer's details + stats
python3 "$BLOG" question <question-id> # a question's info + whether I answered it
Verify the connection first
ZDIR="$SKILL_DIR"; [ -f "$ZDIR/scripts/blog.py" ] || ZDIR=$(find /tmp -maxdepth 8 -path '*/skills/*/scripts/blog.py' 2>/dev/null | head -1 | sed 's#/scripts/blog.py##')
python3 "$ZDIR/scripts/blog.py" whoami
# → {"id": "...", "name": "<the connected display name>", "url_token": "<the connected handle>", ...}
On a 401/403 the cookie is expired — tell the user to reconnect at
https://studio.acedata.cloud/console/connectors (re-capture with the ACE
extension). Do not retry in a loop.
Reading recipes
| Goal | Command |
|---|---|
| Who am I | python3 "$BLOG" whoami |
| My latest articles + vote/comment counts | python3 "$BLOG" articles --limit 20 |
| My latest answers + like/favorite/comment counts | python3 "$BLOG" answers --limit 20 |
| Next page (any list) | add --offset 20 |
| One article's stats | python3 "$BLOG" article <id> |
| One answer's stats (incl. 赞同 voteup) | python3 "$BLOG" answer <id> |
| A question's info + my answer id (if any) | python3 "$BLOG" question <id> |
Article stats: voteup_count (赞同), comment_count (评论). Zhihu does not
expose per-article read counts on these endpoints.
Answer stat caveat: the answers list endpoint does not return
voteup_count (赞同) — it only exposes like_count (喜欢), favorite_count
(收藏) and comment_count. For the authoritative 赞同 count of an answer, call
answer <id> (the single-answer endpoint returns voteup_count). question <id>
reports already_answered + my_answer_id so you know whether to use
answer-question (new) or edit-answer (update).
Publishing — GATED (dry-run unless trailing --confirm)
publish writes to the user's real account. Without a trailing --confirm it
dry-runs (prints what it would do, changes nothing). --confirm is honored
only as the last argument, so a title/content containing "--confirm" can
never silently go live. Always show the dry-run to the user, get an explicit
"yes", then re-run with --confirm last.
# Content is HTML. For Markdown, convert to HTML first (e.g. `pandoc -f gfm -t html`).
python3 "$BLOG" publish --title "标题" --content-file article.html # dry-run
python3 "$BLOG" publish --title "标题" --content-file article.html --draft-only --confirm # save a private draft
python3 "$BLOG" publish --title "标题" --content-file article.html --confirm # PUBLIC, goes live
--draft-onlystops after saving a private draft (safe — nothing public).- Without
--draft-only, the article is published publicly under the user's name. Default to--draft-onlyunless the user clearly asked to go live. - Images are auto-hosted. Zhihu strips any
<img>whosesrcis not on its own CDN, so on--confirmthe CLI re-uploads every external image (HTML<img src>and Markdown, plusdata:URIs) to Zhihu's image service and rewrites the URLs first — images already on*.zhimg.comare left untouched. The result reportsimages: {found, rehosted, failed}; the dry-run reportsimages_found. Pass--no-imagesto skip this. So you can hand the CLI HTML/Markdown with normal public image URLs and the pictures survive.
Answering questions — GATED (dry-run unless trailing --confirm)
Two write commands cover the question/answer side. Both gate exactly like
publish: no trailing --confirm → dry-run; --confirm is honored only
as the last argument. Always show the dry-run, get an explicit "yes", then
re-run with --confirm last.
# Content is HTML (same as articles). For Markdown, convert to HTML first.
# Post a NEW answer to a question
python3 "$BLOG" answer-question --question <qid> --content-file ans.html # dry-run
python3 "$BLOG" answer-question --question <qid> --content-file ans.html --draft-only --confirm # PRIVATE draft (safe)
python3 "$BLOG" answer-question --question <qid> --content-file ans.html --confirm # PUBLIC, goes live
# Edit an EXISTING answer (replaces its live, public content)
python3 "$BLOG" edit-answer --id <answer-id> --content-file ans.html # dry-run
python3 "$BLOG" edit-answer --id <answer-id> --content-file ans.html --confirm # overwrites live answer
- One answer per question. Zhihu allows a single answer per user per
question. If the user already answered,
answer-question --confirmreturns a clear error telling you to useedit-answerinstead — find the existing answer id withanswersorquestion <qid>(which reportsmy_answer_id). --draft-onlyis the safe path for new answers — it saves a private draft on the question (nothing public). The user reviews it on Zhihu, then you re-run without--draft-only(with--confirm) to publish. Prefer this unless the user clearly asked to go live immediately.edit-answerhas no private mode — the answer is already public, so any--confirmedit is live immediately. It preserves the answer's current repost setting unless you override with--repost allowed|disallowed.- 转载授权 (
--repost allowed|disallowed): new answers default todisallowed(don't grant repost rights); editing keeps the current setting. - Images are auto-hosted for answers too — same behavior and
--no-imagesflag aspublish.
Gotchas — surface before the user is surprised
- This is the user's real Zhihu account. Confirm before any publish / answer / edit; reading exposes their own private drafts.
- Cookie expiry: Zhihu cookies are short-lived. A
401/403means reconnect at studio.acedata.cloud/console/connectors — never loop-retry. - ToS: cookie automation is against most platforms' terms. This only ever acts on the user's own account with their own captured cookie; the user owns that risk. Never use it to scrape other people's content at scale.
- Never print
ZHIHU_COOKIES— it is full account access. - Scope: Zhihu only. Other Chinese platforms (掘金 / CSDN / …) ship as their
own per-platform skills (e.g.
csdn,juejin), each with its own connector — not a--platformswitch here.
Record the output
After you successfully publish and obtain the live result URL, call the built-in
publish_artifact tool ONCE so the user can track this deliverable in My Outputs:
publish_artifact(kind="article", channel="zhihu", title="<title>", url="<the REAL returned URL>", status="delivered")
Use the real returned URL — never fabricate one. Call it once per published item,
only after delivery is confirmed; skip it (or use status="failed") if publishing failed.
See _shared/artifacts.md.
Signals
- GitHub stars
- 290
- Forks
- 24
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
zhihu- Source
- github.com/sigcli/sigcli