知乎专栏发文章接口 (Zhihu Column Publish API)
从真实浏览器 HAR(DevTools 录制 zhuanlan.zhihu.com 发布一篇文章)逆向得出。发一篇文章不是单接口,而是 建草稿 → 自动保存 → 加话题 → 发布 四步。
四步流程
| 步骤 | 接口 | 说明 |
|---|---|---|
| 1 建草稿 | POST https://zhuanlan.zhihu.com/api/articles/drafts | Body: {"content": "<p>HTML…</p>", "table_of_contents": false, "delta_time": 0, "can_reward": false}。返回完整草稿对象,含文章 id。 |
| 2 自动保存 | PATCH https://zhuanlan.zhihu.com/api/articles/{id}/draft | 编辑过程中反复调用;delta_time 为编辑耗时(秒)。 |
| 3 加话题 | POST https://zhuanlan.zhihu.com/api/articles/{id}/topics ×N | Body 直接传整个 topic 对象:{name, url, excerpt, introduction, followersCount, avatarUrl, type, id, questionsCount}。每个话题调一次。 |
| 4 发布 | POST https://www.zhihu.com/api/v4/content/publish | 真正的"发文章"。见下。 |
发布接口详情
- host 是
www.zhihu.com,但origin/referer是zhuanlan.zhihu.com(sec-fetch-site: same-site)——编辑页在 zhuanlan 域,发布 API 在 www 域,跨子域调用。 - Body 是嵌套 JSON,
pc_business_params和响应data.result都是字符串化的 JSON(双层转义),解析要json.loads两次:
{
"action": "article",
"data": {
"publish": { "traceId": "时间戳,random-uuid" },
"extra_info": {
"publisher": "pc",
"pc_business_params": "{\"content\":\"<h3>…HTML…</h3>\",\"title\":\"…\",\"commentPermission\":\"anyone\",\"disclaimer_type\":\"none\",\"disclaimer_status\":\"close\",\"table_of_contents_enabled\":false,\"canReward\":false}"
},
"draft": { "disabled": 1, "id": "2071994212195759032", "isPublished": false },
"commentsPermission": { "comment_permission": "anyone" },
"creationStatement": { "disclaimer_type": "none", "disclaimer_status": "close" },
"contentsTables": { "table_of_contents_enabled": false },
"commercialReportInfo": { "isReport": 0 },
"appreciate": { "can_reward": false, "tagline": "" },
"hybrid": { "html": "<h3>…</h3>…", "textLength": 2365 },
"title": { "title": "…" }
}
}响应 200:{"message": "success", "data": {"result": "{\"publish\":{…id…}}"}}。
老接口发布链路(已验证可跑通,无需动态签名)
HAR 里记录的是新接口 POST /api/v4/content/publish(要 x-zse-96),但逆向工具 zhihu-cli 用的是老接口链路,全程只要 cookie + x-xsrf-token,2026-08 实测三步全 200、文章真实发布成功:
1. POST https://zhuanlan.zhihu.com/api/articles/drafts # 建【空】草稿,json={},返回 id
2. PATCH https://zhuanlan.zhihu.com/api/articles/{id}/draft # 填 {"title","content"}(content 是 HTML)
3. PUT https://zhuanlan.zhihu.com/api/articles/{id}/publish # 发布,json={"column":null,"commentPermission":"anyone"}关键点:
- 建草稿传空 body(老接口),正文在 PATCH 才填;新接口是建草稿时就带全文。
- 发布请求本身不含正文——正文已在草稿里。
- 全程只依赖
x-xsrftoken(= cookie 里的_xsrf)+z_c0。不需要 x-zse-93/96。 - 删除文章:
DELETE https://zhuanlan.zhihu.com/api/articles/{id}(200)。 - 读已发布文章(验证用):
GET https://zhuanlan.zhihu.com/api/articles/{id}返回 200 且含 content;但GET www.zhihu.com/api/v4/articles/{id}读接口会 403(读接口风控更严,不影响发布)。
发布到专栏(column)
老接口 PUT /publish 的 column 字段必须传完整专栏对象,传字符串或裸 id 会 500:
# 正确
{"column": {"id": "c_1943647118402248830", "title": "计算机基础",
"type": "column", "url": "http://www.zhihu.com/api/v4/columns/c_1943647118402248830"},
"commentPermission": "anyone"}
# 错误 → 500
{"column": "c_1943647118402248830", ...}
{"column": "1943647118402248830", ...}2026-08 实测:这样发布后 GET 文章,column 字段返回完整对象,文章确实挂在专栏下。GET https://www.zhihu.com/api/v4/columns/{c_id} 可取专栏 title/id。
顺带修正:新接口 POST /api/v4/content/publish 其实不需要 x-zse-96——纯 requests + 有效 cookie 直接调用返回 200 success(之前"要签名"的判断是错的)。但新接口在 data 里加 column 也带不出来(返回 null),挂专栏只有老接口传完整对象这一条路走通。
列出自己的专栏
GET https://www.zhihu.com/api/v4/members/{url_token}/column-contributions?offset=0&limit=50&include=data[*].column.articles_count — 返回 data[].column 数组,含 id/title/articles_count。url_token 用 GET /api/v4/me 取。
表格标签(知乎编辑器专用)
知乎编辑器(ProseMirror)不认标准 <table>——markdown 库转出的 <table><thead>… 发布后会被服务端静默丢弃。必须转成知乎自定义格式:
<table data-draft-node="block" data-draft-type="table"
data-size="normal" data-row-style="normal"><tbody>
<tr><th>表头1</th><th>表头2</th></tr>
<tr><td>数据</td><td>数据</td></tr>
</tbody></table>即:把 markdown 库输出的 <thead><tr> 合并进 <tbody><tr>,去掉 thead/tbody 嵌套,加上三个 data-* 属性。代码块 <pre>、粗体、标题层级则无需转换,直接可用。
反爬签名(重放难点)
- 发布接口要求
x-zse-93: 101_3_3.0+x-zse-96: 2.0_…+x-zst-81: 3_2.0aR_…三个签名头 + cookiez_c0(登录凭证)+x-xsrftoken。 x-zse-*由页面 JS 按 cookie / UA / 时间实时计算,curl 无法直接重放;带z_c0登录 cookie 也不够。- 实测签名头稳定性(HAR 里逐条比对):
x-zst-81会话内静态(16 条请求同一值,可复用);x-zse-93是版本号,静态;x-zse-96每次请求都不同(7 条请求 7 个值),绑定请求内容/时间,不可复用。 - 自动保存(PATCH draft)和建草稿(POST drafts)只验
x-zst-81+x-xsrf-token,只用静态 auth 就能直接发请求;加话题 / 发布必须每次重算x-zse-96。 - cookie 里的
__utma/__utmz(GA)、Hm_lvt_*(百度统计)等是纯统计埋点,对请求无作用。 - 坑:Chrome DevTools 导出的 HAR 会把请求头的 cookie 整条剥掉(98 条请求 0 条带 cookie 头),xsrf/zst81 还在;cookie 要从「复制为 cURL」里取。
坑
- 正文 title/content 在发布体里出现多处(
pc_business_params、hybrid.html、title),以pc_business_params内为准。 hybrid.html是前端渲染好的正文,与content基本一致;发布成功后服务端会给段落加data-pid。- 先建草稿拿到 id,发布体
draft.id引用它,isPublished: false表示草稿态。
关联:cURL (Client URL 命令行请求工具)、Web 登录鉴权 (Session, Cookie, Bearer, JWT)
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web