78 lines
3.2 KiB
Python
78 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
||
# 一次性:从 运营/Voypace/Voypace投放计划.xlsx 的 Sheet4 生成初始 mtg_data.json
|
||
# 之后的每日增量由 adscenter_daily_sync.py 负责。
|
||
import openpyxl, json, os, datetime
|
||
|
||
XLSX = "/Users/wangyuan/Documents/SHAREit/运营/Voypace/Voypace投放计划.xlsx"
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
camps = json.load(open(os.path.join(HERE, "campaigns.json")))
|
||
CAMPS = {r["campid"]: r for r in camps}
|
||
# 渠道名归一:Sheet4 用 Mediago,campaigns 用 MediaGo
|
||
CH_NORM = {"mediago": "MediaGo", "mintegral": "Mintegral", "bigo": "Bigo"}
|
||
# 无 campid 行的兜底:按 (包名, 渠道) 反查 campid
|
||
BY_PKG_CH = {(r["pkg"], r["channel"]): r["campid"] for r in camps}
|
||
|
||
base = datetime.date(1899, 12, 30)
|
||
def to_date(v):
|
||
if isinstance(v, (int, float)):
|
||
return (base + datetime.timedelta(days=int(v))).isoformat()
|
||
if isinstance(v, (datetime.datetime, datetime.date)):
|
||
return v.strftime("%Y-%m-%d")
|
||
return str(v)[:10]
|
||
|
||
def num(v):
|
||
try: return float(str(v).replace(",", "").strip() or 0)
|
||
except: return 0.0
|
||
|
||
wb = openpyxl.load_workbook(XLSX, data_only=True)
|
||
ws = wb["Sheet4"]
|
||
rows = list(ws.iter_rows(values_only=True))
|
||
|
||
cur_date = None
|
||
out = {}
|
||
for r in rows[1:]:
|
||
# 列: 0时间 1单价 2地区 3包名 4OS 5AdpingID 6投放平台 7Imp 8Clk 9CTR 10Install 11CVR 12Spend 13oCPI
|
||
if r[0] not in (None, ""):
|
||
cur_date = to_date(r[0])
|
||
pkg = (str(r[3]).strip() if r[3] else "")
|
||
loc = (str(r[2]).strip().upper() if r[2] else "")
|
||
adping = (str(r[5]).strip() if r[5] else "")
|
||
ch_raw = (str(r[6]).strip() if r[6] else "")
|
||
ch = CH_NORM.get(ch_raw.lower(), ch_raw)
|
||
if not ch or not cur_date:
|
||
continue
|
||
# 定位 campid
|
||
cid = adping.split(".")[0] if adping else ""
|
||
if cid not in CAMPS:
|
||
# 兜底:靠 包名+渠道(处理 Avito 那种 campid 为 campaign_name 的行)
|
||
# 包名可能带 (APK)等后缀,取主体
|
||
pkg_main = pkg.split("(")[0].split("(")[0].strip()
|
||
cid = BY_PKG_CH.get((pkg_main, ch), "")
|
||
if cid not in CAMPS:
|
||
continue
|
||
info = CAMPS[cid]
|
||
imp = int(num(r[7])); clk = int(num(r[8])); cv = int(num(r[10])); sp = round(num(r[12]), 2)
|
||
if not loc:
|
||
loc = str(info.get("geo", "")).upper()
|
||
key = (cur_date, info["channel"], cid, loc)
|
||
a = out.get(key)
|
||
if a:
|
||
a["impression"] += imp; a["clicks"] += clk; a["conversions"] += cv; a["spend"] = round(a["spend"] + sp, 2)
|
||
else:
|
||
out[key] = {
|
||
"date": cur_date, "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, "spend": sp,
|
||
}
|
||
|
||
data = []
|
||
for a in out.values():
|
||
imp, clk, cv = a["impression"], a["clicks"], a["conversions"]
|
||
a["ctr"] = round(clk/imp, 5) if imp else 0.0
|
||
a["cvr"] = round(cv/clk, 5) if clk else 0.0
|
||
data.append(a)
|
||
data.sort(key=lambda x: (x["date"], x["channel"], -x["impression"]))
|
||
json.dump(data, open(os.path.join(HERE, "mtg_data.json"), "w"), ensure_ascii=False, indent=2)
|
||
print(f"生成 {len(data)} 条 -> mtg_data.json")
|