回填历史数据

按页读取观测值端点,直到游标为空:2025 年 11 月 3 日至 2026 年 10 月 23 日的 USD/IDR 每日汇率共 249 天,按每页 100 行计算只需 3 次请求。每个账户都可以回溯到该数据序列的起点。

用 API 回填

观测值端点接受日期范围、页大小限制和游标。外汇汇率使用 /v1/fx/rates/{base}/observations,CPI 使用 /v1/economics/{country}/cpi/observations,大宗商品 K 线使用 /v1/commodities/spot/{commodity}/{currency}/bars。日期参数根据端点不同,为 start 和 end,或 start_date 和 end_date。每个响应都有 next_cursor,最后一页该值为 null。

  1. 设置你的 API 密钥 获取免费 API 密钥,并将其保存在环境变量 NARWHAL_API_KEY 中。脚本读取该变量,并通过 Authorization: Bearer YOUR_KEY 将密钥发送至 https://api.narwhalapi.com。
  2. 设置数据序列和日期范围 示例请求 2025 年 11 月 1 日至 2026 年 10 月 23 日的 USD/IDR,limit 设为 100。发送上一个响应返回的游标时,每一页都要保持相同的筛选条件。
  3. 把每一页保存到文件 使用该脚本把每条观测值作为一行 JSON 追加到 usd_idr.jsonl。只要 next_cursor 有值就继续发送请求,该值为空时停止。
    Python:把一年的 USD/IDR 每日汇率保存到文件
    import json, os
    import requests
    
    H = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
    params = {
        "currencies": "IDR",
        "start_date": "2025-11-01",
        "end_date": "2026-10-23",
        "limit": 100,
    }
    rows, pages, cursor = 0, 0, None
    with open("usd_idr.jsonl", "a") as out:
        while True:
            if cursor:
                params["cursor"] = (
                    cursor  # the cursor carries the filters; resend them unchanged
                )
            r = requests.get(
                "https://api.narwhalapi.com/v1/fx/rates/USD/observations",
                params=params,
                headers=H,
                timeout=15,
            )
            r.raise_for_status()
            body = r.json()
            for day in body["observations"]:
                out.write(json.dumps(day) + "\n")
                rows += 1
            pages += 1
            cursor = body.get("next_cursor")
            if not cursor:
                break
    print(pages, "pages,", rows, "days")
  4. 查看返回的日期 对于 2025 年 11 月 1 日至 2026 年 10 月 23 日的范围,脚本会输出 3 pages, 249 days。响应的起始日期可能晚于你请求的起始日期,所以除了最后一个日期,也要查看第一个已保存的日期。

脚本分别统计页数和天数。它先写入每条观测值,再请求下一页,游标循环结束后输出合计。文件以追加模式打开,每一行保存一条观测值。请求读取的是以 USD 为基础货币的 IDR 数据序列。

每一页按一次调用从你的额度中扣除,使用免费额度的账户每分钟限 50 次请求。回填多个数据序列时,即使每个序列只需要几页,也需要在页与页之间留出间隔,才能不超过每分钟的限制。

响应带有 X-RateLimit-Remaining。如果某页请求返回 429 rate_limit_exceeded,请先按 Retry-After 的值等待,再请求下一页。402 insufficient_credits 响应表示余额已用完,请先充值再继续。错误使用 application/problem+json,并包含 status、code、detail 和 request_id,因此要根据错误代码判断回填能否继续。

使用表格或无代码工作流

几百行的数据用 Sheets 导入就足够了。对于一次性的回填,脚本比循环翻页的工作流更简单。如果希望观测值一到就写入,请使用文件方式;Python 示例在整个回填过程中都会追加数据行。

n8n、Make 或 Zapier 也可以用游标循环翻页。工作流每次发送游标时都要带上相同的日期和数据序列筛选条件,并在 next_cursor 为 null 时停止。持续更新的数据流可以先做一次回填,再接上每日数据拉取。

选择回填方式

情况最合适的方法原因
一次性回填单个数据序列一年的数据脚本它逐页读取整个范围,并把数据行追加到文件
多个数据序列遍历各数据序列,并在页与页之间暂停暂停可使请求保持在每分钟限制之内
持续更新的数据流先回填一次,再每天拉取回填会补上每日拉取开始之前的历史数据

回填中途停止了怎么办?

从上次保存的日期重新开始。脚本每收到观测值就追加 JSON 行,所以即使任务中断,已写入的数据行仍保留在文件中。把这个已保存的日期作为继续该范围的起点。

保存的游标可能会过期。如果请求返回 409 cursor_expired,请不带游标重新开始,并从上次保存的日期继续。游标携带了其范围的筛选条件,所以在翻页读取该范围期间,不要更改这些条件。

每页最多可以请求多少行?

limit 最大可设为该端点的上限:外汇汇率观测值为 366,经济数据观测值为 1,000,大宗商品 K 线为 500,经济日历为 100。默认值为 100。limit 只控制页大小,你仍然需要沿着 next_cursor 一直读取,直到响应中没有下一页。

为什么我的回填比请求的起始日期晚开始?

所有账户都可读取完整历史。返回的第一个日期显示序列实际从何时开始,因此请求更早的开始日期并不保证返回更早的行。

计算示例:2025 年 11 月 1 日至 2026 年 10 月 23 日的 USD/IDR

以 2025 年 11 月 1 日为起始、2026 年 10 月 23 日为结束、页大小限制 100 来请求 USD/IDR,并用相同的筛选条件依次使用返回的游标。在这个范围内,响应共有 3 页 249 行,所以回填需要 3 次请求。

第一行是 2025 年 11 月 3 日星期一,因为 2025 年 11 月 1 日和 2 日是周末。2025 年 11 月 3 日的 USD/IDR 汇率为 16,710.3,2026 年 10 月 23 日为 18,107.8。

项目结果
请求的范围2025 年 11 月 1 日至 2026 年 10 月 23 日
页大小限制100 行
该范围的页数和请求数3 页,3 次请求
该范围的行数249
第一条观测值2025 年 11 月 3 日:每 1 USD 兑 16,710.3 IDR
最后一条观测值2026 年 10 月 23 日:每 1 USD 兑 18,107.8 IDR

发布于。

后续步骤