# 批量任务里重试数据接口，怎么保证不被重复扣费？

> 每晚跑的抖音、TikTok 评论任务怎么安全重试：哪些错误该重试，每个请求一个 Idempotency-Key 如何防止重复扣费，附一个可以直接粘贴的重试函数。

网页版：https://okdata.online/zh/answers/retry-api-calls-without-double-billing/

**简短回答：**给每个请求一个由接口和参数算出来的重试键，调用超时或连接断开时，用同一个键重发。在 okdata 上，24 小时内的重复请求会返回保存的结果、不扣费，所以一个 1500 页的抖音评论任务不管重试多少次，都是 $3.00。只重试标明可以重试的错误，内容不存在的错误永远不要重试。

一个夜间任务要给 500 条抖音视频各拉 3 页评论。这 1500 次调用里，总会有几次超时或断线。最麻烦的是没收到任何响应的那种：调用可能已经完成并扣了费，也可能没有。盲目重试可能付两次钱，不重试数据就缺一块。幂等键就是用来解决这个两难的。

## 幂等键怎么起作用

- 每个请求带一个 `Idempotency-Key` 请求头。
- 24 小时内同一个 key、同一个请求，返回保存的结果：`meta.replayed: true`，扣费 `"0"`，响应头带 `Idempotent-Replayed: true`。
- 如果第一次失败了，什么都不会保存，重试就是重新跑一次；失败的那次本来就没扣费。
- 如果第一次还在跑，会返回 `rate_limited`，`retry_after_seconds: 1`，等一下再发。
- 同一个 key 用在不同的请求上，会被拒绝并返回 `invalid_params`，所以要按请求生成 key，整个任务共用一个是行不通的。

幂等键能保留多久，是每个用这套机制的 API 都会被问到的问题，比如[这个关于支付接口幂等键的提问](https://developer.squareup.com/forums/t/how-long-are-idempotency-keys-retained-for-the-createpayment-endpoint/28303)。这里的答案是 24 小时。

## 哪些情况该重试

| 返回码 | 要不要重试 |
|---|---|
| `rate_limited` | 要，等 `retry_after_seconds` |
| `upstream_busy`、`upstream_failed`、`internal_error` | 要，退避后重试 |
| `timeout`，或者完全没收到响应 | 要，用同一个 key |
| `not_found`、`invalid_params`、`unauthorized`、`insufficient_balance` | 不要，修正或跳过 |

## 可以直接粘贴的重试函数

```python
import hashlib, json, os, time, requests

KEY = os.environ["OKDATA_API_KEY"]

def call(path, **params):
    idem = hashlib.sha256(json.dumps([path, params], sort_keys=True).encode()).hexdigest()
    h = {"Authorization": f"Bearer {KEY}", "Idempotency-Key": idem}
    for attempt in range(6):
        try:
            body = requests.get(f"https://api.okdata.online{path}", headers=h, params=params, timeout=90).json()
        except requests.RequestException:
            time.sleep(2 ** attempt)              # 没收到响应：用同一个 key 重发
            continue
        if body["code"] == "ok":
            return body
        if body.get("retry") not in ("yes", "same_key"):
            raise RuntimeError(f"{body['code']}: {body['message']}")
        time.sleep(body.get("retry_after_seconds") or 2 ** attempt)
    raise RuntimeError(f"重试多次仍失败: {path} {params}")

page = call("/v1/douyin/videos/comments/list", id="7301234567890123456", sort="hot")
```

同一个函数也适用于 [TikTok 视频评论](/zh/apis/tiktok/videos/comments/list/)和其他所有接口；[抖音视频评论](/zh/apis/douyin/videos/comments/list/)每页 $0.002，重放不收费。

## 再养成两个习惯

- 保存每次返回的 `request_id`。用 `GET https://api.okdata.online/v1/calls/{request_id}` 能查到任何一次调用最后怎么结算。
- 给重试次数封顶。重试六次还失败，通常是出了故障；看一眼服务状态，晚些时候用同样的 key 重跑整个任务。

全部返回码、哪些扣费、哪些值得重试，见[错误码](/zh/docs/errors/)。

## 用到的接口

- [抖音视频评论](https://okdata.online/zh/apis/douyin/videos/comments/list/) `douyin.videos.comments.list`：$0.002/次 · 每页约 20 条评论
- [TikTok 视频评论](https://okdata.online/zh/apis/tiktok/videos/comments/list/) `tiktok.videos.comments.list`：$0.002/次 · 每页约 50 条评论

更新于 2026-11-12
