基于 Web API 批量获取鹏程实验室 GAIA 数据下载链接的方法
最近需要使用鹏程实验室开放平台上的 GAIA(Global Artificial Impervious Area)不透水面数据。这个数据切分的瓦片比较小,手动点击下载非常麻烦。
我研究了一下网页端的网络请求逻辑,发现可以批处理下载。主要依赖两个关键 API:
- 获取某一年下面的文件列表
getFileListByPage; - 根据文件路径生成真实的临时下载链接
downloadResource。
整体的下载流程是:获取某年份全部文件名 → 拼接产品路径 → 请求获得真实下载的URL
具体流程如下:
1. 获取文件列表接口
第一个接口用于获取某个年份目录下的所有文件列表。
请求地址
POST https://data-starcloud.pcl.ac.cn/aiforearth/api/data/getFileListByPage
主要请求头
请求头中最关键参数是 Cookie,JSESSIONID注意替换成你自己的。"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。