init: voypace ad report

This commit is contained in:
王源
2026-09-10 15:26:54 +08:00
commit 3c525a9672
9 changed files with 1833 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
# 从本机 Excel 补齐「数据库没有表」的渠道数据(Adsterra / TrafficStars / Eagllwin 等)。
# 数据库只有 mintegral/bigo/mediago,其余渠道由运营手工维护到 Excel,本脚本读取后
# 输出成与 db_fetch.py 相同结构的 JSON(stdout),供 daily_sync 合并进 mtg_data.json。
#
# ── Excel 规范 ──────────────────────────────────────────────
# 路径:<运营>/Voypace/Voypace补充数据.xlsx (可用环境变量 VOYPACE_XLSX 覆盖)
# 第一个 sheet,首行为表头,需包含以下列(列名大小写/顺序不限):
# date 日期,yyyy-MM-dd(或 Excel 日期)
# campid 对应 campaigns.json 里的 campid
# impression 展示
# clicks 点击
# conversions 转化/安装
# spend 花费(USD)
# 可选列:location(国家,缺省用 campaigns.json 里的 geo)
# 渠道/产品/包名 一律由 campid 反查 campaigns.json,Excel 无需再填。
# 只读取 campid 命中 campaigns.json 的行;其它行忽略。
# ────────────────────────────────────────────────────────────
import json, sys, os, datetime
HERE = os.path.dirname(os.path.abspath(__file__))
_camps = json.load(open(os.path.join(HERE, "campaigns.json")))
CAMPS = {str(r["campid"]): r for r in _camps}
DEFAULT_XLSX = os.environ.get(
"VOYPACE_XLSX",
"/Users/wangyuan/Documents/SHAREit/运营/Voypace/Voypace补充数据.xlsx",
)
def norm_date(v):
if v is None: return ""
if isinstance(v, (datetime.datetime, datetime.date)):
return v.strftime("%Y-%m-%d")
s = str(v).strip()
return s[:10] if s else ""
def to_int(v):
try: return int(float(str(v).replace(",", "").strip() or 0))
except: return 0
def to_float(v):
try: return float(str(v).replace(",", "").replace("$", "").strip() or 0)
except: return 0.0
def main(path):
if not os.path.exists(path):
# 没有补充表就输出空数组,daily_sync 照常跑
print("[]"); return
try:
import openpyxl
except ImportError:
sys.stderr.write("openpyxl 未安装,跳过 Excel 补数\n")
print("[]"); return
wb = openpyxl.load_workbook(path, data_only=True)
ws = wb.worksheets[0]
rows = list(ws.iter_rows(values_only=True))
if not rows:
print("[]"); return
header = [str(c).strip().lower() if c is not None else "" for c in rows[0]]
def col(*names):
for n in names:
if n in header: return header.index(n)
return None
ci = {
"date": col("date", "时间", "日期"),
"campid": col("campid", "adping id", "adping_id"),
"loc": col("location", "地区", "国家"),
"imp": col("impression", "impressions", "展示"),
"clk": col("clicks", "click", "点击"),
"cv": col("conversions", "install", "installs", "转化", "安装"),
"spend": col("spend", "花费", "cost"),
}
out = []
for r in rows[1:]:
if ci["campid"] is None: break
cid = str(r[ci["campid"]]).strip() if r[ci["campid"]] is not None else ""
cid = cid.split(".")[0] # 去掉可能的 .0
if cid not in CAMPS: continue
info = CAMPS[cid]
loc = ""
if ci["loc"] is not None and r[ci["loc"]] is not None:
loc = str(r[ci["loc"]]).strip().upper()
if not loc: loc = str(info.get("geo", "")).upper()
imp = to_int(r[ci["imp"]]) if ci["imp"] is not None else 0
clk = to_int(r[ci["clk"]]) if ci["clk"] is not None else 0
cv = to_int(r[ci["cv"]]) if ci["cv"] is not None else 0
sp = to_float(r[ci["spend"]]) if ci["spend"] is not None else 0.0
dt = norm_date(r[ci["date"]]) if ci["date"] is not None else ""
if not dt: continue
out.append({
"date": dt, "channel": info["channel"], "campid": cid,
"offer_name": f'{cid}_{info["product"]}', "product": info["product"],
"pkg": info["pkg"], "location": loc, "bid_type": "",
"impression": imp, "clicks": clk, "conversions": cv,
"ctr": round(clk/imp, 5) if imp else 0.0,
"cvr": round(cv/clk, 5) if clk else 0.0,
"spend": round(sp, 2),
})
print(json.dumps(out, ensure_ascii=False))
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else DEFAULT_XLSX)