批量下载鹏程实验室数据的方法

基于 Web API 批量获取鹏程实验室 GAIA 数据下载链接的方法

最近需要使用鹏程实验室开放平台上的 GAIA(Global Artificial Impervious Area)不透水面数据。这个数据切分的瓦片比较小,手动点击下载非常麻烦。

我研究了一下网页端的网络请求逻辑,发现可以批处理下载。主要依赖两个关键 API:

  1. 获取某一年下面的文件列表 getFileListByPage
  2. 根据文件路径生成真实的临时下载链接 downloadResource

整体的下载流程是:获取某年份全部文件名 → 拼接产品路径 → 请求获得真实下载的URL

具体流程如下:

1. 获取文件列表接口

第一个接口用于获取某个年份目录下的所有文件列表。

请求地址

复制代码
POST https://data-starcloud.pcl.ac.cn/aiforearth/api/data/getFileListByPage

主要请求头

请求头中最关键参数是 CookieJSESSIONID注意替换成你自己的。"Referer"里面的id=13&r_id=13代表了你的产品ID。

复制代码
headers = {
    "Accept": "*/*",
    "Content-Type": "application/json",
    "Cookie": "JSESSIONID=你的JSESSIONID",
    "Origin": "https://data-starcloud.pcl.ac.cn",
    "Referer": "https://data-starcloud.pcl.ac.cn/iearthdata/map?id=13&r_id=13",
    "User-Agent": "Mozilla/5.0 ..."
}

请求参数

请求体是 JSON 格式如下,这里要注意page和count的值,count只能在1-1000之间,所以要模拟翻页几次才能获取全部的list。

复制代码
{
  "params": {
    "table": "rs_gaia_1985_2024_yearly",
    "path": "GAIA_1985_2025_yearly/GAIA_2017",
    "page": 1,
    "enableSpatialQuery": true,
    "count": 1000
  }
}

参数含义如下:

|----------------------|-------------------------------------------------|
| 参数 | 含义 |
| table | 产品对应的数据表名,例如 GAIA 使用 rs_gaia_1985_2024_yearly |
| path | 要查询的年份目录,例如 GAIA_1985_2025_yearly/GAIA_2017 |
| page | 页码,从 1 开始 |
| count | 每页返回数量,一般最大为 1000 |
| enableSpatialQuery | 是否启用空间查询,这里保持网页请求中的 true 即可 |

其中年份是通过 path 控制的,例如:

复制代码
GAIA_1985_2025_yearly/GAIA_1985
GAIA_1985_2025_yearly/GAIA_2000
GAIA_1985_2025_yearly/GAIA_2024

返回值

返回结果中比较关键的是"file": "GAIA_2017_-100_0.tif", 这是下一个API参数需要的文件名。

复制代码
{
  "total": 1450,
  "response": [
    {
      "file": "GAIA_2017_-100_0.tif",
      "size": 12345678
    }
  ]
}

实际批量下载时,主要使用:

|--------------------|----------------|
| 字段 | 用途 |
| total | 当前年份的文件总数 |
| response | 当前页返回的文件列表 |
| response[i].file | 文件名,用于后续拼接下载路径 |
| response[i].size | 文件大小,可用于下载后校验 |

2. 生成真实下载链接接口

拿到文件名以后,还不能直接下载。网页端还会再请求一个接口,用于生成真实的临时下载 URL,也就是 signedUrl。

请求地址

复制代码
POST https://data-starcloud.pcl.ac.cn/starcloud/api/file/downloadResource

主要请求头

这个接口除了需要 JSESSIONID,还需要 Authorization

复制代码
headers = {
    "Accept": "*/*",
    "Content-Type": "application/json",
    "Cookie": "JSESSIONID=你的JSESSIONID",
    "Authorization": "Bearer 你的AUTH_TOKEN",
    "Origin": "https://data-starcloud.pcl.ac.cn",
    "Referer": "https://data-starcloud.pcl.ac.cn/iearthdata/map?id=13&r_id=13",
    "User-Agent": "Mozilla/5.0 ..."
}

其中关键认证信息是:

复制代码
Cookie: JSESSIONID=你的JSESSIONID
Authorization: Bearer 你的AUTH_TOKEN

如果 Authorization 过期,可能无法生成下载链接。

objectKey 的拼接方式

第二个接口最关键的参数是 objectKey

假设当前年份是2017

文件名是GAIA_2017_-100_0.tif

那么 objectKey 应该拼接为:

复制代码
shared-dataset/Impervious/GAIA_1985_2025_yearly/GAIA_2017/GAIA_2017_-100_0.tif

通用格式为:

复制代码
shared-dataset/Impervious/GAIA_1985_2025_yearly/GAIA_{year}/{filename}

请求参数

请求体示例:

复制代码
{
  "country": "China",
  "objectKey": "shared-dataset/Impervious/GAIA_1985_2025_yearly/GAIA_2017/GAIA_2017_-100_0.tif",
  "resourceId": "13",
  "resourceType": "REMOTE_SENSING",
  "userAccount": "你的账号",
  "userId": "你的用户ID"
}

参数含义如下:

|----------------|---------------------------|
| 参数 | 含义 |
| country | 国家,这里使用 China |
| objectKey | 文件在对象存储中的路径 |
| resourceId | 数据资源 ID,GAIA 页面中对应为 13 |
| resourceType | 资源类型,这里是 REMOTE_SENSING |
| userAccount | 当前登录账号 |
| userId | 当前用户 ID |

返回值

返回值中最关键的是"signedUrl",拿到这个我们就可以随便用任意下载文件的方法下载了,IDW 迅雷 或者Python多进程都可以。

复制代码
{
  "signedUrl": "https://remotesensing-shared-data.obs.cn-south-222.ai.pcl.cn/...",
  "fileSize": 12345678,
  "expireSeconds": 3600
}

实际下载时主要使用:

|-----------------|-------------------------|
| 字段 | 用途 |
| signedUrl | 真正可访问的临时下载链接 |
| fileSize | 文件大小,可用于下载后校验 |
| expireSeconds | 链接有效时间,例如 3600 秒,即 1 小时 |

需要注意,signedUrl 是临时链接,通常会过期。过期后直接访问会返回类似:

复制代码
<Code>AccessDenied</Code>
<Message>Request has expired</Message>

因此,获取 signedUrl 后立即下载,不要隔很久再下载。


3. 简单示例:获取某一年第一个文件的真实下载链接

下面给一个最小示例,只演示:

复制代码
获取文件列表 → 取第一个文件 → 生成 signedUrl

不包含完整下载逻辑。

复制代码
# -*- coding: utf-8 -*-
import math
import json
import requests


BASE = "https://data-starcloud.pcl.ac.cn"
REFERER = f"{BASE}/iearthdata/map?id=13&r_id=13"

YEAR = 2017
COUNT = 1000

JSESSIONID = "替换成你自己的JSESSIONID"
AUTH_TOKEN = "替换成你自己的AUTH_TOKEN,不要带Bearer"

USER_ACCOUNT = "替换成你的账号"
USER_ID = "替换成你的用户ID"


def common_headers():
    return {
        "Accept": "*/*",
        "Content-Type": "application/json",
        "Cookie": f"JSESSIONID={JSESSIONID}",
        "Origin": BASE,
        "Referer": REFERER,
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0"
        ),
    }


def download_headers():
    headers = common_headers()
    headers["Authorization"] = f"Bearer {AUTH_TOKEN}"
    return headers


def get_file_list(year, page=1, count=1000):
    url = f"{BASE}/aiforearth/api/data/getFileListByPage"

    payload = {
        "params": {
            "table": "rs_gaia_1985_2024_yearly",
            "path": f"GAIA_1985_2025_yearly/GAIA_{year}",
            "page": page,
            "enableSpatialQuery": True,
            "count": count,
        }
    }

    resp = requests.post(
        url,
        headers=common_headers(),
        json=payload,
        timeout=60,
    )

    print("文件列表接口状态码:", resp.status_code)

    if resp.status_code != 200:
        print(resp.text)
        raise RuntimeError("获取文件列表失败")

    return resp.json()


def build_object_key(year, filename):
    return (
        f"shared-dataset/Impervious/"
        f"GAIA_1985_2025_yearly/GAIA_{year}/{filename}"
    )


def get_signed_url(year, filename):
    url = f"{BASE}/starcloud/api/file/downloadResource"

    object_key = build_object_key(year, filename)

    payload = {
        "country": "China",
        "objectKey": object_key,
        "resourceId": "13",
        "resourceType": "REMOTE_SENSING",
        "userAccount": USER_ACCOUNT,
        "userId": USER_ID,
    }

    resp = requests.post(
        url,
        headers=download_headers(),
        json=payload,
        timeout=60,
    )

    print("下载链接接口状态码:", resp.status_code)

    if resp.status_code != 200:
        print(resp.text)
        raise RuntimeError("获取 signedUrl 失败")

    data = resp.json()

    signed_url = data.get("signedUrl")
    if not signed_url:
        print(json.dumps(data, ensure_ascii=False, indent=2))
        raise RuntimeError("返回结果中没有 signedUrl")

    return signed_url


def main():
    # 1. 获取第一页文件列表
    data = get_file_list(YEAR, page=1, count=COUNT)

    total = int(data.get("total", 0))
    files = data.get("response", [])

    print("total:", total)
    print("当前页文件数:", len(files))

    if not files:
        print("没有获取到文件")
        return

    # 2. 取第一个文件测试
    filename = files[0]["file"]
    print("测试文件:", filename)

    # 3. 生成真实下载 URL
    signed_url = get_signed_url(YEAR, filename)

    print("真实下载链接:")
    print(signed_url)


if __name__ == "__main__":
    main()

运行成功后,会输出类似:

复制代码
文件列表接口状态码: 200
total: 1450
当前页文件数: 1000
测试文件: GAIA_2017_-100_0.tif
下载链接接口状态码: 200
真实下载链接:
https://remotesensing-shared-data.obs.cn-south-222.ai.pcl.cn/...

4. 常见问题

1. 为什么返回 403?

通常是认证信息失效,例如:

复制代码
JSESSIONID 过期
AUTH_TOKEN 过期
Cookie 没有复制完整
登录状态已经失效

可以重新打开网页,登录后在浏览器开发者工具的 Network 面板里复制最新请求头。

2. 为什么 signedUrl 过一会儿不能用了?

因为 signedUrl 是临时签名链接。返回值里通常会有:

复制代码
expireSeconds: 3600

也就是大约 1 小时有效。过期后需要重新调用 downloadResource 生成新的 signedUrl。

3. count 可以设置成 2000 吗?

从测试和网页逻辑来看,建议使用:

复制代码
count = 1000

如果某一年文件超过 1000 个,就通过 page 翻页获取,不建议强行设置成 2000。

相关推荐
皮卡祺q1 小时前
【JVM】:类加载机制,jvm内存布局,垃圾回收,String 不可变性源码分析
java·开发语言·jvm·多线程·string
JAVA面经实录9171 小时前
Java核心底层原理全集(终版无遗漏·生产级PDF)
java·开发语言·学习
java修仙传1 小时前
实习日志:完成算法调用总接口并修复联调问题
java·开发语言·数据库
铅笔小新z1 小时前
【Linux】进程间通信(IPC)
java·linux·运维
Rabitebla1 小时前
深入理解 C++ STL:stack 和 queue 的底层原理与实现
c语言·开发语言·数据结构·c++·算法
极客先躯1 小时前
高级java每日一道面试题-2025年12月11日-实战篇[Docker]-如何配置 Docker 的资源限制(CPU、内存、磁盘)?
java·docker·如何配置docker的资源限制·资源限制的底层支柱·linux cgroups·cpu 限制·从逻辑到策略
gCode Teacher 格码致知1 小时前
Python教学:正则表达式的寻找、匹配、替换、删除 四种模式案例-由Deepseek产生
开发语言·python·正则表达式
Zfox_1 小时前
【LangGraph】持久化(Persistence)
开发语言·人工智能·redis·langchain·ai编程·langgraph
總鑽風1 小时前
单点登录sso 微服务加网关gateway
java·微服务·gateway·jwt·单点登录