From ab90f57880c424a34ce38645712dbbceb0d600a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=BA=90?= Date: Tue, 8 Sep 2026 15:57:40 +0800 Subject: [PATCH] feat: daily auto-update script (fetch prev-day, incremental merge) --- mtg_data.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ update.sh | 112 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 219 insertions(+), 5 deletions(-) mode change 100644 => 100755 update.sh diff --git a/mtg_data.json b/mtg_data.json index f171f9a..eba9474 100644 --- a/mtg_data.json +++ b/mtg_data.json @@ -4062,5 +4062,117 @@ "ctr": 0.0, "cvr": 0.0, "spend": 0.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91534913", + "offer_name": "91534913_xczyw_HK", + "product": "灵犀互动", + "pkg": "com.qookkagames.xczyw.gp.hk", + "location": "HK", + "bid_type": "", + "impression": 20629, + "clicks": 3199, + "conversions": 9, + "ctr": 0.15507, + "cvr": 0.00281, + "spend": 9.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91533906", + "offer_name": "91533906_xczyw_TW", + "product": "灵犀互动", + "pkg": "com.sialiagames.xczyw.gp.tw", + "location": "TW", + "bid_type": "", + "impression": 18027, + "clicks": 1557, + "conversions": 9, + "ctr": 0.08637, + "cvr": 0.00578, + "spend": 9.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91530537", + "offer_name": "91530537_MBCShahid_SA", + "product": "Shahid", + "pkg": "net.mbc.shahid", + "location": "SA", + "bid_type": "", + "impression": 9, + "clicks": 0, + "conversions": 0, + "ctr": 0.0, + "cvr": 0.0, + "spend": 0.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91551606", + "offer_name": "91551606_ZombieWaves_US", + "product": "Zombie", + "pkg": "com.ddup.zombiewaves.zw", + "location": "US", + "bid_type": "", + "impression": 2, + "clicks": 2, + "conversions": 0, + "ctr": 1.0, + "cvr": 0.0, + "spend": 0.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91530535", + "offer_name": "91530535_HonorofKings_IN", + "product": "Honor of Kings", + "pkg": "com.levelinfinite.sgameGlobal", + "location": "IN", + "bid_type": "", + "impression": 1, + "clicks": 1, + "conversions": 0, + "ctr": 1.0, + "cvr": 0.0, + "spend": 0.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91530536", + "offer_name": "91530536_IGG_RU", + "product": "IGG (Lords Mobile)", + "pkg": "com.igg.android.lordsmobile", + "location": "RU", + "bid_type": "", + "impression": 1, + "clicks": 0, + "conversions": 0, + "ctr": 0.0, + "cvr": 0.0, + "spend": 0.0 + }, + { + "date": "2026-09-07", + "channel": "Mintegral", + "campid": "91530547", + "offer_name": "91530547_Magalu_BR", + "product": "Magalu_BR (Android)", + "pkg": "com.luizalabs.mlapp", + "location": "BR", + "bid_type": "", + "impression": 1, + "clicks": 0, + "conversions": 0, + "ctr": 0.0, + "cvr": 0.0, + "spend": 0.0 } ] \ No newline at end of file diff --git a/update.sh b/update.sh old mode 100644 new mode 100755 index 605a6ed..c054d91 --- a/update.sh +++ b/update.sh @@ -1,7 +1,109 @@ #!/bin/bash -# update.sh - 从上传的 Excel 数据同步到 data.json -# 服务器每天自动执行一次(由 schedule.conf 控制) -# 数据来源:手动上传的 data.json(通过 git push 更新) -# 如果后续有 API 数据源,可以在这里用 curl 拉取并写入 data.json +# Moboost 报告 - 每日自动更新脚本 +# 服务器每天执行一次(schedule.conf=24)。 +# 逻辑:拉取"前一天"的 Mintegral 数据,增量合并进 mtg_data.json(去重,不覆盖历史)。 +# 工作目录 = 仓库根目录(同目录下有 mtg_data.json / campaigns.json)。 -echo "$(date '+%Y-%m-%d %H:%M:%S') - data.json is up to date (manual upload mode)" +set -e +cd "$(dirname "$0")" + +python3 - <<'PYEOF' +import hashlib, time, json, os, urllib.request, urllib.parse, urllib.error +from datetime import date, timedelta + +# ---- Mintegral 认证(access-key=平台"Success key",token secret=平台"API key")---- +API_KEY = "22793ef96b076fbdefa7c1266d4ebccb" +SKEY = "c47267b67f9e4231325efe4283044721" +BASE = "https://ss-api.mintegral.com/api/v2/reports/data" + +def headers(): + ts = str(int(time.time())) + token = hashlib.md5((SKEY + hashlib.md5(ts.encode()).hexdigest()).encode()).hexdigest() + return {"access-key": API_KEY, "token": token, "timestamp": ts} + +def req(params): + url = BASE + "?" + urllib.parse.urlencode(params) + for attempt in range(6): + try: + r = urllib.request.Request(url, headers=headers()) + with urllib.request.urlopen(r, timeout=90) as resp: + return resp.status, resp.headers.get("Content-Type",""), resp.read() + except urllib.error.HTTPError as e: + return e.code, e.headers.get("Content-Type",""), e.read() + except Exception as e: + time.sleep(5*(attempt+1)) + raise RuntimeError("网络请求失败") + +# ---- 目标 campid(Mintegral 渠道),优先从 campaigns.json 读,读不到则用内置列表 ---- +FALLBACK_IDS = ["91530535","91530534","91530536","91530547","91530548", + "91530549","91530550","91530537","91530538","91551606", + "91534913","91533906"] +try: + camps = json.load(open("campaigns.json")) + mtg = {c["campid"]: c for c in camps if c.get("channel")=="Mintegral"} +except Exception: + mtg = {} +if not mtg: + mtg = {cid: {"campid":cid,"product":"","pkg":""} for cid in FALLBACK_IDS} +mtg_ids = list(mtg.keys()) + +# ---- 目标日期:前一天(UTC+8)---- +target = (date.today() - timedelta(days=1)).isoformat() +print(f"[update] 拉取 {target} 的 Mintegral 数据 ...") + +base = {"timezone":"+8","start_time":target,"end_time":target, + "dimension_option":"Offer,Location","time_granularity":"daily"} + +# type=1 生成 +for attempt in range(20): + p = dict(base); p["type"]="1" + s,ct,b = req(p) + try: code = json.loads(b.decode("utf-8","replace")).get("code") + except: code = None + if code == 200: break + time.sleep(6) +else: + print("[update] 数据生成超时,退出"); raise SystemExit(0) + +# type=2 下载 +p = dict(base); p["type"]="2" +s,ct,b = req(p) +if "octet-stream" not in ct: + print("[update] 下载失败:", b.decode("utf-8","replace")[:200]); raise SystemExit(0) + +lines = b.decode("utf-8","replace").splitlines() +header = lines[0].split("\t"); col = {n:i for i,n in enumerate(header)} +new_rows = [] +for line in lines[1:]: + f = line.split("\t") + if len(f) < len(header): continue + offer = f[col["Offer Name"]] + matched = next((cid for cid in mtg_ids if cid in offer), None) + if not matched: continue + rec = mtg[matched] + d = f[col["Date"]] + new_rows.append({ + "date": f"{d[:4]}-{d[4:6]}-{d[6:]}", + "channel":"Mintegral","campid":matched,"offer_name":offer, + "product":rec.get("product",""),"pkg":rec.get("pkg",""), + "location":f[col["Location"]],"bid_type":"", + "impression":int(f[col["Impression"]]),"clicks":int(f[col["Click"]]), + "conversions":int(f[col["Conversion"]]), + "ctr":float(f[col["Ctr"]]),"cvr":float(f[col["Cvr"]]),"spend":float(f[col["Spend"]]), + }) +print(f"[update] 新增匹配 {len(new_rows)} 条") + +# ---- 增量合并(按 date+campid+location 去重;新数据覆盖同键,历史保留)---- +existing = [] +if os.path.exists("mtg_data.json"): + try: existing = json.load(open("mtg_data.json")) + except: existing = [] +merged = {(r["date"],r["campid"],r["location"]): r for r in existing} +for r in new_rows: + merged[(r["date"],r["campid"],r["location"])] = r +out = sorted(merged.values(), key=lambda x:(x["date"], -x["impression"])) +json.dump(out, open("mtg_data.json","w"), ensure_ascii=False, indent=2) +print(f"[update] 合并后共 {len(out)} 条,已写入 mtg_data.json") +PYEOF + +echo "[update] $(date '+%Y-%m-%d %H:%M:%S') 完成"