按需查阅，不是必读接入步骤。接入路线：[查询取数接入路线](/docs)

# 批量导出数据

需要离线分析或一次性取走大量内容时，用导出代替逐条调用读取接口。导出是异步任务：你创建任务，Whale 在后台按 UTC 天或小时分区生成文件，完成后你逐个下载。

导出只包含每条内容的**当前版本**。需要历史版本或指标快照时，使用[读取内容与历史](/docs/documents)；需要持续获取新增和变化的数据时，使用[增量同步数据](/docs/change-feed)。

## 使用前提

- 已按[获取与使用 API Key](/docs/authentication)配置 `WHALE_BASE_URL` 和 `WHALE_API_KEY`，本机有 curl 和 jq。
- 你的应用订阅开启了导出权限（`export_allowed`）。没有开启时创建任务返回 `403 export_forbidden`，请联系你的组织管理员向 Whale 申请。
- 如果你的订阅限定了数据集或平台，请求里必须显式传 `dataset_ids` 和 `source_platforms`，且不能超出订阅范围。

## 创建导出任务


```bash
export_response="$(curl -sS --fail-with-body -X POST "$WHALE_BASE_URL/v1/exports" \
  -H "Authorization: Bearer $WHALE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset_ids": ["social_media_raw"],
    "source_platforms": ["reddit"],
    "published_from": "2026-10-01T00:00:00Z",
    "published_to": "2026-10-08T00:00:00Z",
    "format": "jsonl",
    "partition_by": "hour"
  }')"
printf '%s' "$export_response" | jq .
EXPORT_ID="$(printf '%s' "$export_response" | jq -er '.export_id')"
```


| 字段 | 必填 | 说明 |
| --- | ---: | --- |
| `dataset_ids` | 条件 | 要导出的数据集；订阅限定了数据集时必填 |
| `source_platforms` | 条件 | 要导出的平台；订阅限定了平台时必填 |
| `published_from` / `published_to` | 否 | 按发布时间筛选，RFC 3339；`from` 必须早于 `to` |
| `format` | 否 | `jsonl`（默认）或 `parquet` |
| `compression` | 否 | 由格式决定：`jsonl` 只能用 `gzip`，`parquet` 只能用 `zstd`；可省略 |
| `fields` | 否 | 只对 `jsonl` 有效，可包含 `platform_payload`；`parquet` 固定使用完整跨平台字段，不包含 `platform_payload` |
| `partition_by` | 否 | `day`（默认）或 `hour`；Reddit 等高吞吐平台建议使用 `hour` |
| `max_rows` | 否 | 每个分区的安全上限，1–100000，默认 100000 |
| `max_bytes` | 否 | 每个分区的安全上限，最大 1073741824（1 GiB），默认最大值 |

请求成功返回 `202`：


```json
{
  "export_id": "exp_01K...",
  "status": "queued",
  "format": "jsonl",
  "compression": "gzip",
  "rows": 0,
  "bytes": 0,
  "file_count": 0,
  "partitions": 7,
  "created_at": "2026-10-14T08:00:00Z",
  "expires_at": "2026-10-21T08:00:00Z"
}
```


### 分区与行数上限

带时间范围的任务默认按 UTC 日期拆分；`partition_by=hour` 时按 UTC 小时拆分。`max_rows` 和 `max_bytes` 是每个分区的安全边界。分区达到边界后如果仍有数据，任务会明确失败，不会再以成功状态静默截断。

Reddit 等每日百万级数据应使用小时分区；如果单小时仍超过边界，再缩小时间范围或按平台拆分。持续同步仍优先使用[增量同步数据](/docs/change-feed)。

## 查询进度


```bash
curl -sS --fail-with-body "$WHALE_BASE_URL/v1/exports/$EXPORT_ID" \
  -H "Authorization: Bearer $WHALE_API_KEY" | jq '{status, rows, bytes, file_count, partitions, error}'
```


`status` 依次为 `queued`、`running`，最终为 `succeeded`、`failed`、`cancelled` 或 `expired`。从 5 秒间隔开始轮询并逐步退避。`failed` 时 `error` 说明原因；分区失败会自动重试，并从已完成的位置继续。

## 下载文件

任务为 `succeeded` 后列出文件：


```bash
curl -sS --fail-with-body "$WHALE_BASE_URL/v1/exports/$EXPORT_ID/files" \
  -H "Authorization: Bearer $WHALE_API_KEY" | jq .
```



```json
{
  "files": [
    {"sequence": 0, "format": "manifest", "rows": 18234, "bytes": 2048, "download_url": "/v1/exports/exp_01K.../files/0", "expires_at": "2026-10-21T08:00:00Z"},
    {"sequence": 1000001, "format": "jsonl", "rows": 5000, "bytes": 3145728, "download_url": "/v1/exports/exp_01K.../files/1000001", "expires_at": "2026-10-21T08:00:00Z"}
  ]
}
```


逐个下载。`download_url` 是相对路径，需要拼上 `WHALE_BASE_URL` 并携带同一个 API Key：


```bash
curl -sS --fail-with-body "$WHALE_BASE_URL/v1/exports/$EXPORT_ID/files" \
  -H "Authorization: Bearer $WHALE_API_KEY" |
  jq -r '.files[] | "\(.sequence) \(.format)"' |
  while read -r sequence format; do
    case "$format" in
      jsonl) extension="jsonl.gz" ;;
      parquet) extension="parquet" ;;
      *) extension="json" ;;
    esac
    curl -sS --fail-with-body -o "export-$EXPORT_ID-$sequence.$extension" \
      "$WHALE_BASE_URL/v1/exports/$EXPORT_ID/files/$sequence" \
      -H "Authorization: Bearer $WHALE_API_KEY"
  done
```


- `sequence` 为 `0` 的文件是清单（`manifest.json`），列出格式、总行数、各分区的时间范围、行数和文件。
- 其余是数据分片，`sequence` 为“分区序号 × 1000000 + 分片序号”，例如第 1 个分区的第 1 片是 `1000001`。按 `files` 返回的值下载，不要自行推算。
- 数据分片格式：`jsonl` 为 GZIP 压缩的 JSON Lines，每行一条内容；`parquet` 为 ZSTD 压缩的 Parquet。
- 文件和任务在创建后 **7 天**过期，过期后无法下载，需要重新创建任务。

## 导出字段

| 字段 | 说明 |
| --- | --- |
| `document_id` / `version_id` | Whale 内容 ID 与当前版本 ID，可用于[读取内容与历史](/docs/documents) |
| `external_content_id` / `canonical_url` | 平台原始内容 ID 与规范链接 |
| `dataset_id` / `source_platform` / `content_type` | 数据集、平台与内容类型 |
| `title` / `body` / `body_format` | 标题、完整正文与正文格式 |
| `language` / `region` / `country_code` | 语言与地域 |
| `published_at` | 平台发布时间 |
| `content_hash` / `size_bytes` / `char_count` | 正文哈希、字节数与字符数 |
| `author` / `subreddit` / `score` / `external_parent_id` / `external_thread_id` | 作者、社区、评分与回复关系；`jsonl` 需在 `fields` 中显式选择，`parquet` 默认包含 |
| `platform_payload` | 完整平台原始字段；仅 JSONL 支持，需显式选择并获得对应字段授权 |

请求的字段必须都在你的订阅字段授权内，否则返回 `403 export_forbidden`。

## 取消任务


```bash
curl -sS --fail-with-body -X POST "$WHALE_BASE_URL/v1/exports/$EXPORT_ID/cancel" \
  -H "Authorization: Bearer $WHALE_API_KEY"
```


已生成的文件在过期前仍可下载。

## 计费、限额与错误

每次创建导出按一次请求计量，按你的组织定价扣减积分；创建失败会退回。订阅可能设置每日导出任务数上限（`daily_export_limit`），达到上限后当天无法再创建。

| HTTP | code | 含义与处理 |
| --- | --- | --- |
| 400 | `invalid_export_request` | 请求体格式不合法 |
| 400 | `export_create_failed` | 校验未通过，例如格式与压缩不匹配、`published_from` 晚于 `published_to`、`parquet` 传了部分字段，或已达每日导出上限；按 `message` 修正 |
| 400 | `invalid_export_file` | `sequence` 不是非负整数 |
| 401 | `unauthorized` | API Key 无效 |
| 402 | `credit_insufficient` | 组织积分不足 |
| 403 | `export_forbidden` | 订阅未开启导出，或数据集、平台、字段超出授权 |
| 404 | `export_not_found` / `export_file_not_found` | 任务或文件不存在、不属于你的应用，或已过期 |
| 429 | `rate_limit_exceeded` | 超过限流，按 `Retry-After` 退避 |

完整字段见[公开 API Reference](/public-api-reference)。

## 继续当前任务

[返回接入路线](/docs)；查完所需信息后，回到正在执行的步骤。 