📑 Contents Overview / TL;DR 为什么选聚合站 逆向过程(方法学) API 端点与参数 签名机制真相 错误码与常见坑 平台 tab 结构 可复用脚本 References

1. Overview / TL;DR

rebang.today(今日热榜)是一个多平台热榜聚合站,聚合了小红书、微博、知乎、 B 站、抖音、36氪等平台的热搜/热榜。目标是从它的前端产物中逆向出 小红书热榜的正确抓取方式——不依赖浏览器自动化,直接用 curl 打接口。

💡

核心结论:接口是 GET https://rebang.today/api/latest-list, 小红书热榜的正确参数为 tab=xiaohongshu&sub_tab=hot-search&page=1&page_size=50—— 无需任何签名,带浏览器 UA 裸请求即可拿到完整 JSON。

关键事实
API 端点/api/latest-list(同源路径,非 api.rebang.today 子域)
小红书参数tab=xiaohongshu + sub_tab=hot-search(子榜「热点」)
单页条数实际最多 20 条(schema 上限 50,has_more=false 即榜单结束)
签名机制存在(X-Sign / X-Timestamp / X-Nonce / X-Version),但密钥不在客户端 → 无需签名
错误码1001 invalid request parameter · 1025 榜单不存在 · 1026 快照过期

2. 为什么选聚合站

直接抓小红书官方热榜接口需要处理签名、cookie、风控等一堆问题; 聚合站已经把这些脏活做完了,而且天然覆盖多个平台,一个接口全家桶。

官方接口(xhs)

需要签名参数(x-s / x-t 等)、可能要求登录 cookie、有风控与频控, 接口路径不公开、经常变动——逆向成本高。

聚合站(rebang.today)

前端是标准 Next.js/React 产物,接口逻辑在 JS bundle 里可读; 一个端点覆盖 N 个平台;无需登录、无需签名,纯 curl 即可。

ℹ️

代价是数据经过聚合站二次加工(热度单位、排序、标签),且接口随时可能改版。 但作为「快速获取热榜话题」的场景,性价比远高于啃官方接口。

3. 逆向过程(方法学)

整体思路:下载目标站点全部前端产物 → 用 grep/python 从 bundle 里提取 schema 与配置 → 构造请求验证。 全程不需要打开浏览器,省 token、可脚本化:

  1. 1 抓首页 HTML。 curl -s https://rebang.today/ -o /tmp/rebang.html。 Next.js 的 RSC payload 里直接内嵌了全部 tab 配置(含平台 key、id、子榜结构、品牌色)。
  2. 2 下载全部 JS chunk。 从 HTML 里提取 _next/static/chunks/*.js 脚本 URL,逐个下载到 /tmp/rbjs/ (共 35 个)。接口路径、zod schema、签名逻辑都在这些 bundle 里。
  3. 3 grep 提取 schema 与配置。latestListPROXY_PATHNEXT_PUBLIC_*latestListQuerySchema 等关键词,定位参数校验逻辑与代理路径。
  4. 4 构造请求验证。 用 curl 直接打 /api/latest-list,根据错误码(1001/1025)迭代参数,直到返回 200。
关键 grep 命令(从 bundle 提取信息)
# 定位「小红书」tab 配置(RSC payload 里 key/id/品牌色)
grep -o '"key":"[a-z-]*"' /tmp/rebang.html | sort -u

# 找接口路径与 schema(chunk 里 latestListQuerySchema)
grep -o 'latestListQuerySchema[^;]\{0,120\}' /tmp/rbjs/*.js | head

# 找代理路径配置
grep -o 'PROXY_PATH:"[^"]*"' /tmp/rbjs/*.js | sort -u

# 环境变量清单(确认是否有签名密钥暴露)
grep -o 'NEXT_PUBLIC[A-Z_]*' /tmp/rbjs/*.js | sort -u
🎯

彩蛋:分析过程中曾定位神秘数字 905020——最终发现它只是某个 process.env polyfill 模块入口调用的首个实参,与业务无关。 逆向时很容易被这类「看起来有意义的数字」带偏,先判断它所在的模块角色再深挖。

4. API 端点与参数

4.1 参数 schema(从 bundle 中 latestListQuerySchema 还原)

参数类型说明
tabstring必填。平台 key,如 xiaohongshu / weibo / zhihu。用数字 id(如 100)会报 1001
sub_tabstring平台内子榜 key。小红书是 hot-search(「热点」);父 tab 没有顶层榜单,漏了它必报 1025
pagenumber页码,从 1 开始
page_sizenumber≤ 50(超了报 1001)。实测每榜最多返回 20 条
snapshot_idstring翻页令牌(stat: 前缀的 base64),page>1 时必需;秒级过期
✅ 实测成功的请求
curl -s 'https://rebang.today/api/latest-list?tab=xiaohongshu&sub_tab=hot-search&page=1&page_size=50' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' \
  -H 'Accept: application/json' | python3 -m json.tool | head -60
响应结构(code + data.list)
{
  "code": 200,
  "data": {
    "list": [
      {
        "title": "用万能旅行拍照姿势美美出片",
        "extra_info": { "display_text_items": [{ "text": "920.8w" }] },
        "www_url": "https://www.xiaohongshu.com/search_result?keyword=...",
        "hot": true
      }
    ],
    "next_refresh_time": 1786811108,
    "snapshot_id": "stat:eyJ0YWIiOiJ4aWFvaG9uZ3NodSIs...",
    "has_more": false
  }
}

注意热度值在 extra_info.display_text_items[].text 里(形如 "920.8w",w = 万),hot: true 表示平台置顶/热推标签。

5. 签名机制真相

前端 bundle 里确实存在一套 HMAC-SHA256 签名机制:请求需要带 X-Sign / X-Timestamp / X-Nonce / X-Version 四个头,服务端校验。这是不少 Next.js 站点的标准防刷配置。

但在客户端产物里搜 NEXT_PUBLIC_API_SIGN_SECRET 等密钥变量—— 一个都没有。签名密钥只存在于服务端,浏览器端实际发的请求也不带签名头

结论:裸请求即可,无需计算签名。判断方法很简单——在浏览器 DevTools 里看真实请求头, 或搜 bundle 里是否有 NEXT_PUBLIC_* 密钥变量;两者都没有 → 不用签名。

⚠️

若将来接口开始校验签名(返回 401/403 或新的错误码),需要重新评估:要么服务端下发密钥, 要么换数据源。本笔记的抓取脚本会在那时失效——这是所有逆向接口的宿命。

6. 错误码与常见坑

错误码消息含义 / 触发条件
1001invalid request parameter参数不合法:page_size>50、tab 用了数字 id、参数拼写错误
1025榜单不存在该 tab 没有顶层榜单(小红书必须加 sub_tab=hot-search)
1026榜单快照已过期snapshot_id 过期:必须拿到 snapshot 后立刻翻页(同一秒内)

血泪坑位清单(全部实测踩过)

  • 父 tab 没有榜单!小红书 tab 下只有子榜「热点」(hot-search),不带 sub_tab 必报 1025「榜单不存在」——这是最隐蔽的坑。
  • page_size 有上限:传 100 直接报 1001。schema 上限 50,实际每榜 20 条封顶。
  • tab 用英文字符串:RSC payload 里虽然给了 id=100,但接口只认 key=xiaohongshu
  • snapshot_id 秒级过期:先拿 page1 的 snapshot,再单独 curl page2——中间隔了几秒就报 1026。翻页必须脚本内连续请求。
  • has_more=false 就是终局:榜单就 20 条,翻页也不会更多,别白费功夫。
  • 必须带浏览器 UA:无 UA 或默认 python-requests UA 可能被拒。
ℹ️

排错套路:先单独验 tab(报 1025 → 缺 sub_tab;报 1001 → tab 写法不对), 再验 page_size(报 1001 → 超上限),最后才考虑翻页。

7. 平台 tab 结构

从首页 RSC payload 的 tab 配置可还原每个平台的 key 与子榜结构。 已确认的映射:

平台tab key子榜示例
小红书xiaohongshu(id=100,品牌色 #FF2442)hot-search 热点
微博weibo(待验证)hot(推测)
知乎zhihu(待验证)

发现新平台的正确姿势(不用猜):

从首页 HTML 提取 tab 结构
# 1. 找所有 tab key
grep -o '"key":"[a-z-]*"' /tmp/rebang.html | sort -u

# 2. 找小红书的子榜结构("id":100 附近的 source_tabs / children 数组)
grep -o '.\{0,200\}"id":100.\{0,600\}' /tmp/rebang.html | head -1 | python3 -m json.tool 2>/dev/null || true

# 3. 直接试错:tab=weibo&sub_tab=hot → 200 或 1025 迭代
curl -s 'https://rebang.today/api/latest-list?tab=weibo&sub_tab=hot&page=1&page_size=20' \
  -H 'User-Agent: Mozilla/5.0' | head -c 300

8. 可复用脚本

本次逆向成果已固化为 Hermes skill rebang-today-hot-list(含完整踩坑记录),并附带可直接运行的抓取脚本:

  • Skill: ~/.hermes/skills/data-fetching/rebang-today-hot-list/
  • 脚本: scripts/fetch_rebang.py(工作区副本:/Users/spark/Source/rebang-hot-list/fetch_rebang.py
用法
# 小红书热榜(默认输出 编号.标题(热度) + 原站链接)
python3 fetch_rebang.py --tab xiaohongshu --sub-tab hot-search

# 原始 JSON 输出(便于二次分析)
python3 fetch_rebang.py --tab xiaohongshu --sub-tab hot-search --json

# 翻页(注意:快照秒级过期,脚本内已自动处理连续请求)
python3 fetch_rebang.py --tab xiaohongshu --sub-tab hot-search --page 2

以后在对话里说「抓一下小红书热榜」即可复用该 skill——直接跑脚本,不再重新逆向,token 开销降低一个数量级。

References