> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sorsa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 分页

向 Sorsa API 请求数据列表时，结果会分页返回。要获取完整数据集，需要通过游标逐页读取，直到没有更多数据。

***

## 游标分页的工作方式

Sorsa 使用游标分页，而非传统页码。社交媒体数据会不断新增内容，基于偏移量的分页可能跳过或重复结果，因此游标方式更可靠。

所有分页端点的流程相同：

1. 首次请求不传游标。
2. 响应包含数据和 `next_cursor` 字段。
3. 将 `next_cursor` 值传入下一次请求，以获取下一页。
4. 当 `next_cursor` 为 `null` 或响应中没有该字段时，表示已到达末尾。

并非所有端点都分页。`/info`、`/tweet-info`、`/score` 和 `/about` 等单对象端点返回一个结果，不包含游标。部分列表端点也在一次响应中返回全部数据，包括 `/info-batch`、`/tweet-info-bulk`，以及 `/top-followers` 等加密货币分析列表。请检查端点参考是否包含 `next_cursor` 参数。

***

## 响应结构

分页响应采用以下两种结构之一：

```text theme={null}
{
  "users": [ ... ],
  "next_cursor": "DAABCgABF7Y..."
}
```

```text theme={null}
{
  "tweets": [ ... ],
  "next_cursor": "DAABCgABF7Y..."
}
```

这些分页用户和推文响应的数据位于 `users` 或 `tweets` 中。将 `next_cursor` 视为不透明值：原样传回；当其为 null、空或缺失时停止。不要递增游标，也不要将其转换为 JavaScript Number。

响应包装结构和对象的完整说明请参阅[响应格式](https://docs.sorsa.io/zh-Hans/response-format)。

***

## 如何传递游标

游标字段始终叫 `next_cursor`。端点之间的唯一区别是传递位置：GET 端点使用查询参数，POST 端点使用 JSON 请求体。

**GET 端点**（如 `/followers`、`/follows`、`/list-tweets`）通过查询参数接收 `next_cursor`：

```bash theme={null}
# First page
curl --request GET \
  --url 'https://api.sorsa.io/v3/followers?username=elonmusk' \
  --header 'ApiKey: YOUR_API_KEY'

# Next page
curl --request GET \
  --url 'https://api.sorsa.io/v3/followers?username=elonmusk&next_cursor=DAABCgABF7Y...' \
  --header 'ApiKey: YOUR_API_KEY'
```

**POST 端点**（如 `/search-tweets`、`/user-tweets`、`/comments`）通过 JSON 请求体接收 `next_cursor`：

```bash theme={null}
# First page
curl --request POST \
  --url 'https://api.sorsa.io/v3/search-tweets' \
  --header 'ApiKey: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"query": "bitcoin"}'

# Next page
curl --request POST \
  --url 'https://api.sorsa.io/v3/search-tweets' \
  --header 'ApiKey: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"query": "bitcoin", "next_cursor": "DAABCgABF7Y..."}'
```

***

## 每页条数并不固定

由于 X 平台数据的特性，每页返回的数量可能不同。最多返回 20 条的端点，某一页可能只有 18、12 甚至 5 条，即使下一页仍有数据。

**不要根据条数判断是否已到末尾。** 少于预期条数并不表示没有更多数据。始终检查 `next_cursor`；只要它存在且不为 `null`，就还有页面可获取。

***

## 完整分页示例

以下示例将结果保存在内存中。大型任务应逐页写入存储、按字符串 ID 去重，并在页面保存后记录检查点。使用同一游标时，账号、查询、筛选条件和排序应保持不变。设置页面或请求预算，并检测重复游标，防止意外无限循环。单个循环中的延迟不会协调其他共享同一密钥的工作进程。

**Python：分页获取粉丝（GET 端点）**

```python theme={null}
import time
import requests

API_KEY = "YOUR_API_KEY"

def fetch_all_followers(username):
    all_users = []
    cursor = None

    while True:
        params = {"username": username}
        if cursor:
            params["next_cursor"] = cursor

        response = requests.get(
            "https://api.sorsa.io/v3/followers",
            params=params,
            headers={"ApiKey": API_KEY},
            timeout=30,
        )
        response.raise_for_status()
        data = response.json()

        users = data.get("users", [])
        all_users.extend(users)
        print(f"Page fetched: {len(users)} users. Total so far: {len(all_users)}")

        cursor = data.get("next_cursor")
        if not cursor:
            break

        time.sleep(0.05)  # respect 20 req/s rate limit

    return all_users

followers = fetch_all_followers("elonmusk")
print(f"Done. {len(followers)} followers total.")
```

**Python：分页获取搜索结果（POST 端点）**

```python theme={null}
import time
import requests

API_KEY = "YOUR_API_KEY"

def search_all_tweets(query):
    all_tweets = []
    cursor = None

    while True:
        body = {"query": query}
        if cursor:
            body["next_cursor"] = cursor

        response = requests.post(
            "https://api.sorsa.io/v3/search-tweets",
            json=body,
            headers={"ApiKey": API_KEY},
            timeout=30,
        )
        response.raise_for_status()
        data = response.json()

        tweets = data.get("tweets", [])
        all_tweets.extend(tweets)
        print(f"Page fetched: {len(tweets)} tweets. Total so far: {len(all_tweets)}")

        cursor = data.get("next_cursor")
        if not cursor:
            break

        time.sleep(0.05)

    return all_tweets

results = search_all_tweets("bitcoin")
print(f"Done. {len(results)} tweets total.")
```

**JavaScript：分页获取粉丝（GET 端点）**

```javascript theme={null}
async function fetchAllFollowers(username) {
  const API_KEY = "YOUR_API_KEY";
  const allUsers = [];
  let cursor = null;

  while (true) {
    const params = new URLSearchParams({ username });
    if (cursor) params.append("next_cursor", cursor);

    const response = await fetch(
      `https://api.sorsa.io/v3/followers?${params}`,
      { headers: { "ApiKey": API_KEY } }
    );
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();

    const users = data.users || [];
    allUsers.push(...users);
    console.log(`Page fetched: ${users.length} users. Total: ${allUsers.length}`);

    cursor = data.next_cursor;
    if (!cursor) break;

    await new Promise(r => setTimeout(r, 50));
  }

  return allUsers;
}
```

**JavaScript：分页获取搜索结果（POST 端点）**

```javascript theme={null}
async function searchAllTweets(query) {
  const API_KEY = "YOUR_API_KEY";
  const allTweets = [];
  let cursor = null;

  while (true) {
    const body = { query };
    if (cursor) body.next_cursor = cursor;

    const response = await fetch("https://api.sorsa.io/v3/search-tweets", {
      method: "POST",
      headers: {
        "ApiKey": API_KEY,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();

    const tweets = data.tweets || [];
    allTweets.push(...tweets);
    console.log(`Page fetched: ${tweets.length} tweets. Total: ${allTweets.length}`);

    cursor = data.next_cursor;
    if (!cursor) break;

    await new Promise(r => setTimeout(r, 50));
  }

  return allTweets;
}
```

***

## 带错误处理的分页

生产环境中应结合分页和重试逻辑，避免单页失败中断整个数据收集任务。完整错误处理说明请参阅[错误码](https://docs.sorsa.io/zh-Hans/error-codes)。

```python theme={null}
import time
import requests

API_KEY = "YOUR_API_KEY"

def paginate_with_retries(username, max_retries=3):
    all_users = []
    cursor = None

    while True:
        params = {"username": username}
        if cursor:
            params["next_cursor"] = cursor

        for attempt in range(max_retries):
            response = requests.get(
                "https://api.sorsa.io/v3/followers",
                params=params,
                headers={"ApiKey": API_KEY},
                timeout=30,
            )

            if response.status_code == 200:
                break
            elif response.status_code == 429:
                time.sleep(1)
                continue
            elif response.status_code >= 500:
                time.sleep(2)
                continue
            else:
                raise Exception(f"Error {response.status_code}: {response.text}")
        else:
            raise Exception("Max retries exceeded")

        data = response.json()
        all_users.extend(data.get("users", []))

        cursor = data.get("next_cursor")
        if not cursor:
            break

        time.sleep(0.05)

    return all_users
```

***

## 后续步骤

* [速率限制](https://docs.sorsa.io/zh-Hans/rate-limits)：分页获取大规模数据集时，了解每秒 20 次上限
* [错误码](https://docs.sorsa.io/zh-Hans/error-codes)：处理分页循环中的 429 和其他错误
* [响应格式](https://docs.sorsa.io/zh-Hans/response-format)：User 与 Tweet 对象的完整结构参考
