feat: daily auto-update script (fetch prev-day, incremental merge)
This commit is contained in:
@@ -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') 完成"
|
||||
|
||||
Reference in New Issue
Block a user