Support monthly source rollover; update data - 8/29-8/31
The 2609 file carries only 8/31 and has no 投手 sheet, so build_data.py now globs every *每日数据-武汉萌奇熊.xlsx, merges 平台消耗统计 per date (newer filename wins for a repeated date), and takes the roster from the newest file that still has a 投手 sheet. Future month rollovers need no code change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
+36
-16
@@ -5,10 +5,14 @@
|
|||||||
# 规则:@某某 == 某某;未填投放人的 Adpingid 丢弃;
|
# 规则:@某某 == 某某;未填投放人的 Adpingid 丢弃;
|
||||||
# exclude.json 的 packages(按包名)与 adpings(按 Adpingid)永久剔除,均为精确匹配。
|
# exclude.json 的 packages(按包名)与 adpings(按 Adpingid)永久剔除,均为精确匹配。
|
||||||
# 每个 package 明细:Geo 后缀 = 该 package 花费最高的地区;Adpingid = 该地区内花费最高的 Adping。
|
# 每个 package 明细:Geo 后缀 = 该 package 花费最高的地区;Adpingid = 该地区内花费最高的 Adping。
|
||||||
import openpyxl, collections, json, datetime, os, sys
|
import openpyxl, collections, json, datetime, os, sys, glob
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
SRC = os.path.join(HERE, '2608每日数据-武汉萌奇熊.xlsx')
|
# 源文件按月滚动(2608 -> 2609 -> ...)。全部读进来合并:
|
||||||
|
# 同一天出现在多个文件时,以文件名较新的为准(源表会修订历史数据)。
|
||||||
|
SRC_FILES = sorted(glob.glob(os.path.join(HERE, '*每日数据-武汉萌奇熊.xlsx')))
|
||||||
|
if not SRC_FILES:
|
||||||
|
sys.exit('ERROR: 仓库里找不到 *每日数据-武汉萌奇熊.xlsx')
|
||||||
_cfg = json.load(open(os.path.join(HERE, 'exclude.json')))
|
_cfg = json.load(open(os.path.join(HERE, 'exclude.json')))
|
||||||
EXCL = set(_cfg['packages']) # 按包名剔除
|
EXCL = set(_cfg['packages']) # 按包名剔除
|
||||||
EXCL_ADP = set(_cfg.get('adpings', {})) # 按 Adpingid 剔除
|
EXCL_ADP = set(_cfg.get('adpings', {})) # 按 Adpingid 剔除
|
||||||
@@ -20,12 +24,20 @@ def key(v):
|
|||||||
return str(int(v))
|
return str(int(v))
|
||||||
return str(v).strip()
|
return str(v).strip()
|
||||||
|
|
||||||
wb = openpyxl.load_workbook(SRC, data_only=True)
|
books = [(f, openpyxl.load_workbook(f, data_only=True)) for f in SRC_FILES]
|
||||||
|
|
||||||
|
# --- 投手表:取最新一个带「投手」sheet 的文件(月度文件可能不含该表)---
|
||||||
|
roster_file, roster = None, None
|
||||||
|
for f, wb_ in reversed(books):
|
||||||
|
if '投手' in wb_.sheetnames:
|
||||||
|
roster_file, roster = f, wb_['投手']
|
||||||
|
break
|
||||||
|
if roster is None:
|
||||||
|
sys.exit('ERROR: 所有源文件都没有「投手」sheet,无法归属投放人')
|
||||||
|
|
||||||
# --- 投手表:Adpingid -> 投放人 ---
|
|
||||||
amap = {}
|
amap = {}
|
||||||
dropped_ids = set(EXCL_ADP) # 先纳入按 Adpingid 剔除的,再补上按包名命中的
|
dropped_ids = set(EXCL_ADP) # 先纳入按 Adpingid 剔除的,再补上按包名命中的
|
||||||
for r in wb['投手'].iter_rows(min_row=2, values_only=True):
|
for r in roster.iter_rows(min_row=2, values_only=True):
|
||||||
adp, pkg, buyer, status = r[1], r[3], r[11], r[0]
|
adp, pkg, buyer, status = r[1], r[3], r[11], r[0]
|
||||||
if adp is None or str(status).strip() == '示例':
|
if adp is None or str(status).strip() == '示例':
|
||||||
continue
|
continue
|
||||||
@@ -42,19 +54,25 @@ for r in wb['投手'].iter_rows(min_row=2, values_only=True):
|
|||||||
|
|
||||||
owned = collections.Counter(amap.values()) # 每位投手名下的 Adpingid 数(已扣除排除包名)
|
owned = collections.Counter(amap.values()) # 每位投手名下的 Adpingid 数(已扣除排除包名)
|
||||||
|
|
||||||
# --- 平台消耗统计 ---
|
# --- 平台消耗统计:逐文件读取,按日期归组,新文件覆盖同一天的旧数据 ---
|
||||||
rows = []
|
by_date = {}
|
||||||
for r in wb['平台消耗统计'].iter_rows(min_row=2, values_only=True):
|
for f, wb_ in books:
|
||||||
d, adp, pkg, geo, spend = r[1], r[2], r[3], r[4], r[8]
|
if '平台消耗统计' not in wb_.sheetnames:
|
||||||
if d is None or adp is None or not isinstance(d, datetime.datetime):
|
|
||||||
continue
|
continue
|
||||||
try:
|
day = collections.defaultdict(list)
|
||||||
s = float(spend) if spend is not None else 0.0
|
for r in wb_['平台消耗统计'].iter_rows(min_row=2, values_only=True):
|
||||||
except (TypeError, ValueError):
|
d, adp, pkg, geo, spend = r[1], r[2], r[3], r[4], r[8]
|
||||||
s = 0.0
|
if d is None or adp is None or not isinstance(d, datetime.datetime):
|
||||||
rows.append((d.date(), key(adp), (str(pkg).strip() if pkg else '(未知)'),
|
continue
|
||||||
(str(geo).strip() if geo else ''), s))
|
try:
|
||||||
|
s = float(spend) if spend is not None else 0.0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
s = 0.0
|
||||||
|
day[d.date()].append((d.date(), key(adp), (str(pkg).strip() if pkg else '(未知)'),
|
||||||
|
(str(geo).strip() if geo else ''), s))
|
||||||
|
by_date.update(day) # books 已按文件名升序,后者覆盖前者
|
||||||
|
|
||||||
|
rows = [r for day_rows in by_date.values() for r in day_rows]
|
||||||
if not rows:
|
if not rows:
|
||||||
sys.exit('ERROR: 平台消耗统计 无有效数据行')
|
sys.exit('ERROR: 平台消耗统计 无有效数据行')
|
||||||
|
|
||||||
@@ -132,6 +150,8 @@ data = {
|
|||||||
}
|
}
|
||||||
json.dump(data, open(os.path.join(HERE, 'data.json'), 'w'), ensure_ascii=False, indent=2)
|
json.dump(data, open(os.path.join(HERE, 'data.json'), 'w'), ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print('源文件:', ', '.join(os.path.basename(f) for f in SRC_FILES))
|
||||||
|
print('投手表取自:', os.path.basename(roster_file))
|
||||||
print(f"窗口 {start} -> {end}")
|
print(f"窗口 {start} -> {end}")
|
||||||
print(f"排除包名命中 {excluded_rows} 行,剔除花费 {excluded_spend:.2f}")
|
print(f"排除包名命中 {excluded_rows} 行,剔除花费 {excluded_spend:.2f}")
|
||||||
print(f"合计 {data['grand_total']:.2f} / Adpingid {data['week34_id_count']}")
|
print(f"合计 {data['grand_total']:.2f} / Adpingid {data['week34_id_count']}")
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"week": "Last 3 Days: 8/28-8/30",
|
"week": "Last 3 Days: 8/29-8/31",
|
||||||
"source": "萌奇熊日报",
|
"source": "萌奇熊日报",
|
||||||
"generated_at": "2026-08-31",
|
"generated_at": "2026-09-01",
|
||||||
"date_range": "2026/08/28-2026/08/30",
|
"date_range": "2026/08/29-2026/08/31",
|
||||||
"note": "投放人以「投手」表 Media Buyer 列为准,@某某 已与某某合并计算;未填写投放人的 Adpingid 不计入;exclude.json 所列包名已排除。Package 后缀为该 Package 花费最高的投放地区,Adpingid 为该地区内花费最高的 Adping。"
|
"note": "投放人以「投手」表 Media Buyer 列为准,@某某 已与某某合并计算;未填写投放人的 Adpingid 不计入;exclude.json 所列包名已排除。Package 后缀为该 Package 花费最高的投放地区,Adpingid 为该地区内花费最高的 Adping。"
|
||||||
},
|
},
|
||||||
"grand_total": 11581.3293,
|
"grand_total": 11872.5278,
|
||||||
"week34_id_count": 123,
|
"week34_id_count": 119,
|
||||||
"buyers": [
|
"buyers": [
|
||||||
{
|
{
|
||||||
"name": "黄政",
|
"name": "黄政",
|
||||||
"@name": "黄政",
|
"@name": "黄政",
|
||||||
"total": 3344.4122,
|
"total": 3480.3241,
|
||||||
"active_ids": 32,
|
"active_ids": 31,
|
||||||
"owned_ids": 133,
|
"owned_ids": 133,
|
||||||
"pkgs": [
|
"pkgs": [
|
||||||
{
|
{
|
||||||
@@ -21,42 +21,42 @@
|
|||||||
"geo": "SA",
|
"geo": "SA",
|
||||||
"label": "com.amazon.mShop.android.shopping_SA",
|
"label": "com.amazon.mShop.android.shopping_SA",
|
||||||
"adping": "2732727",
|
"adping": "2732727",
|
||||||
"spend": 1145.5168
|
"spend": 1220.3056
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.king.candycrushsodasaga",
|
"pkg": "com.king.candycrushsodasaga",
|
||||||
"geo": "BR",
|
"geo": "DE",
|
||||||
"label": "com.king.candycrushsodasaga_BR",
|
"label": "com.king.candycrushsodasaga_DE",
|
||||||
"adping": "8460883",
|
"adping": "6951594",
|
||||||
"spend": 1098.694
|
"spend": 1089.63
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.yandex.lavka",
|
"pkg": "com.yandex.lavka",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "com.yandex.lavka_RU",
|
"label": "com.yandex.lavka_RU",
|
||||||
"adping": "8156506",
|
"adping": "8156506",
|
||||||
"spend": 360.9953
|
"spend": 340.0207
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.fxpro.direct.application",
|
"pkg": "com.fxpro.direct.application",
|
||||||
"geo": "PK",
|
"geo": "PK",
|
||||||
"label": "com.fxpro.direct.application_PK",
|
"label": "com.fxpro.direct.application_PK",
|
||||||
"adping": "7202934",
|
"adping": "7202934",
|
||||||
"spend": 234.09
|
"spend": 233.96
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "ru.banki.banki",
|
"pkg": "tech.jm",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "ru.banki.banki_RU",
|
"label": "tech.jm_RU",
|
||||||
"adping": "91548044",
|
"adping": "1745925",
|
||||||
"spend": 137.5186
|
"spend": 156.7521
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "万科",
|
"name": "万科",
|
||||||
"@name": "万科",
|
"@name": "万科",
|
||||||
"total": 2426.0128,
|
"total": 2318.8753,
|
||||||
"active_ids": 24,
|
"active_ids": 24,
|
||||||
"owned_ids": 112,
|
"owned_ids": 112,
|
||||||
"pkgs": [
|
"pkgs": [
|
||||||
@@ -65,42 +65,42 @@
|
|||||||
"geo": "TH",
|
"geo": "TH",
|
||||||
"label": "com.king.candycrushsaga_TH",
|
"label": "com.king.candycrushsaga_TH",
|
||||||
"adping": "5497580",
|
"adping": "5497580",
|
||||||
"spend": 1059.413
|
"spend": 968.234
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "ru.aliexpress.buyer",
|
"pkg": "ru.aliexpress.buyer",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "ru.aliexpress.buyer_RU",
|
"label": "ru.aliexpress.buyer_RU",
|
||||||
"adping": "6607117",
|
"adping": "6607117",
|
||||||
"spend": 953.539
|
"spend": 938.279
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "ru.mobstudio.andgalaxy",
|
"pkg": "ru.mobstudio.andgalaxy",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "ru.mobstudio.andgalaxy_RU",
|
"label": "ru.mobstudio.andgalaxy_RU",
|
||||||
"adping": "7953512",
|
"adping": "7953512",
|
||||||
"spend": 152.69
|
"spend": 144.16
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "ru.kinopoisk",
|
"pkg": "ru.kinopoisk",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "ru.kinopoisk_RU",
|
"label": "ru.kinopoisk_RU",
|
||||||
"adping": "10599681",
|
"adping": "10599681",
|
||||||
"spend": 126.88
|
"spend": 126.23
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.edadeal.android",
|
"pkg": "com.edadeal.android",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "com.edadeal.android_RU",
|
"label": "com.edadeal.android_RU",
|
||||||
"adping": "10599157",
|
"adping": "10599157",
|
||||||
"spend": 81.2
|
"spend": 85.55
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "谭超",
|
"name": "谭超",
|
||||||
"@name": "谭超",
|
"@name": "谭超",
|
||||||
"total": 2250.706,
|
"total": 2311.848,
|
||||||
"active_ids": 23,
|
"active_ids": 23,
|
||||||
"owned_ids": 124,
|
"owned_ids": 124,
|
||||||
"pkgs": [
|
"pkgs": [
|
||||||
@@ -109,43 +109,43 @@
|
|||||||
"geo": "ID",
|
"geo": "ID",
|
||||||
"label": "com.kwai.bulldog_ID",
|
"label": "com.kwai.bulldog_ID",
|
||||||
"adping": "7815836",
|
"adping": "7815836",
|
||||||
"spend": 1148.97
|
"spend": 1149.95
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.vitastudio.mahjong",
|
"pkg": "com.vitastudio.mahjong",
|
||||||
"geo": "ID",
|
"geo": "ID",
|
||||||
"label": "com.vitastudio.mahjong_ID",
|
"label": "com.vitastudio.mahjong_ID",
|
||||||
"adping": "91467820",
|
"adping": "91467820",
|
||||||
"spend": 465.14
|
"spend": 519.33
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "astrum.ent.m2alyona",
|
"pkg": "astrum.ent.m2alyona",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "astrum.ent.m2alyona_RU",
|
"label": "astrum.ent.m2alyona_RU",
|
||||||
"adping": "11690181",
|
"adping": "11690181",
|
||||||
"spend": 273.82
|
"spend": 263.12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "tech.jm",
|
"pkg": "tech.jm",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "tech.jm_RU",
|
"label": "tech.jm_RU",
|
||||||
"adping": "9297014",
|
"adping": "9297014",
|
||||||
"spend": 116.73
|
"spend": 196.63
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "in.startv.hotstar.dplus",
|
"pkg": "in.startv.hotstar.dplus",
|
||||||
"geo": "ID",
|
"geo": "ID",
|
||||||
"label": "in.startv.hotstar.dplus_ID",
|
"label": "in.startv.hotstar.dplus_ID",
|
||||||
"adping": "9096620",
|
"adping": "9096620",
|
||||||
"spend": 71.95
|
"spend": 73.74
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "周梓君",
|
"name": "周梓君",
|
||||||
"@name": "周梓君",
|
"@name": "周梓君",
|
||||||
"total": 1860.5323,
|
"total": 1954.4797,
|
||||||
"active_ids": 27,
|
"active_ids": 26,
|
||||||
"owned_ids": 119,
|
"owned_ids": 119,
|
||||||
"pkgs": [
|
"pkgs": [
|
||||||
{
|
{
|
||||||
@@ -153,14 +153,14 @@
|
|||||||
"geo": "KZ",
|
"geo": "KZ",
|
||||||
"label": "ru.ozon.app.android_KZ",
|
"label": "ru.ozon.app.android_KZ",
|
||||||
"adping": "5963354",
|
"adping": "5963354",
|
||||||
"spend": 974.2013
|
"spend": 1000.9987
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "ru.kinopoisk",
|
"pkg": "ru.kinopoisk",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "ru.kinopoisk_RU",
|
"label": "ru.kinopoisk_RU",
|
||||||
"adping": "8291713",
|
"adping": "8291713",
|
||||||
"spend": 575.96
|
"spend": 630.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.edadeal.android",
|
"pkg": "com.edadeal.android",
|
||||||
@@ -174,22 +174,22 @@
|
|||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "com.yandex.bank_RU",
|
"label": "com.yandex.bank_RU",
|
||||||
"adping": "2937658",
|
"adping": "2937658",
|
||||||
"spend": 65.721
|
"spend": 75.981
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.appteka.lapy",
|
"pkg": "com.appteka.lapy",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "com.appteka.lapy_RU",
|
"label": "com.appteka.lapy_RU",
|
||||||
"adping": "5623760",
|
"adping": "5623760",
|
||||||
"spend": 17.2
|
"spend": 21.2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "詹涵粤",
|
"name": "詹涵粤",
|
||||||
"@name": "詹涵粤",
|
"@name": "詹涵粤",
|
||||||
"total": 1699.666,
|
"total": 1807.0007,
|
||||||
"active_ids": 17,
|
"active_ids": 15,
|
||||||
"owned_ids": 144,
|
"owned_ids": 144,
|
||||||
"pkgs": [
|
"pkgs": [
|
||||||
{
|
{
|
||||||
@@ -197,50 +197,50 @@
|
|||||||
"geo": "MY",
|
"geo": "MY",
|
||||||
"label": "com.king.candycrushsaga_MY",
|
"label": "com.king.candycrushsaga_MY",
|
||||||
"adping": "5464039",
|
"adping": "5464039",
|
||||||
"spend": 1144.35
|
"spend": 1154.11
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.esprit.rpg.lttlefirykng",
|
"pkg": "com.esprit.rpg.lttlefirykng",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "com.esprit.rpg.lttlefirykng_RU",
|
"label": "com.esprit.rpg.lttlefirykng_RU",
|
||||||
"adping": "10514860",
|
"adping": "10514860",
|
||||||
"spend": 311.45
|
"spend": 359.1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.reefgames.fabletown",
|
"pkg": "com.reefgames.fabletown",
|
||||||
"geo": "RU",
|
"geo": "RU",
|
||||||
"label": "com.reefgames.fabletown_RU",
|
"label": "com.reefgames.fabletown_RU",
|
||||||
"adping": "91532857",
|
"adping": "91532857",
|
||||||
"spend": 70.9804
|
"spend": 63.8392
|
||||||
},
|
|
||||||
{
|
|
||||||
"pkg": "net.peakgames.Yuzbir",
|
|
||||||
"geo": "TR",
|
|
||||||
"label": "net.peakgames.Yuzbir_TR",
|
|
||||||
"adping": "91550293",
|
|
||||||
"spend": 38.75
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "com.netease.newspike",
|
"pkg": "com.netease.newspike",
|
||||||
"geo": "US",
|
"geo": "US",
|
||||||
"label": "com.netease.newspike_US",
|
"label": "com.netease.newspike_US",
|
||||||
"adping": "91551388",
|
"adping": "91551388",
|
||||||
"spend": 36.6713
|
"spend": 62.3091
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pkg": "net.peakgames.Yuzbir",
|
||||||
|
"geo": "TR",
|
||||||
|
"label": "net.peakgames.Yuzbir_TR",
|
||||||
|
"adping": "91550293",
|
||||||
|
"spend": 45.85
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"unmatched": [
|
"unmatched": [
|
||||||
|
{
|
||||||
|
"adping": "11729927",
|
||||||
|
"pkg": "com.amazon.mShop.android.shopping",
|
||||||
|
"spend": 13.5
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"adping": "11729830",
|
"adping": "11729830",
|
||||||
"pkg": "com.amazon.mShop.android.shopping",
|
"pkg": "com.amazon.mShop.android.shopping",
|
||||||
"spend": 0.002
|
"spend": 0.002
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"adping": "11729927",
|
|
||||||
"pkg": "com.amazon.mShop.android.shopping",
|
|
||||||
"spend": 0.0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"adping": "11729820",
|
"adping": "11729820",
|
||||||
"pkg": "com.amazon.mShop.android.shopping",
|
"pkg": "com.amazon.mShop.android.shopping",
|
||||||
|
|||||||
Reference in New Issue
Block a user