#!/usr/bin/env python3 # 从 xlsx 重算 data.json。取数规则见 README 注释: # 平台消耗统计: B=Date, C=Adpingid, D=包名, I=花费 # 投手: B=Adpingid, L=Media Buyer投放人 # @某某 == 某某;未填投放人的 Adpingid 丢弃;exclude.json 里的包名永久剔除。 import openpyxl, collections, json, datetime, os, sys HERE = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.join(HERE, '2608每日数据-武汉萌奇熊.xlsx') EXCL = set(json.load(open(os.path.join(HERE, 'exclude.json')))['packages']) NON_BUYER = {'GP下架'} WINDOW_DAYS = 3 def key(v): if isinstance(v, float) and v.is_integer(): return str(int(v)) return str(v).strip() wb = openpyxl.load_workbook(SRC, data_only=True) # --- 投手表:Adpingid -> 投放人;同时收集需按 Adping 剔除的 id --- amap, dropped_ids = {}, set() for r in wb['投手'].iter_rows(min_row=2, values_only=True): adp, pkg, buyer, status = r[1], r[3], r[11], r[0] if adp is None or str(status).strip() == '示例': continue k = key(adp) if pkg is not None and str(pkg).strip() in EXCL: # 精确匹配,避免误伤 in.swiggy.android dropped_ids.add(k) continue b = str(buyer).strip() if buyer is not None else '' if b in ('', 'None') or b.lstrip('@').strip() in NON_BUYER: continue amap[k] = b.lstrip('@').strip() # --- 平台消耗统计 --- rows = [] for r in wb['平台消耗统计'].iter_rows(min_row=2, values_only=True): d, adp, pkg, spend = r[1], r[2], r[3], r[8] if d is None or adp is None or not isinstance(d, datetime.datetime): continue try: s = float(spend) if spend is not None else 0.0 except (TypeError, ValueError): s = 0.0 rows.append((d.date(), key(adp), (str(pkg).strip() if pkg else '(未知)'), s)) if not rows: sys.exit('ERROR: 平台消耗统计 无有效数据行') end = max(r[0] for r in rows) start = end - datetime.timedelta(days=WINDOW_DAYS - 1) buyers = collections.defaultdict(lambda: collections.defaultdict(float)) ids = collections.defaultdict(set) unmatched = collections.defaultdict(float) excluded_spend, excluded_rows = 0.0, 0 for d, adp, pkg, s in rows: if not (start <= d <= end): continue if pkg in EXCL or adp in dropped_ids: excluded_spend += s; excluded_rows += 1 continue if adp in amap: buyers[amap[adp]][pkg] += s ids[amap[adp]].add(adp) else: unmatched[(adp, pkg)] += s out = [] for b, pk in buyers.items(): out.append({ "name": b, "@name": b, "total": round(sum(pk.values()), 4), "pkgs": [{"pkg": k, "spend": round(v, 4)} for k, v in sorted(pk.items(), key=lambda x: -x[1])[:5]], }) out.sort(key=lambda x: -x['total']) lbl = f"{start.month}/{start.day}-{end.month}/{end.day}" data = { "meta": { "week": f"Last {WINDOW_DAYS} Days: {lbl}", "source": "数据已处理(平台消耗统计 × 投手表匹配,已剔除未填写投放人的 Adpingid 及排除包名)", "generated_at": datetime.date.today().isoformat(), "date_range": f"{start:%Y/%m/%d}-{end:%Y/%m/%d}", "note": "投放人以「投手」表 Media Buyer 列为准,@某某 已与某某合并计算;" "未填写投放人的 Adpingid 不计入;exclude.json 所列包名已排除。", }, "grand_total": round(sum(b['total'] for b in out), 4), "week34_id_count": sum(len(v) for v in ids.values()), "buyers": out, "unmatched": [{"adping": k[0], "pkg": k[1], "spend": round(v, 4)} for k, v in sorted(unmatched.items(), key=lambda x: -x[1])], } json.dump(data, open(os.path.join(HERE, 'data.json'), 'w'), ensure_ascii=False, indent=2) print(f"窗口 {start} -> {end}") print(f"排除包名命中 {excluded_rows} 行,剔除花费 {excluded_spend:.2f}") print(f"合计 {data['grand_total']:.2f} / Adpingid {data['week34_id_count']}") for b in out: print(f" {b['name']:6s} {b['total']:10.2f}")