gen_gap_xlsx.py
24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#!/usr/bin/env python3
"""生成港乐词曲资产数据缺口清单 Excel"""
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
wb = openpyxl.Workbook()
# ── 样式定义 ──────────────────────────────────────────
title_font = Font(name="Microsoft YaHei", size=12, bold=True, color="FFFFFFFF")
title_fill = PatternFill(start_color="FF4472C4", end_color="FF4472C4", fill_type="solid")
title_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
header_font = Font(name="宋体", size=10.5, bold=True)
header_fill = PatternFill(start_color="FFD9E2F3", end_color="FFD9E2F3", fill_type="solid")
header_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
data_font = Font(name="Microsoft YaHei", size=10)
data_align = Alignment(vertical="top", wrap_text=True)
center_align = Alignment(horizontal="center", vertical="top", wrap_text=True)
# 处理方式颜色
FILL_RED = PatternFill(start_color="FFFFC7CE", end_color="FFFFC7CE", fill_type="solid") # 🔴 人工
FILL_GREEN = PatternFill(start_color="FFC6EFCE", end_color="FFC6EFCE", fill_type="solid") # 🟢 爬虫
FILL_YELLOW = PatternFill(start_color="FFFFEB9C", end_color="FFFFEB9C", fill_type="solid") # 🟡 待扩展
FILL_GRAY = PatternFill(start_color="FFD9D9D9", end_color="FFD9D9D9", fill_type="solid") # ⚪ 暂不可操作
thin_border = Border(
left=Side(style="thin"), right=Side(style="thin"),
top=Side(style="thin"), bottom=Side(style="thin"),
)
# ── Sheet 1: 数据缺口清单 ─────────────────────────────
ws1 = wb.active
ws1.title = "数据缺口清单"
# 标题行
ws1.merge_cells("A1:G1")
c = ws1["A1"]
c.value = "港乐词曲资产导入 — 数据缺口清单(2026-07-14)"
c.font = title_font; c.fill = title_fill; c.alignment = title_align
# 表头
headers = ["序号", "缺口类型", "数量", "处理方式", "涉及数据", "补齐说明", "优先级"]
for i, h in enumerate(headers, 1):
cell = ws1.cell(row=2, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
# 数据
rows = [
# (序号, 缺口类型, 数量, 处理方式, 涉及数据, 补齐说明, 优先级, fill)
(1, "疑似重复,待人工审核", "4,410 首",
"🔴 人工处理", "去重暂存表中的候选记录",
"系统判定与已有歌曲高度相似,需人工逐条判断是同一首歌(合并)还是不同歌曲(新增导入)。已部署 L2 审核看板支持批量操作。",
"P2", FILL_RED),
(2, "歌词为空且词曲作者均不详", "1,167 首",
"🔴 人工处理", "源库中歌词和作者字段同时缺失的歌曲",
"源数据本身缺失,爬虫无法生成不存在的元数据。如需导入,需人工查找并补录歌词和作者信息后重新触发导入。可先抽样检查这批歌曲在平台上是否实际有歌词。",
"P3", FILL_RED),
(3, "主录音导入失败——缺歌手信息", "96 条",
"🟢 爬虫补录", "records1 中因 spider DB 缺歌手关系而失败的记录",
"平台上有歌手信息但尚未同步到本地爬虫数据库。补录歌手关系后系统自动重试。也可调整策略允许空歌手容错(补充录音已支持,主录音暂未支持)。",
"P1", FILL_GREEN),
(4, "主录音导入失败——音频/歌词转存失败", "19 条",
"🟢 自动重试", "records1 中因网络或存储问题转存失败的记录(音频 10 条 + 歌词 9 条)",
"属于临时性故障,源文件仍在平台上。重新触发转存即可,无需重新爬取。",
"P1", FILL_GREEN),
(5, "主录音导入失败——完全无歌词", "3 条",
"🟢 爬虫尝试", "records1 中三个平台均无歌词数据的记录",
"可尝试爬虫从其他来源抓取歌词;如平台确实无歌词,需人工确认是否为纯音乐作品。数量极少,影响可忽略。",
"P2", FILL_GREEN),
(6, "歌曲仅有非目标平台录音", "809 首",
"🟡 扩展平台", "在三平台(QQ/酷狗/网易云)均无有效录音,仅在其他平台有录音的歌曲",
"当前爬虫仅覆盖三个目标平台。扩展平台覆盖后可自动获取录音数据。需业务确认:是否接受这些歌曲暂不入库?",
"P3", FILL_YELLOW),
(7, "已软删歌曲——录音已成功但歌曲不可见", "25 首",
"🔴 人工恢复", "records1 已成功但歌曲状态仍为 deleted 的记录",
"历史遗留的状态不一致,主录音数据已入库但歌曲被标记为已删除导致前端不可见。确认后批量恢复即可,无需重新爬取。",
"P0", FILL_RED),
(8, "已软删歌曲——现具备三平台录音条件", "71 首",
"🔴 人工恢复", "之前因审计/清理被软删除,但当前已具备有效录音条件的歌曲",
"数据已补齐,只需逐条确认删除原因是否已过时,符合条件后恢复歌曲状态。",
"P2", FILL_RED),
(9, "已软删歌曲——当前仍无有效三平台录音", "133 首",
"⚪ 暂不处理", "被软删除且当前仍无法获取三平台录音的歌曲(含 ETL 清理 143 中剩余部分 + song_time=0 + 元数据重复合并)",
"当前无法补齐录音数据。若后续扩展平台覆盖或有其他数据来源,可重新评估。",
"P3", FILL_GRAY),
(10, "原声类歌名", "79 首",
"🔴 人工处理", "歌名形如 '@XXX创作的原声' 或 '用户创作的原声' 的特殊歌曲",
"歌名为平台自动生成的标记文本,通常不具备导入价值。其中 78 首同时满足歌词/作者缺失条件被前置规则先拦截,仅 1 首 dedup_reason 显式标注为原声类。建议直接跳过。",
"P3", FILL_RED),
]
for r_idx, (no, gtype, count, method, scope, desc, priority, fill) in enumerate(rows, 3):
vals = [no, gtype, count, method, scope, desc, priority]
for c_idx, v in enumerate(vals, 1):
cell = ws1.cell(row=r_idx, column=c_idx, value=v)
cell.font = data_font
cell.alignment = center_align if c_idx in (1, 3, 4, 7) else data_align
cell.border = thin_border
# 处理方式列上色
if c_idx == 4:
cell.fill = fill
# 列宽
col_widths = [6, 30, 10, 14, 30, 55, 6]
for i, w in enumerate(col_widths, 1):
ws1.column_dimensions[get_column_letter(i)].width = w
# ── Sheet 2: 总览 ────────────────────────────────────
ws2 = wb.create_sheet("总览")
ws2.merge_cells("A1:C1")
c = ws2["A1"]
c.value = "港乐词曲资产导入 — 缺口处理总览"
c.font = title_font; c.fill = title_fill; c.alignment = title_align
for i, h in enumerate(["处理方式", "涉及数量", "占比"], 1):
cell = ws2.cell(row=2, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
summary = [
("🔴 需人工处理(审核/恢复/补录)", "5,752 首", "84.1%", FILL_RED),
("🟢 可通过爬虫/自动手段解决", "118 条", "1.7%", FILL_GREEN),
("🟡 需扩展平台覆盖后自动导入", "809 首", "11.9%", FILL_YELLOW),
("⚪ 暂不可操作,等待外部条件变化", "133 首", "2.0%", FILL_GRAY),
("缺口合计", "约 6,812 首/条", "—", None),
]
for r_idx, (cat, cnt, pct, fill) in enumerate(summary, 3):
for c_idx, v in enumerate([cat, cnt, pct], 1):
cell = ws2.cell(row=r_idx, column=c_idx, value=v)
cell.font = data_font if r_idx < 7 else Font(name="Microsoft YaHei", size=10, bold=True)
cell.alignment = center_align if c_idx >= 2 else Alignment(vertical="top", wrap_text=True)
cell.border = thin_border
if fill and c_idx == 1:
cell.fill = fill
ws2.column_dimensions["A"].width = 36
ws2.column_dimensions["B"].width = 18
ws2.column_dimensions["C"].width = 10
# ── Sheet 3: 优先级排序 ──────────────────────────────
ws3 = wb.create_sheet("优先级排序")
ws3.merge_cells("A1:E1")
c = ws3["A1"]
c.value = "港乐词曲资产导入 — 补齐优先级排序"
c.font = title_font; c.fill = title_fill; c.alignment = title_align
for i, h in enumerate(["优先级", "处理方式", "涉及数量", "预期效果", "备注"], 1):
cell = ws3.cell(row=2, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
prio_rows = [
("P0", "恢复 25 首状态不一致的歌曲", "25 首", "立即恢复前端可见,零成本", "仅需修改 deleted 字段"),
("P1", "调整策略后自动重试 96 条缺歌手记录", "96 条", "主录音成功率 99.86% → 99.97%", "需开发评估空歌手容错策略"),
("P1", "重试 19 条转存失败记录", "19 条", "自动完成,无额外开发", "临时性故障,重跑即可"),
("P2", "人工审核 4,410 首疑似重复歌曲", "4,410 首", "释放暂存积压,可能新增歌曲或确认合并", "已部署审核看板"),
("P2", "确认恢复 71 首已删除但已有录音的歌曲", "71 首", "增加有效歌曲数", "逐条确认删除原因"),
("P2", "尝试爬取 3 条无歌词记录的歌词", "3 条", "数量极少,可忽略", "如确为纯音乐则标记跳过"),
("P3", "扩展爬虫平台覆盖后自动导入", "809 首", "需平台扩展开发", "依赖技术规划"),
("P3", "人工补录 1,245+1 首歌词/作者后重新导入", "1,246 首", "工作量大,建议先抽样评估价值", "优先检查是否有实际歌词"),
("P3", "等待外部条件后评估 133 首已删歌曲", "133 首", "视平台扩展情况", "当前无可用数据"),
]
for r_idx, (pri, action, cnt, effect, note) in enumerate(prio_rows, 3):
vals = [pri, action, cnt, effect, note]
for c_idx, v in enumerate(vals, 1):
cell = ws3.cell(row=r_idx, column=c_idx, value=v)
cell.font = data_font
cell.alignment = center_align if c_idx in (1, 3) else data_align
cell.border = thin_border
# 优先级上色
if c_idx == 1:
if "P0" in str(v):
cell.fill = PatternFill(start_color="FFFF6B6B", end_color="FFFF6B6B", fill_type="solid")
cell.font = Font(name="Microsoft YaHei", size=10, bold=True, color="FFFFFFFF")
elif "P1" in str(v):
cell.fill = PatternFill(start_color="FFFFD93D", end_color="FFFFD93D", fill_type="solid")
elif "P2" in str(v):
cell.fill = PatternFill(start_color="FF6BCB77", end_color="FF6BCB77", fill_type="solid")
cell.font = Font(name="Microsoft YaHei", size=10, color="FFFFFFFF")
elif "P3" in str(v):
cell.fill = PatternFill(start_color="FF4D96FF", end_color="FF4D96FF", fill_type="solid")
cell.font = Font(name="Microsoft YaHei", size=10, color="FFFFFFFF")
ws3.column_dimensions["A"].width = 8
ws3.column_dimensions["B"].width = 38
ws3.column_dimensions["C"].width = 12
ws3.column_dimensions["D"].width = 38
ws3.column_dimensions["E"].width = 30
# ── Sheet 4: 字段级缺口 ──────────────────────────────
ws4 = wb.create_sheet("字段级缺口")
ws4.merge_cells("A1:G1")
c = ws4["A1"]
c.value = "港乐词曲资产导入 — 字段级缺口(已入库但关键字段为空)"
c.font = title_font; c.fill = title_fill; c.alignment = title_align
for i, h in enumerate(["序号", "缺口类型", "为空数量", "所在表/范围", "平台分布", "补齐方式", "优先级"], 1):
cell = ws4.cell(row=2, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
field_rows = [
(1, "歌曲封面图片缺失", "1,960 首",
"hk_songs_test(86,870 首有效歌曲)", "全平台",
"🟢 爬虫可回补:重新触发封面下载/转存;如平台本身无封面则需人工提供",
"P1", FILL_GREEN),
(2, "曲作者字段为空", "3,232 首",
"hk_songs_test(86,870 首有效歌曲)", "全平台",
"🟢 爬虫可回补:从爬虫数据库 composer_name 批量回写;爬虫也无数据则需人工查找",
"P1", FILL_GREEN),
(3, "词作者字段为空", "1,488 首",
"hk_songs_test(86,870 首有效歌曲)", "全平台",
"🟢 爬虫可回补:从爬虫数据库 lyricist_name 批量回写;爬虫也无数据则需人工查找",
"P1", FILL_GREEN),
(4, "歌手信息为空(singers=[])", "393 条",
"爬虫库 songs 表(25,369 首已导入歌曲)",
"QQ: 373 / 酷狗: 5 / 网易云: 15",
"🟢 爬虫可回补:系统已内置 backfill-empty-singers 功能,从 spider DB 补录歌手关系",
"P1", FILL_GREEN),
(5, "歌手头像缺失", "4,057 人",
"爬虫库 singers 表(13,142 位歌手)",
"QQ: 3,039 / 酷狗: 549 / 网易云: 469",
"🟢 爬虫可回补:重新爬取歌手页面获取最新头像 URL 并转存 OSS",
"P1", FILL_GREEN),
(6, "歌词文本为空", "366 条",
"爬虫库 songs 表(25,369 首已导入歌曲)",
"QQ: 201 / 酷狗: 157 / 网易云: 8",
"🟢 爬虫可回补:重新爬取歌词页面;如平台确实无歌词则为纯音乐,无需补齐",
"P2", FILL_GREEN),
(7, "歌曲封面为空(爬虫侧)", "139 条",
"爬虫库 songs 表(25,369 首已导入歌曲)",
"QQ: 18 / 酷狗: 58 / 网易云: 63",
"🟢 爬虫可回补:重新触发封面下载/转存",
"P1", FILL_GREEN),
(8, "专辑封面为空", "82 个",
"爬虫库 albums 表(21,081 个专辑)",
"QQ: 0 / 酷狗: 21 / 网易云: 61",
"🟢 爬虫可回补:重新触发专辑封面下载/转存",
"P2", FILL_GREEN),
]
for r_idx, (no, gtype, count, scope, dist, method, priority, fill) in enumerate(field_rows, 3):
vals = [no, gtype, count, scope, dist, method, priority]
for c_idx, v in enumerate(vals, 1):
cell = ws4.cell(row=r_idx, column=c_idx, value=v)
cell.font = data_font
cell.alignment = center_align if c_idx in (1, 3, 7) else data_align
cell.border = thin_border
if c_idx == 6:
cell.fill = fill
# 汇总行
sum_row = 3 + len(field_rows)
ws4.merge_cells(f"A{sum_row}:B{sum_row}")
ws4.cell(row=sum_row, column=1, value="字段级缺口合计").font = Font(name="Microsoft YaHei", size=10, bold=True)
ws4.cell(row=sum_row, column=3, value="约 11,578").font = Font(name="Microsoft YaHei", size=10, bold=True)
ws4.cell(row=sum_row, column=6, value="绝大部分可通过爬虫回补").font = Font(name="Microsoft YaHei", size=10, bold=True)
for c_idx in range(1, 8):
ws4.cell(row=sum_row, column=c_idx).border = thin_border
ws4.column_dimensions["A"].width = 6
ws4.column_dimensions["B"].width = 26
ws4.column_dimensions["C"].width = 12
ws4.column_dimensions["D"].width = 32
ws4.column_dimensions["E"].width = 28
ws4.column_dimensions["F"].width = 52
ws4.column_dimensions["G"].width = 6
# ── 更新 Sheet 3 优先级排序:加入字段级缺口 ──────────────
# 在现有 prio_rows 后面插入字段级缺口的优先级行
field_prio_rows = [
("P1", "爬虫回补 1,960 首封面图片", "1,960 首", "前端展示效果显著改善", "重新触发封面下载/转存"),
("P1", "爬虫回补 4,057 位歌手头像", "4,057 人", "歌手页面展示完整", "重新爬取歌手页面"),
("P1", "爬虫回补词曲作者(3,232 + 1,488 首)", "4,720 首", "元数据完整性提升", "从爬虫库批量回写"),
("P1", "爬虫回补 393 条歌手信息", "393 条", "歌手关联完整性提升", "backfill-empty-singers"),
("P2", "爬虫回补 366 条歌词文本", "366 条", "歌词展示完整", "重新爬取歌词页面"),
("P2", "爬虫回补 139 条歌曲封面 + 82 个专辑封面", "221 条", "爬虫侧数据完整性提升", "重新触发转存"),
]
insert_row = 3 + len(prio_rows)
for i, (pri, action, cnt, effect, note) in enumerate(field_prio_rows):
r = insert_row + i
vals = [pri, action, cnt, effect, note]
for c_idx, v in enumerate(vals, 1):
cell = ws3.cell(row=r, column=c_idx, value=v)
cell.font = data_font
cell.alignment = center_align if c_idx in (1, 3) else data_align
cell.border = thin_border
if c_idx == 1:
if "P1" in str(v):
cell.fill = PatternFill(start_color="FFFFD93D", end_color="FFFFD93D", fill_type="solid")
elif "P2" in str(v):
cell.fill = PatternFill(start_color="FF6BCB77", end_color="FF6BCB77", fill_type="solid")
cell.font = Font(name="Microsoft YaHei", size=10, color="FFFFFFFF")
# ── 更新 Sheet 2 总览:加入字段级缺口分类 ──────────────
# 先取消已有的合并单元格,再插入新行
for merged in list(ws2.merged_cells.ranges):
ws2.unmerge_cells(str(merged))
# 在现有 summary 行后插入字段级缺口行
field_summary = [
("", "", "", None), # 空行分隔
("— 字段级缺口 —", "", "", None),
("🟢 爬虫可回补(封面/歌手/作者/头像/歌词)", "约 11,578 条", "—", FILL_GREEN),
(" 其中:歌曲封面图片", "1,960 首", "—", None),
(" 其中:词曲作者", "4,720 首", "—", None),
(" 其中:歌手信息", "393 条", "—", None),
(" 其中:歌手头像", "4,057 人", "—", None),
(" 其中:歌词文本", "366 条", "—", None),
(" 其中:歌曲/专辑封面(爬虫侧)", "221 条", "—", None),
]
insert_row2 = 3 + len(summary)
for i, (cat, cnt, pct, fill) in enumerate(field_summary):
r = insert_row2 + i
for c_idx, v in enumerate([cat, cnt, pct], 1):
cell = ws2.cell(row=r, column=c_idx, value=v)
cell.font = data_font
cell.alignment = center_align if c_idx >= 2 else Alignment(vertical="top", wrap_text=True)
cell.border = thin_border
if fill and c_idx == 1:
cell.fill = fill
if cat.startswith("—"):
cell.font = Font(name="Microsoft YaHei", size=10, bold=True)
# 重新设置说明区域
notes_start = insert_row2 + len(field_summary) + 1
ws2.merge_cells(f"A{notes_start}:C{notes_start}")
ws2.cell(row=notes_start, column=1, value="📋 处理方式说明:").font = Font(name="Microsoft YaHei", size=10, bold=True)
notes = [
"1. 🔴 人工处理:需业务/版权人员人工审核、恢复或补录源数据,系统仅辅助呈现和标记",
"2. 🟢 爬虫/自动:可通过爬虫补录歌手关系、重试转存失败、或重新触发导入流程自动完成",
"3. 🟡 扩展平台:当前爬虫仅覆盖 QQ/酷狗/网易云三平台,扩展覆盖范围后可自动导入",
"4. ⚪ 暂不处理:当前无法获取录音数据,等待平台扩展或外部数据源接入后再评估",
]
for i, note in enumerate(notes):
cell = ws2.cell(row=notes_start + 1 + i, column=1, value=note)
cell.font = Font(name="Microsoft YaHei", size=9, color="FF666666")
ws2.merge_cells(f"A{notes_start + 1 + i}:C{notes_start + 1 + i}")
# ── Sheet 5: 已同步数据(records1)─────────────────────
ws5 = wb.create_sheet("已同步数据(records1)")
ws5.merge_cells("A1:G1")
c = ws5["A1"]
c.value = "港乐词曲资产导入 — 已同步数据总览(records1 主录音)"
c.font = title_font; c.fill = title_fill; c.alignment = title_align
# ── 5.1 平台分布与导入状态 ──
r = 3
ws5.cell(row=r, column=1, value="一、各平台导入状态").font = Font(name="Microsoft YaHei", size=10.5, bold=True)
r = 4
for i, h in enumerate(["平台", "总量", "已成功", "失败", "成功率"], 1):
cell = ws5.cell(row=r, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
plat_rows = [
("QQ 音乐", "56,132", "56,068", "64", "99.89%"),
("酷狗", "14,716", "14,698", "18", "99.88%"),
("网易云", "15,338", "15,302", "36", "99.77%"),
("合计", "86,186", "86,068", "118", "99.86%"),
]
for i, (plat, total, ok, fail, rate) in enumerate(plat_rows, 5):
vals = [plat, total, ok, fail, rate]
for c_idx, v in enumerate(vals, 1):
cell = ws5.cell(row=i, column=c_idx, value=v)
cell.font = data_font if i < 8 else Font(name="Microsoft YaHei", size=10, bold=True)
cell.alignment = center_align
cell.border = thin_border
if i == 8:
cell.fill = PatternFill(start_color="FFD9E2F3", end_color="FFD9E2F3", fill_type="solid")
# ── 5.2 失败原因分布 ──
r = 10
ws5.cell(row=r, column=1, value="二、导入失败原因分布").font = Font(name="Microsoft YaHei", size=10.5, bold=True)
r = 11
for i, h in enumerate(["失败原因", "失败码", "QQ", "酷狗", "网易云", "合计", "占比"], 1):
cell = ws5.cell(row=r, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
fail_rows = [
("spider DB 缺歌手关系", "-5", "48", "13", "35", "96", "81.4%"),
("音频下载或 OSS 转存失败", "-6", "9", "1", "0", "10", "8.5%"),
("歌词 OSS 转存失败", "-8", "7", "1", "1", "9", "7.6%"),
("无可用歌词", "-2", "0", "3", "0", "3", "2.5%"),
("合计", "", "64", "18", "36", "118", "100%"),
]
for i, (reason, code, qq, kg, ne, total, pct) in enumerate(fail_rows, 12):
vals = [reason, code, qq, kg, ne, total, pct]
for c_idx, v in enumerate(vals, 1):
cell = ws5.cell(row=i, column=c_idx, value=v)
cell.font = data_font if i < 16 else Font(name="Microsoft YaHei", size=10, bold=True)
cell.alignment = center_align
cell.border = thin_border
if i == 16:
cell.fill = PatternFill(start_color="FFD9E2F3", end_color="FFD9E2F3", fill_type="solid")
# ── 5.3 已入库歌曲字段完整度 ──
r = 18
ws5.cell(row=r, column=1, value="三、已入库歌曲字段完整度(records1 严格范围,86,186 首)").font = Font(name="Microsoft YaHei", size=10.5, bold=True)
r = 19
for i, h in enumerate(["字段", "有值", "为空", "完整率", "补齐方式"], 1):
cell = ws5.cell(row=r, column=i, value=h)
cell.font = header_font; cell.fill = header_fill
cell.alignment = header_align; cell.border = thin_border
field_rows = [
("歌名(name)", "86,186", "0", "100%", "—"),
("音频链接(audio_url)", "86,186", "0", "100%", "—"),
("歌词链接(lyrics_url)", "86,183", "3", "100%", "🟢 可忽略"),
("歌手文本(singer)", "86,186", "0", "100%", "—"),
("封面图片(cover_url)", "84,427", "1,759", "98.0%", "🟢 爬虫回补"),
("曲作者(composer)", "84,702", "1,484", "98.3%", "🟢 从爬虫库批量回写"),
("词作者(lyricist)", "82,955", "3,231", "96.3%", "🟢 从爬虫库批量回写"),
("发行时间(issue_time)", "68,797", "17,389", "79.8%", "🟡 爬虫可尝试爬取"),
("BPM 分类(bpm_class)", "35,169", "51,017", "40.8%", "🟡 需 BPM 分析工具计算"),
]
for i, (field, has, empty, rate, method) in enumerate(field_rows, 20):
vals = [field, has, empty, rate, method]
for c_idx, v in enumerate(vals, 1):
cell = ws5.cell(row=i, column=c_idx, value=v)
cell.font = data_font
cell.alignment = center_align if c_idx >= 2 else data_align
cell.border = thin_border
if c_idx == 4 and rate != "—":
pct = float(rate.replace('%', ''))
if pct < 95:
cell.fill = FILL_YELLOW
elif pct < 99:
cell.fill = FILL_GREEN
ws5.column_dimensions["A"].width = 30
ws5.column_dimensions["B"].width = 12
ws5.column_dimensions["C"].width = 12
ws5.column_dimensions["D"].width = 12
ws5.column_dimensions["E"].width = 24
ws5.column_dimensions["F"].width = 10
ws5.column_dimensions["G"].width = 8
# 保存
out = "docs/港乐词曲资产数据缺口清单.xlsx"
wb.save(out)
print(f"✅ 已生成 {out}")