哥白尼数据空间OData API避坑指南:Python requests查询参数怎么拼才对?
哥白尼数据空间OData API深度解析:Python requests高效查询实战指南
当我们需要从哥白尼数据空间获取卫星遥感数据时,OData API提供了强大的查询能力。但对于开发者来说,构建正确的查询参数往往充满挑战——特别是当涉及复杂的时间范围筛选、轨道号匹配和产品类型组合查询时。本文将深入剖析这些技术难点,帮助您避开常见陷阱。
1. OData API基础与认证机制
哥白尼数据空间的OData API基于标准的Open Data Protocol,但它在认证和查询语法上有自己的特点。首先,我们需要了解如何正确获取访问令牌,这是所有API调用的前提。
获取访问令牌的核心代码如下:
def get_access_token(username: str, password: str) -> str:
data = {
"client_id": "cdse-public",
"username": username,
"password": password,
"grant_type": "password",
}
try:
response = requests.post(
"https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token",
data=data,
)
response.raise_for_status()
except Exception as e:
raise Exception(f"认证失败: {response.json()}")
return response.json()["access_token"]
注意:这里使用的是OAuth 2.0的密码授权模式,client_id固定为"cdse-public"
常见认证问题包括:
- 账号密码错误(返回401状态码)
- 网络连接问题(可能导致超时)
- 令牌过期(通常有效期为1小时)
提示:建议将获取的access token存储在变量中,并在后续所有API请求的headers中加入
Authorization: Bearer <token>
2. 复杂查询参数构建详解
构建正确的filter参数是OData API查询的核心难点。哥白尼数据空间的OData实现有一些特殊语法,特别是对于属性路径的访问。
2.1 时间范围查询
时间范围查询需要使用ContentDate/Start或ContentDate/End属性,格式必须严格遵循ISO 8601标准:
start_date = "2023-01-01"
end_date = "2023-01-31"
time_filter = f"ContentDate/Start gt {start_date}T00:00:00.000Z and ContentDate/Start lt {end_date}T00:00:00.000Z"
常见错误:
- 忘记添加"T00:00:00.000Z"时间部分
- 使用错误的比较运算符(如使用"=="而不是"gt"/"lt")
- 日期格式不符合YYYY-MM-DD标准
2.2 轨道号与产品类型查询
对于像relativeOrbitNumber这样的属性,需要使用特殊的OData.CSC语法:
orbit_number = 360
product_type = "SR_2_LAN___"
orbit_filter = f"Attributes/OData.CSC.IntegerAttribute/any(att:att/Name eq 'relativeOrbitNumber' and att/OData.CSC.IntegerAttribute/Value eq {orbit_number})"
type_filter = f"Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'productType' and att/OData.CSC.StringAttribute/Value eq '{product_type}')"
这种语法结构可以分解为:
Attributes/OData.CSC.IntegerAttribute/any(att:att/Name eq 'relativeOrbitNumber')- 查找名为relativeOrbitNumber的属性att/OData.CSC.IntegerAttribute/Value eq {orbit_number}- 匹配属性值
2.3 组合多个查询条件
将多个条件组合时,需要使用"and"连接,并注意URL编码:
base_url = "https://catalogue.dataspace.copernicus.eu/odata/v1/Products?$filter="
full_filter = " and ".join([orbit_filter, type_filter, time_filter])
encoded_filter = requests.utils.quote(full_filter)
final_url = base_url + encoded_filter
重要:URL中的特殊字符(如空格、引号)必须正确编码,否则会导致查询失败
3. 分页与结果限制处理
当查询结果很多时,合理使用分页参数可以提高效率并避免超时。
3.1 使用$top限制返回数量
top_param = "&$top=100" # 限制返回100条记录
3.2 使用$skip实现分页
page_size = 100
page = 2
skip_param = f"&$skip={page_size * (page - 1)}"
3.3 获取结果总数
要获取匹配查询条件的总记录数(不考虑分页),可以使用$count:
count_url = "https://catalogue.dataspace.copernicus.eu/odata/v1/Products/$count"
response = session.get(count_url, headers=headers)
total_count = int(response.text)
4. 高级查询技巧与性能优化
4.1 空间范围查询
除了时间范围,还可以通过地理空间条件筛选数据:
wkt_polygon = "POLYGON((98.982415 27.856909,98.982415 36.398266,116.208977 36.398266,116.208977 27.856909,98.982415 27.856909))"
spatial_filter = f"Attributes/OData.CSC.Intersects(area=geography'SRID=4326;{wkt_polygon}')"
4.2 选择特定字段
使用$select可以减少返回数据量,提高性能:
select_param = "&$select=Id,Name,ContentDate"
4.3 排序结果
使用$orderby对结果排序:
order_param = "&$orderby=ContentDate/Start desc" # 按开始时间降序
4.4 组合查询示例
将上述所有技巧组合起来的完整示例:
def build_query_url(params):
filters = []
# 时间范围
if 'date_range' in params:
start, end = params['date_range']
filters.append(f"ContentDate/Start gt {start}T00:00:00.000Z and ContentDate/Start lt {end}T00:00:00.000Z")
# 轨道号
if 'orbit_number' in params:
filters.append(f"Attributes/OData.CSC.IntegerAttribute/any(att:att/Name eq 'relativeOrbitNumber' and att/OData.CSC.IntegerAttribute/Value eq {params['orbit_number']})")
# 产品类型
if 'product_type' in params:
filters.append(f"Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'productType' and att/OData.CSC.StringAttribute/Value eq '{params['product_type']}')")
# 空间范围
if 'geometry' in params:
wkt = params['geometry']
filters.append(f"Attributes/OData.CSC.Intersects(area=geography'SRID=4326;{wkt}')")
base_url = "https://catalogue.dataspace.copernicus.eu/odata/v1/Products?"
query_parts = []
if filters:
query_parts.append(f"$filter={' and '.join(filters)}")
if 'select' in params:
query_parts.append(f"$select={','.join(params['select'])}")
if 'top' in params:
query_parts.append(f"$top={params['top']}")
if 'skip' in params:
query_parts.append(f"$skip={params['skip']}")
if 'orderby' in params:
query_parts.append(f"$orderby={params['orderby']}")
return base_url + "&".join(query_parts)
5. 错误处理与调试技巧
5.1 常见错误代码
| 状态码 | 含义 | 可能原因 |
|---|---|---|
| 400 | 错误请求 | 查询语法错误 |
| 401 | 未授权 | 令牌无效或过期 |
| 404 | 未找到 | 端点URL错误 |
| 500 | 服务器错误 | 服务器问题 |
5.2 调试查询URL
当查询失败时,可以按照以下步骤排查:
- 打印完整的请求URL
- 检查时间格式是否正确
- 验证属性名称是否拼写正确
- 确保所有特殊字符都已正确编码
- 尝试简化查询,逐步添加条件
# 打印调试信息
print("请求URL:", final_url)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
5.3 处理大型结果集
对于可能返回大量结果的查询:
- 始终使用分页($top和$skip)
- 考虑按时间或其他条件分批查询
- 使用多线程下载时注意速率限制
from concurrent.futures import ThreadPoolExecutor
def download_product(product):
download_url = f"https://zipper.dataspace.copernicus.eu/odata/v1/Products({product['Id']})/$value"
# 实现下载逻辑...
with ThreadPoolExecutor(max_workers=4) as executor:
executor.map(download_product, products)
在实际项目中,最常遇到的问题往往是属性路径拼写错误和时间格式不正确。建议将常用的查询条件封装成可重用的函数,并在代码中添加充分的注释说明每个参数的含义和格式要求。
更多推荐


所有评论(0)