init: voypace ad report
This commit is contained in:
+105
@@ -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)
|
||||
Reference in New Issue
Block a user