zhihu — search, read & publish on Zhihu

SkillSearch

Interact 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.

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:

ScriptAuthCapabilities
scripts/search.pyZHIHU_DEVELOPER_TOKEN (Bearer)Search Zhihu, search the web, get hot topics
scripts/blog.pyZHIHU_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

GoalCommand
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 — use search command 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

GoalCommand
Who am Ipython3 "$BLOG" whoami
My latest articles + vote/comment countspython3 "$BLOG" articles --limit 20
My latest answers + like/favorite/comment countspython3 "$BLOG" answers --limit 20
Next page (any list)add --offset 20
One article's statspython3 "$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-only stops after saving a private draft (safe — nothing public).
  • Without --draft-only, the article is published publicly under the user's name. Default to --draft-only unless the user clearly asked to go live.
  • Images are auto-hosted. Zhihu strips any <img> whose src is not on its own CDN, so on --confirm the CLI re-uploads every external image (HTML <img src> and Markdown ![](url), plus data: URIs) to Zhihu's image service and rewrites the URLs first — images already on *.zhimg.com are left untouched. The result reports images: {found, rehosted, failed}; the dry-run reports images_found. Pass --no-images to 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 --confirmdry-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 --confirm returns a clear error telling you to use edit-answer instead — find the existing answer id with answers or question <qid> (which reports my_answer_id).
  • --draft-only is 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-answer has no private mode — the answer is already public, so any --confirm edit is live immediately. It preserves the answer's current repost setting unless you override with --repost allowed|disallowed.
  • 转载授权 (--repost allowed|disallowed): new answers default to disallowed (don't grant repost rights); editing keeps the current setting.
  • Images are auto-hosted for answers too — same behavior and --no-images flag as publish.

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/403 means 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 --platform switch 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