如何通过 Azure Billing API/SDK 获取成本分析数据
结论
要获取与 Azure Portal Cost analysis 接近的按资源成本数据,建议直接使用 Azure Cost Management Query API,按 ResourceId 分组并汇总 PreTaxCost。一般不需要自行拼接 Usage Aggregate 和 RateCard 来计算费用。
Usage Aggregate 与 RateCard 主要通过 meterId 关联。理论上,可以结合用量、计价阶梯和费率计算成本。但实际账单还会受到协议价格、折扣、预留实例、Savings Plan、免费额度、舍入、税费和币种转换等因素影响。因此,直接使用下面的公式,往往无法复现 Cost analysis 的结果:
cost = quantity × rate
推荐方案:Cost Management Query API
在 Subscription 范围内,请求地址为:
POST https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2023-11-01
下面的请求体按 ResourceId 汇总实际成本:
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "2026-08-01T00:00:00Z",
"to": "2026-08-29T23:59:59Z"
},
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": {
"name": "PreTaxCost",
"function": "Sum"
}
},
"grouping": [
{
"type": "Dimension",
"name": "ResourceId"
}
]
}
}
如果需要查看每个资源每天的成本,将 granularity 改为:
"granularity": "Daily"
还可以加入资源类型、资源组或服务名称等维度:
"grouping": [
{
"type": "Dimension",
"name": "ResourceId"
},
{
"type": "Dimension",
"name": "ResourceType"
},
{
"type": "Dimension",
"name": "ResourceGroupName"
}
]
可用维度取决于计费范围、订阅类型和实际数据。如果 API 提示维度无效,可以先查询当前 scope 支持的 dimensions,也可以减少分组字段,逐个测试。
Python REST API 示例
安装身份认证组件:
pip install azure-identity requests
以下代码可以使用 Azure CLI 登录身份、托管身份或服务主体获取令牌:
from datetime import datetime, timezone
import requests
from azure.identity import DefaultAzureCredential
subscription_id = "00000000-0000-0000-0000-000000000000"
credential = DefaultAzureCredential()
token = credential.get_token(
"https://management.azure.com/.default"
).token
url = (
f"https://management.azure.com/subscriptions/{subscription_id}"
"/providers/Microsoft.CostManagement/query"
"?api-version=2023-11-01"
)
payload = {
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "2026-08-01T00:00:00Z",
"to": "2026-08-29T23:59:59Z",
},
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": {
"name": "PreTaxCost",
"function": "Sum",
}
},
"grouping": [
{
"type": "Dimension",
"name": "ResourceId",
},
{
"type": "Dimension",
"name": "ResourceType",
},
],
},
}
response = requests.post(
url,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json=payload,
timeout=60,
)
response.raise_for_status()
result = response.json()
properties = result["properties"]
columns = [column["name"] for column in properties["columns"]]
for row in properties["rows"]:
record = dict(zip(columns, row))
print(
record.get("ResourceId"),
record.get("ResourceType"),
record.get("PreTaxCost"),
record.get("Currency"),
)
不要假设响应列的顺序固定。应先读取 properties.columns,再按位置与 properties.rows 对应。
典型的响应结构如下:
{
"properties": {
"columns": [
{"name": "PreTaxCost", "type": "Number"},
{"name": "ResourceId", "type": "String"},
{"name": "ResourceType", "type": "String"},
{"name": "Currency", "type": "String"}
],
"rows": [
[
12.34,
"/subscriptions/.../resourceGroups/rg-demo/providers/Microsoft.Compute/virtualMachines/vm01",
"microsoft.compute/virtualmachines",
"USD"
]
]
}
}
实际列名和顺序由查询条件及计费数据决定,应以 API 返回的 columns 为准。
使用 Python SDK
对应的 SDK 包为:
pip install azure-identity azure-mgmt-costmanagement
示例代码:
from azure.identity import DefaultAzureCredential
from azure.mgmt.costmanagement import CostManagementClient
from azure.mgmt.costmanagement.models import (
QueryAggregation,
QueryDataset,
QueryDefinition,
QueryGrouping,
QueryTimePeriod,
)
subscription_id = "00000000-0000-0000-0000-000000000000"
scope = f"/subscriptions/{subscription_id}"
credential = DefaultAzureCredential()
client = CostManagementClient(credential)
query = QueryDefinition(
type="ActualCost",
timeframe="Custom",
time_period=QueryTimePeriod(
from_property="2026-08-01T00:00:00Z",
to="2026-08-29T23:59:59Z",
),
dataset=QueryDataset(
granularity="None",
aggregation={
"totalCost": QueryAggregation(
name="PreTaxCost",
function="Sum",
)
},
grouping=[
QueryGrouping(type="Dimension", name="ResourceId"),
QueryGrouping(type="Dimension", name="ResourceType"),
],
),
)
result = client.query.usage(scope=scope, parameters=query)
column_names = [column.name for column in result.columns]
for row in result.rows or []:
record = dict(zip(column_names, row))
print(
record.get("ResourceId"),
record.get("ResourceType"),
record.get("PreTaxCost"),
record.get("Currency"),
)
不同版本的 azure-mgmt-costmanagement 可能使用略有差异的模型构造参数。如果本地 SDK 提示参数名称错误,请以当前安装版本生成的类型签名为准。相比之下,REST API 请求体通常更容易排查。
如果需要逐条用量明细
如果要查看每条计费记录,而不是汇总报表,可以使用 Azure Consumption Usage Details API。记录通常包含以下信息:
resourceIdmeterIdquantityunitPricecostusageStart、usageEndresourceGroupinstanceNamechargeType
获取明细后,可以直接按 resourceId 汇总返回的成本字段:
from collections import defaultdict
cost_by_resource = defaultdict(float)
for item in usage_details:
properties = item.get("properties", {})
resource_id = properties.get("resourceId") or "(unassigned)"
cost = properties.get("cost") or properties.get("pretaxCost") or 0
cost_by_resource[resource_id] += float(cost)
Usage Details 的数据量可能很大,因此需要处理 API 返回的分页链接。如果要进行周期性分析或处理大量历史数据,更适合配置 Cost Management Exports,定期把成本明细导出到 Storage Account,再通过 Python、数据库或数据仓库处理。
Usage Aggregate 与 RateCard 如何关联
如果确实需要自行估算,可以按下面的流程处理:
- 从 Usage Aggregate 获取每条用量记录。
- 读取记录中的
meterId、quantity、时间和资源信息。 - 在 RateCard 中找到
meterId相同的 meter。 - 根据
meterRates处理阶梯价格。 - 按
resourceUri或同等的资源标识汇总计算结果。 - 分别处理币种、折扣、免费额度、预留资源和其他调整项。
阶梯价格不能一律按单一费率计算。例如:
meterRates:
0: 0.10
100: 0.08
当用量为 150 时,可能需要这样计算:
100 × 0.10 + 50 × 0.08
具体的阶梯含义和计价方式仍须以相应计费协议及 meter 定义为准。RateCard 给出的费率也未必是最终结算价格。因此,这种方法适合估算成本或理解 meter 的计价方式,不适合需要与账单严格对账的场景。
为什么部分成本没有 ResourceId
Cost Management 查询结果中的 ResourceId 可能为空,这不一定表示查询有误。常见原因包括:
- Marketplace 购买项目
- Support 或订阅级费用
- Reservation 或 Savings Plan 购买费用
- 税费、退款及账单调整
- 某些共享服务或经典资源
- 尚未关联到具体 Azure Resource Manager 资源的成本记录
展示结果时,建议将这类记录归入”未分配成本”或”订阅级成本”,不要直接丢弃。
与 Portal 数据对齐时的注意事项
要复现 Portal 中的数值,需要让以下条件保持一致:
- 时间范围和时区。
- Scope,例如 subscription、resource group 或 billing account。
- 成本类型。
ActualCost与AmortizedCost的结果可能相差很大。 - 筛选条件和分组维度。
- 币种和计费周期。
- 成本数据的入账延迟。
- 税前成本、税后金额和发票总额的统计口径。
- 预留实例与 Savings Plan 对实际成本和摊销成本的不同展示方式。
按资源展示成本时,建议使用 Cost Management Query API,以 ResourceId 分组并汇总 PreTaxCost。需要逐笔追溯时,再使用 Usage Details 或 Cost Management Exports,无需通过 RateCard 重算最终账单。
备注:内容仅供参考。
