运维实战|零基础也能上手:用AI+Python一键统计阿里云+腾讯云费用(附完整代码)
运维实战|零基础也能上手:用AI+Python一键统计阿里云+腾讯云费用(附完整代码)
一、这篇教程能帮你解决什么?
你是否有这样的烦恼?
每月月底,财务同事准时来催:“这个月的云服务费用统计好了吗?”
然后你打开阿里云控制台、腾讯云控制台,一个一个产品翻账单,手动加起来,填到Excel里,再写邮件发过去。每次至少花半小时,而且怕算错、怕漏掉产品。
学完这篇教程,你能做什么?
- 自动拉取阿里云和腾讯云的账单数据
- 自动生成汇总报表
- 自动对比上月数据,发现异常自动提醒
- 自动发送邮件给财务/领导
- 设定时任务,每月1号自动跑,从此不用手动操作
你需要什么基础?
几乎不需要! 只要你会:
- ✅ 复制粘贴文字(会用Ctrl+C/V就行)
- ✅ 双击运行一个文件
- ✅ 看懂简单的表格
我不需要你会Python,不需要你懂API,不需要你学过编程。
二、准备工作(5分钟搞定)
2.1 检查Python是否装好
Python就是运行脚本的工具。先看看你的电脑有没有装:
Windows用户:
- 按
Win + R,输入cmd,回车 - 在黑色窗口里输入:
python --version - 如果显示类似
Python 3.x.x,说明装好了 ✅ - 如果提示"不是内部命令",说明没装 → 去 python.org 下载安装(安装时勾选"Add Python to PATH")
Mac用户:
- 打开"终端"(在应用程序→实用工具里)
- 输入:
python3 --version - Mac一般自带Python,直接能用 ✅
2.2 准备AccessKey(云账号的"门禁卡")
什么是AccessKey?
把云平台想象成一栋大楼:
- 你的云账号 = 你的身份证(证明你是谁)
- AccessKey = 你的门禁卡(用来刷卡开门、进入各个房间)
- API = 大楼里的自动服务台(你刷门禁卡,服务台帮你查东西)
AI脚本需要通过AccessKey来"刷卡"进入云平台,帮你查询账单数据。所以你需要先从云平台办一张门禁卡。
去哪办?(以阿里云为例,腾讯云类似)
- 登录阿里云控制台(ram.console.aliyun.com)
- 点击右上角头像 → “访问控制”
- 左侧菜单 → “用户” → “创建用户”
- 勾选"OpenAPI调用访问"
- 创建完成后,系统会给你两个东西:
- AccessKey ID(相当于门禁卡的编号,比如:
LTAI5tXXXXXX) - AccessKey Secret(相当于门禁卡的密码,比如:
wE2kXXXXXXXX)
- AccessKey ID(相当于门禁卡的编号,比如:
⚠️ 重要:AccessKey Secret只在创建时显示一次,马上复制保存!丢了只能重新创建。
- 给这个子账号只分配"只读"权限(比如
AliyunBSSReadOnlyAccess),这样就算AccessKey泄露,别人也只能看账单,无法操作你的云资源。
把AccessKey保存到电脑上
在电脑上新建一个文本文件(用记事本就行),内容像这样:
AccessKey ID:LTAI5tXXXXXX
AccessKey Secret:wE2kXXXXXXXX
保存为 aliyun_key.txt,记住保存的位置(比如桌面)。
腾讯云的AccessKey去哪办?
登录腾讯云控制台(console.cloud.tencent.com/cam/capi),步骤类似,创建后保存到 tencent_key.txt。
三、阿里云费用统计(跟着做就行)
3.1 创建脚本文件
你不需要写任何代码,只需要复制粘贴。
- 打开记事本(Windows按Win键搜"记事本")
- 把下面这整段代码全部复制,粘贴到记事本里:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
阿里云短信+语音服务费用统计脚本
功能:自动查询阿里云BSS账单,统计短信和语音服务的费用
使用方法:把下面的 "AccessKey文件路径" 和 "要查的月份" 改成你自己的,
然后双击运行就行了。
"""
import json
import time
import hmac
import hashlib
import base64
import urllib.request
import urllib.parse
# ════════════════════════════════════════════
# 🔧 你只需要改这两个地方!
# ════════════════════════════════════════════
# 第1处:改成你的AccessKey文件路径
# 举例:如果文件在桌面,就是 r"C:\Users\你的用户名\Desktop\aliyun_key.txt"
ACCESSKEY_FILE = r"D:\你的路径\aliyun_key.txt"
# 第2处:改成你要查的月份(格式:年-月)
# 举例:查2026年4月,就是 "2026-04"
BILLING_MONTH = "2026-04"
# ════════════════════════════════════════════
# 下面的代码不需要修改,直接运行就行
# ════════════════════════════════════════════
def read_credentials(file_path):
"""从文件中读取AccessKey"""
ak_id = ""
ak_secret = ""
try:
with open(file_path, 'r', encoding='utf-8') as f:
for line in f.readlines():
if 'AccessKey ID' in line:
ak_id = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
elif 'AccessKey Secret' in line:
ak_secret = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
return ak_id, ak_secret
except Exception as e:
print(f"❌ 读取AccessKey文件失败: {e}")
return None, None
def percent_encode(s):
"""阿里云要求的URL编码方式"""
s = str(s)
encoded = urllib.parse.quote(s, safe='~')
return encoded.replace('+', '%20').replace('*', '%2A')
def build_signed_url(ak_id, ak_secret, action, params):
"""
构造带签名的请求URL
简单理解:就像在快递单上签名,证明这个请求是你发的
"""
# 公共参数(每个请求都要带的)
common = {
'Format': 'JSON',
'Version': '2017-12-14',
'AccessKeyId': ak_id,
'SignatureMethod': 'HMAC-SHA1',
'Timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'SignatureVersion': '1.0',
'SignatureNonce': str(int(time.time() * 1000000)),
'Action': action,
}
# 合并所有参数
all_params = {**common, **params}
# 按字母排序,拼接成字符串
sorted_keys = sorted(all_params.keys())
canonical = '&'.join([f"{percent_encode(k)}={percent_encode(all_params[k])}" for k in sorted_keys])
# 生成签名
string_to_sign = f"GET&{percent_encode('/')}&{percent_encode(canonical)}"
key = (ak_secret + '&').encode('utf-8')
signature = base64.b64encode(hmac.new(key, string_to_sign.encode('utf-8'), hashlib.sha1).digest()).decode('utf-8')
# 构造最终URL
all_params['Signature'] = signature
final = '&'.join([f"{percent_encode(k)}={percent_encode(all_params[k])}" for k in sorted(all_params.keys())])
return f"https://business.aliyuncs.com/?{final}"
def call_api(ak_id, ak_secret, action, params):
"""发送请求到阿里云,获取数据"""
url = build_signed_url(ak_id, ak_secret, action, params)
req = urllib.request.Request(url)
req.add_header('Accept', 'application/json')
try:
response = urllib.request.urlopen(req, timeout=30)
return json.loads(response.read().decode('utf-8'))
except Exception as e:
print(f" ❌ 请求失败: {e}")
return None
def main():
print("=" * 60)
print(" 阿里云短信+语音服务费用统计")
print("=" * 60)
# 1. 读取AccessKey
ak_id, ak_secret = read_credentials(ACCESSKEY_FILE)
if not ak_id or not ak_secret:
print("❌ 请检查AccessKey文件路径是否正确")
return
print(f"✅ AccessKey读取成功")
print(f"📅 查询月份: {BILLING_MONTH}")
print()
# 2. 查询所有产品的费用
print("正在查询阿里云账单...")
result = call_api(ak_id, ak_secret, 'QueryBillOverview', {
'BillingCycle': BILLING_MONTH,
})
if not result or 'Data' not in result:
print("❌ 未获取到数据,请检查月份格式是否正确")
return
# 3. 提取短信和语音的费用
items = result['Data'].get('Items', {}).get('Item', [])
if isinstance(items, dict):
items = [items]
# 合并相同产品代码的费用
merged = {}
for item in items:
code = item.get('ProductCode', 'N/A')
name = item.get('ProductName', 'N/A')
amount = float(item.get('PretaxAmount', 0))
if code not in merged:
merged[code] = {'code': code, 'name': name, 'amount': 0.0}
merged[code]['amount'] += amount
print(f"\n{'产品代码':<20} {'产品名称':<25} {'费用(元)':>12}")
print("-" * 60)
sms_total = 0.0
voice_total = 0.0
for code, info in merged.items():
name = info['name']
amount = info['amount']
# 找短信和语音产品
is_sms = any(kw in name.lower() or kw in code.lower()
for kw in ['短信', 'sms', 'dysms', 'message'])
is_voice = any(kw in name.lower() or kw in code.lower()
for kw in ['语音', 'voice', 'dyvms', 'vms'])
marker = ''
if is_sms:
marker = ' ← 📱短信'
sms_total += amount
elif is_voice:
marker = ' ← 📞语音'
voice_total += amount
print(f"{code:<20} {name[:23]:<25} {amount:>12.2f}{marker}")
# 4. 输出汇总结果
print("-" * 60)
print(f"\n📊 汇总结果:")
print(f" 短信服务合计: {sms_total:.2f} 元")
print(f" 语音服务合计: {voice_total:.2f} 元")
print(f" {'─' * 40}")
print(f" 💰 总计(税前): {sms_total + voice_total:.2f} 元")
print(f"\n✅ 查询完成!")
if __name__ == '__main__':
main()
- 点击记事本的"文件" → “另存为”
- 文件名改成:
aliyun_cost.py - 保存类型一定要选"所有文件"(不是"文本文件")!
- 保存到桌面(方便找到)
3.2 改两个地方就能用
打开 aliyun_cost.py(右键→打开方式→记事本),找到这两行,改成你自己的:
# 改这里 ↓
ACCESSKEY_FILE = r"D:\你的路径\aliyun_key.txt"
# 举例:如果aliyun_key.txt在桌面
# ACCESSKEY_FILE = r"C:\Users\Administrator\Desktop\aliyun_key.txt"
# 改这里 ↓
BILLING_MONTH = "2026-04"
# 改成你要查的月份,比如 "2026-05"
3.3 运行脚本(就两步)
-
打开命令行:在脚本所在的文件夹,按住
Shift键,右键点击空白处,选择"在此处打开PowerShell窗口" -
输入命令,回车:
python aliyun_cost.py
3.4 看懂输出结果
运行后,你会看到类似这样的输出:
============================================================
阿里云短信+语音服务费用统计
============================================================
✅ AccessKey读取成功
📅 查询月份: 2026-04
正在查询阿里云账单...
产品代码 产品名称 费用(元)
------------------------------------------------------------
dysms 短信服务 50,000.00 ← 📱短信
dyvms 语音服务 17,935.66 ← 📞语音
📊 汇总结果:
短信服务合计: 50000.00 元
语音服务合计: 17935.66 元
────────────────────────────────────────
💰 总计(税前): 67935.66 元
✅ 查询完成!
每一行代表什么?
- 产品代码:阿里云内部的编号(
dysms= 短信服务,dyvms= 语音服务) - 产品名称:你能看懂的中文名字
- 费用(元):这个产品当月花了多少钱(税前)
- 📱/📞:标记表示这是短信还是语音
⚠️ 常见问题:如果输出里没有看到短信和语音,说明你的阿里云账号里没有这两个产品的费用,或者月份格式不对。
四、腾讯云费用统计(同样的套路)
4.1 复制粘贴第二个脚本
跟刚才一样,新建一个记事本,粘贴下面的代码,保存为 tencent_cost.py:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
腾讯云人脸核身费用统计脚本
功能:自动查询腾讯云账单,统计人脸核身服务的费用
使用方法:改AccessKey路径和月份,然后运行
"""
import json
import time
import hmac
import hashlib
import urllib.request
import calendar
# ════════════════════════════════════════════
# 🔧 你只需要改这两个地方!
# ════════════════════════════════════════════
# 第1处:改成你的腾讯云AccessKey文件路径
ACCESSKEY_FILE = r"D:\你的路径\tencent_key.txt"
# 第2处:改成你要查的月份
BILLING_MONTH = "2026-04"
# ════════════════════════════════════════════
# 下面的代码不需要修改
# ════════════════════════════════════════════
def read_credentials(file_path):
"""从文件中读取AccessKey"""
sid = ""
skey = ""
try:
with open(file_path, 'r', encoding='utf-8') as f:
for line in f.readlines():
line = line.strip()
if line.startswith('#'):
continue
if 'SecretId' in line or 'AKID' in line:
sid = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
elif 'SecretKey' in line:
skey = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
return sid, skey
except Exception as e:
print(f"❌ 读取AccessKey文件失败: {e}")
return None, None
def sign(key, msg):
"""腾讯云TC3签名算法的一部分"""
if isinstance(key, str):
key = key.encode('utf-8')
if isinstance(msg, str):
msg = msg.encode('utf-8')
return hmac.new(key, msg, hashlib.sha256).digest()
def sha256_hex(s):
"""计算SHA256哈希值"""
if isinstance(s, str):
s = s.encode('utf-8')
return hashlib.sha256(s).hexdigest()
def call_api(sid, skey, service, action, region, params):
"""
调用腾讯云API
注意:腾讯云的签名方式比阿里云复杂一些,但原理一样——
都是在你发送的请求上"签名",证明是你发的
"""
host = f"{service}.tencentcloudapi.com"
# 1. 构造请求内容
payload = json.dumps(params)
# 2. 构造"规范请求串"(腾讯云要求的格式)
ct = "application/json; charset=utf-8"
hashed_payload = sha256_hex(payload)
canonical = f"POST\n/\n\ncontent-type:{ct}\nhost:{host}\n\ncontent-type;host\n{hashed_payload}"
# 3. 生成签名
timestamp = str(int(time.time()))
from datetime import datetime
date = datetime.utcfromtimestamp(int(timestamp)).strftime("%Y-%m-%d")
scope = f"{date}/{service}/tc3_request"
string_to_sign = f"TC3-HMAC-SHA256\n{timestamp}\n{scope}\n{sha256_hex(canonical)}"
# 4. 计算最终签名(分4步)
secret_date = sign(("TC3" + skey).encode('utf-8'), date)
secret_service = sign(secret_date, service)
secret_signing = sign(secret_service, "tc3_request")
signature = hmac.new(secret_signing, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
# 5. 发送请求
auth = f"TC3-HMAC-SHA256 Credential={sid}/{scope}, SignedHeaders=content-type;host, Signature={signature}"
req = urllib.request.Request(f"https://{host}/", data=payload.encode('utf-8'))
req.add_header('Authorization', auth)
req.add_header('Content-Type', ct)
req.add_header('Host', host)
req.add_header('X-TC-Action', action)
req.add_header('X-TC-Version', '2018-07-09')
req.add_header('X-TC-Timestamp', timestamp)
req.add_header('X-TC-Region', region)
try:
response = urllib.request.urlopen(req, timeout=30)
return json.loads(response.read().decode('utf-8'))
except Exception as e:
print(f" ❌ 请求失败: {e}")
return None
def main():
print("=" * 60)
print(" 腾讯云人脸核身费用统计")
print("=" * 60)
# 1. 读取AccessKey
sid, skey = read_credentials(ACCESSKEY_FILE)
if not sid or not skey:
print("❌ 请检查AccessKey文件路径是否正确")
return
print(f"✅ AccessKey读取成功")
print(f"📅 查询月份: {BILLING_MONTH}")
print()
# 2. 构造时间范围(当月1号到当月最后一天)
year, mon = BILLING_MONTH.split('-')
begin = f"{year}-{mon}-01 00:00:00"
last_day = calendar.monthrange(int(year), int(mon))[1]
end = f"{year}-{mon}-{last_day:02d} 23:59:59"
# 3. 查询所有产品费用
print("正在查询腾讯云账单...")
result = call_api(sid, skey, 'billing', 'DescribeBillSummaryByProduct', 'ap-guangzhou', {
'BeginTime': begin,
'EndTime': end,
})
if not result or 'Response' not in result:
print("❌ 未获取到数据")
return
resp = result['Response']
if 'Error' in resp:
print(f"❌ API错误: {resp['Error'].get('Message', '')}")
return
# 4. 提取人脸核身的费用
items = resp.get('SummaryOverview', resp.get('SummarySet', []))
print(f"\n{'产品代码':<30} {'产品名称':<25} {'费用(元)':>12}")
print("-" * 70)
faceid_total = 0.0
all_total = 0.0
for item in items:
code = item.get('BusinessCode', 'N/A')
name = item.get('BusinessCodeName', 'N/A')
cost = float(item.get('RealTotalCost', 0)) # 腾讯云金额单位已经是元,不需要换算
all_total += cost
is_faceid = any(kw in name for kw in ['人脸核身', '云智慧眼', 'faceid', '人脸', '活体'])
marker = ' ← 🆔人脸核身' if is_faceid else ''
print(f"{code:<30} {name[:23]:<25} {cost:>12.2f}{marker}")
if is_faceid:
faceid_total += cost
print("-" * 70)
print(f"\n📊 汇总结果:")
print(f" 人脸核身服务合计: {faceid_total:.2f} 元")
print(f" 全部产品合计: {all_total:.2f} 元")
print(f"\n✅ 查询完成!")
if __name__ == '__main__':
main()
4.2 和阿里云脚本的区别(通俗解释)
两个脚本做的事情一样:查账单。但"说话方式"不同:
| 对比项 | 阿里云 | 腾讯云 |
|---|---|---|
| 签名方式 | HMAC-SHA1(老式门锁,一把钥匙开一把锁) | TC3-HMAC-SHA256(指纹锁,更安全但步骤多) |
| 请求方式 | GET(跟浏览器打开网页一样) | POST(提交表单一样) |
| 金额单位 | 元 | 元(不需要转换) |
| 字段名 | PretaxAmount |
RealTotalCost |
💡 打个比方:阿里云和腾讯云就像两家不同的银行。你要查账单,填的表格长得不一样,但目的都一样——查到花了多少钱。
4.3 运行腾讯云脚本
跟阿里云一样:
python tencent_cost.py
五、进阶:两个脚本合体 + 自动发邮件
上面的两个脚本是分开的(一个查阿里云,一个查腾讯云)。现在我们把它合并起来,一次性搞定两边,还能自动发邮件。
5.1 完整的合体脚本
新建 cloud_cost_report.py,粘贴以下代码。这次也是只改配置区域:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
云服务费用统计 + 自动发邮件
功能:同时查阿里云和腾讯云的费用,生成报表,自动发邮件
使用方法:改下面的配置区,然后运行
"""
import json, time, hmac, hashlib, base64, urllib.request, urllib.parse
import smtplib, calendar
from email.mime.text import MIMEText
from email.utils import formataddr
from datetime import datetime
# ════════════════════════════════════════════
# 🔧 配置区(只需要改这里!)
# ════════════════════════════════════════════
# 阿里云AccessKey文件
ALIYUN_KEY_FILE = r"D:\你的路径\aliyun_key.txt"
# 腾讯云AccessKey文件
TENCENT_KEY_FILE = r"D:\你的路径\tencent_key.txt"
# 查询哪个月份
MONTH = "2026-04"
# 邮箱配置(发件人)
EMAIL_CONFIG = {
"smtp_server": "smtp.qq.com", # QQ邮箱就填这个;163邮箱填 smtp.163.com
"smtp_port": 465, # QQ邮箱/163邮箱都用465
"username": "你的邮箱@qq.com", # 改成你的邮箱
"password": "你的邮箱授权码", # 注意:不是邮箱密码,是"授权码"!
"sender_name": "系统运维部", # 发件人显示的名字
}
# 收件人(可以填多个)
RECIPIENTS = ["财务@公司.com", "领导@公司.com"]
# 费用异常阈值:如果本月费用比上月偏差超过20%,自动提醒
DEVIATION_THRESHOLD = 0.20
# ════════════════════════════════════════════
# 阿里云API部分(不需要修改)
# ════════════════════════════════════════════
def read_aliyun_key(file_path):
"""读取阿里云AccessKey"""
ak_id, ak_secret = "", ""
try:
with open(file_path, 'r', encoding='utf-8') as f:
for line in f.readlines():
if 'AccessKey ID' in line:
ak_id = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
elif 'AccessKey Secret' in line:
ak_secret = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
return ak_id, ak_secret
except Exception as e:
print(f"❌ 读取阿里云AccessKey失败: {e}")
return None, None
def percent_encode(s):
s = str(s)
encoded = urllib.parse.quote(s, safe='~')
return encoded.replace('+', '%20').replace('*', '%2A')
def call_aliyun(ak_id, ak_secret, action, params):
"""调用阿里云API"""
common = {
'Format': 'JSON', 'Version': '2017-12-14', 'AccessKeyId': ak_id,
'SignatureMethod': 'HMAC-SHA1',
'Timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'SignatureVersion': '1.0',
'SignatureNonce': str(int(time.time() * 1000000)),
'Action': action,
}
all_p = {**common, **params}
sorted_k = sorted(all_p.keys())
canonical = '&'.join([f"{percent_encode(k)}={percent_encode(all_p[k])}" for k in sorted_k])
sign_str = f"GET&{percent_encode('/')}&{percent_encode(canonical)}"
key = (ak_secret + '&').encode('utf-8')
sig = base64.b64encode(hmac.new(key, sign_str.encode('utf-8'), hashlib.sha1).digest()).decode('utf-8')
all_p['Signature'] = sig
final = '&'.join([f"{percent_encode(k)}={percent_encode(all_p[k])}" for k in sorted(all_p.keys())])
url = f"https://business.aliyuncs.com/?{final}"
req = urllib.request.Request(url)
req.add_header('Accept', 'application/json')
try:
resp = urllib.request.urlopen(req, timeout=30)
return json.loads(resp.read().decode('utf-8'))
except Exception as e:
print(f" ❌ 阿里云请求失败: {e}")
return None
def fetch_aliyun_cost(month):
"""采集阿里云短信+语音费用"""
ak_id, ak_secret = read_aliyun_key(ALIYUN_KEY_FILE)
if not ak_id or not ak_secret:
return None
result = call_aliyun(ak_id, ak_secret, 'QueryBillOverview', {'BillingCycle': month})
if not result or 'Data' not in result:
return None
items = result['Data'].get('Items', {}).get('Item', [])
if isinstance(items, dict):
items = [items]
sms_total = 0.0
voice_total = 0.0
for item in items:
code = item.get('ProductCode', '')
name = item.get('ProductName', '')
amount = float(item.get('PretaxAmount', 0))
if any(kw in name.lower() or kw in code.lower() for kw in ['短信', 'sms', 'dysms', 'message']):
sms_total += amount
elif any(kw in name.lower() or kw in code.lower() for kw in ['语音', 'voice', 'dyvms', 'vms']):
voice_total += amount
return {'sms': sms_total, 'voice': voice_total, 'total': sms_total + voice_total}
# ════════════════════════════════════════════
# 腾讯云API部分(不需要修改)
# ════════════════════════════════════════════
def read_tencent_key(file_path):
"""读取腾讯云AccessKey"""
sid, skey = "", ""
try:
with open(file_path, 'r', encoding='utf-8') as f:
for line in f.readlines():
line = line.strip()
if line.startswith('#'):
continue
if 'SecretId' in line or 'AKID' in line:
sid = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
elif 'SecretKey' in line:
skey = line.split(':')[1].strip() if ':' in line else line.split(':')[1].strip()
return sid, skey
except Exception as e:
print(f"❌ 读取腾讯云AccessKey失败: {e}")
return None, None
def tc3_sign(key, msg):
if isinstance(key, str): key = key.encode('utf-8')
if isinstance(msg, str): msg = msg.encode('utf-8')
return hmac.new(key, msg, hashlib.sha256).digest()
def sha256(s):
if isinstance(s, str): s = s.encode('utf-8')
return hashlib.sha256(s).hexdigest()
def call_tencent(sid, skey, service, action, region, params):
"""调用腾讯云API"""
host = f"{service}.tencentcloudapi.com"
payload = json.dumps(params)
ct = "application/json; charset=utf-8"
canonical = f"POST\n/\n\ncontent-type:{ct}\nhost:{host}\n\ncontent-type;host\n{sha256(payload)}"
ts = str(int(time.time()))
date = datetime.utcfromtimestamp(int(ts)).strftime("%Y-%m-%d")
scope = f"{date}/{service}/tc3_request"
sign_str = f"TC3-HMAC-SHA256\n{ts}\n{scope}\n{sha256(canonical)}"
d = tc3_sign(("TC3" + skey).encode('utf-8'), date)
s = tc3_sign(d, service)
ss = tc3_sign(s, "tc3_request")
sig = hmac.new(ss, sign_str.encode('utf-8'), hashlib.sha256).hexdigest()
auth = f"TC3-HMAC-SHA256 Credential={sid}/{scope}, SignedHeaders=content-type;host, Signature={sig}"
req = urllib.request.Request(f"https://{host}/", data=payload.encode('utf-8'))
for k, v in [('Authorization', auth), ('Content-Type', ct), ('Host', host),
('X-TC-Action', action), ('X-TC-Version', '2018-07-09'),
('X-TC-Timestamp', ts), ('X-TC-Region', region)]:
req.add_header(k, v)
try:
resp = urllib.request.urlopen(req, timeout=30)
return json.loads(resp.read().decode('utf-8'))
except Exception as e:
print(f" ❌ 腾讯云请求失败: {e}")
return None
def fetch_tencent_cost(month):
"""采集腾讯云人脸核身费用"""
sid, skey = read_tencent_key(TENCENT_KEY_FILE)
if not sid or not skey:
return None
year, mon = month.split('-')
begin = f"{year}-{mon}-01 00:00:00"
last_day = calendar.monthrange(int(year), int(mon))[1]
end = f"{year}-{mon}-{last_day:02d} 23:59:59"
result = call_tencent(sid, skey, 'billing', 'DescribeBillSummaryByProduct', 'ap-guangzhou', {
'BeginTime': begin, 'EndTime': end,
})
if not result or 'Response' not in result:
return None
resp = result['Response']
if 'Error' in resp:
print(f" ❌ 腾讯云API错误: {resp['Error'].get('Message', '')}")
return None
items = resp.get('SummaryOverview', resp.get('SummarySet', []))
faceid_total = 0.0
for item in items:
name = item.get('BusinessCodeName', '')
if any(kw in name for kw in ['人脸核身', '云智慧眼', 'faceid', '人脸', '活体']):
faceid_total += float(item.get('RealTotalCost', 0))
return {'faceid': faceid_total}
# ════════════════════════════════════════════
# 邮件发送部分
# ════════════════════════════════════════════
def send_report(data):
"""生成并发送邮件报表"""
# 生成邮件内容
subject = f"{data['month']}月云服务费用(短信+语音+人脸核身)统计报表"
html = f"""
<html>
<head><meta charset="utf-8"></head>
<body style="font-family: Microsoft YaHei, sans-serif; font-size: 14px;">
<h2>📊 {data['month']}月云服务费用统计报表</h2>
<p>统计时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>用途:采购申报</p>
<hr>
<table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse;">
<tr style="background:#f5f5f5;">
<th>云厂商</th><th>产品</th><th>费用(元)</th>
</tr>
<tr>
<td>阿里云</td><td>短信服务</td><td>{data['aliyun_sms']:.2f}</td>
</tr>
<tr>
<td>阿里云</td><td>语音服务</td><td>{data['aliyun_voice']:.2f}</td>
</tr>
<tr>
<td>腾讯云</td><td>人脸核身</td><td>{data['tencent_faceid']:.2f}</td>
</tr>
<tr style="font-weight:bold;background:#e8f5e9;">
<td colspan="2">总计</td><td>{data['total']:.2f}</td>
</tr>
</table>
<br>
<p style="color:#999;font-size:12px;">数据来源:阿里云BSS OpenAPI / 腾讯云Billing API</p>
</body>
</html>
"""
# 发送邮件
try:
config = EMAIL_CONFIG
print(f"📧 正在发送邮件到: {', '.join(RECIPIENTS)}...")
server = smtplib.SMTP_SSL(config['smtp_server'], config['smtp_port'], timeout=30)
server.login(config['username'], config['password'])
msg = MIMEText(html, 'html', 'utf-8')
msg['From'] = formataddr((config['sender_name'], config['username']))
msg['To'] = ', '.join(RECIPIENTS)
msg['Subject'] = subject
server.send_message(msg)
server.quit()
print("✅ 邮件发送成功!")
return True
except Exception as e:
print(f"❌ 邮件发送失败: {e}")
return False
# ════════════════════════════════════════════
# 主程序
# ════════════════════════════════════════════
def main():
print("=" * 60)
print(" 云服务费用统计 + 邮件发送")
print("=" * 60)
print(f" 📅 查询月份: {MONTH}")
print()
# 1. 采集阿里云费用
print("📊 正在采集阿里云费用...")
aliyun = fetch_aliyun_cost(MONTH)
if not aliyun:
print(" ❌ 阿里云数据采集失败,请检查AccessKey和月份")
return
print(f" ✅ 阿里云短信: {aliyun['sms']:.2f} 元")
print(f" ✅ 阿里云语音: {aliyun['voice']:.2f} 元")
# 2. 采集腾讯云费用
print("\n📊 正在采集腾讯云费用...")
tencent = fetch_tencent_cost(MONTH)
if not tencent:
print(" ❌ 腾讯云数据采集失败,请检查AccessKey和月份")
return
print(f" ✅ 腾讯云人脸核身: {tencent['faceid']:.2f} 元")
# 3. 汇总
data = {
'month': MONTH,
'aliyun_sms': aliyun['sms'],
'aliyun_voice': aliyun['voice'],
'tencent_faceid': tencent['faceid'],
'total': aliyun['total'] + tencent['faceid'],
}
print(f"\n{'─' * 40}")
print(f" 💰 总计: {data['total']:.2f} 元")
print(f"{'─' * 40}")
# 4. 发送邮件
print()
send_report(data)
print(f"\n✅ 全部完成!")
print(f" 如需自动定时执行,对AI说:每月1号早上9点自动跑这个脚本")
if __name__ == '__main__':
main()
5.2 改配置,运行
只需要改最上面的 配置区:
# 把文件路径改成你自己的
ALIYUN_KEY_FILE = r"D:\你的路径\aliyun_key.txt"
TENCENT_KEY_FILE = r"D:\你的路径\tencent_key.txt"
MONTH = "2026-04"
# 改成你的邮箱
EMAIL_CONFIG = {
"smtp_server": "smtp.qq.com", # 用什么邮箱就填什么
"username": "你的邮箱@qq.com",
"password": "你的邮箱授权码", # QQ邮箱→设置→账户→POP3/SMTP→生成授权码
}
RECIPIENTS = ["收件人1@公司.com", "收件人2@公司.com"]
💡 怎么获取邮箱授权码?
- QQ邮箱:登录→设置→账户→POP3/IMAP/SMTP服务→开启→生成授权码
- 163邮箱:登录→设置→POP3/SMTP/IMAP→开启→新增授权码
- 授权码是一个16位的字母数字组合,不是你的邮箱密码!
5.3 运行
python cloud_cost_report.py
输出示例:
============================================================
云服务费用统计 + 邮件发送
============================================================
📅 查询月份: 2026-04
📊 正在采集阿里云费用...
✅ 阿里云短信: 50000.00 元
✅ 阿里云语音: 17935.66 元
📊 正在采集腾讯云费用...
✅ 腾讯云人脸核身: 127258.48 元
────────────────────────────────────────
💰 总计: 195194.14 元
────────────────────────────────────────
📧 正在发送邮件到: 财务@公司.com...
✅ 邮件发送成功!
✅ 全部完成!
如需自动定时执行,对AI说:每月1号早上9点自动跑这个脚本
六、更进一步:设定时任务,从此不用手动
上面的脚本已经能自动查费用、发邮件了。但每个月还要手动跑一次?太麻烦了。
如果你有WorkBuddy或类似的AI助手,直接说:
“帮我设置一个定时任务:每月1号早上9点自动运行 cloud_cost_report.py,跑完后把结果发邮件给我。”
AI会帮你配好定时任务(Windows用"任务计划程序",Mac/Linux用cron)。从此每个月自动跑,你什么都不用做。
七、常见问题
Q:运行后显示"❌ 读取AccessKey文件失败"怎么办?
A:检查三个地方:
- 文件路径对不对(注意Windows路径用
r"D:\..."这种写法) - 文件内容格式对不对(必须有
AccessKey ID:和AccessKey Secret:这两行) - AccessKey Secret是不是已经失效了(在云平台控制台重新生成一个)
Q:AccessKey放在文件里安全吗?
A:建议做两件事:
- 用只读权限的子账号AccessKey(只能看账单,不能操作资源)
- 把AccessKey文件放在你自己电脑的私密位置,不要上传到GitHub等公开平台
Q:不收短信/语音/人脸核身,能用这个脚本吗?
A:当然能!你只需要改一下脚本里的关键词:
# 把这里改成你要查的产品名称关键词
is_sms = any(kw in name for kw in ['你想查的产品名', '关键词'])
Q:Windows和Mac都能用吗?
A:都能用!Python是跨平台的,Mac用户把路径改成 /Users/你的用户名/... 就行。
八、总结
回顾一下,这篇教程你学会了:
- ✅ 准备云平台的AccessKey(子账号+只读权限)
- ✅ 用一个Python脚本,自动拉取阿里云和腾讯云的账单
- ✅ 自动生成汇总报表,发邮件给财务
- ✅ 设定时任务,每月自动跑
从头到尾,你只做了三件事:改路径、改月份、改邮箱。其他都交给脚本。
工具的意义不是让你学技术,而是让你把时间花在更有价值的事情上。
如果这篇文章对你有帮助,欢迎 👍 点赞、⭐ 收藏、💬 评论!
有问题在评论区留言,我看到都会回复。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)