Files
2026-09-10 15:26:54 +08:00

106 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)