공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
127 lines
5.3 KiB
Python
127 lines
5.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""중분류/소분류 '랜딩행' 제거 — 매뉴얼 1-5 확장(③ K동일 조건 제거).
|
|
|
|
_dedup_all.py 는 부모행 URL == 첫 자식 URL 일 때만 부모행을 삭제한다(③).
|
|
eGov '누리집지도'(고창·임실·정읍·진안 등)는 중분류 랜딩(예 …002000000 '민원안내')과
|
|
첫 소분류(…002007000)의 URL이 달라 ③에 안 걸리고 랜딩행이 남아 있다.
|
|
사용자 요청(2026-05-31): ③조건을 빼고, 자식을 거느린 부모 랜딩행을 전부 삭제.
|
|
· 부모행의 가장 깊은 카테고리 컬럼 lc, lc+1은 빈칸(부모는 그 깊이의 잎이 아님)
|
|
· 직후 자식행이 D~lc 값 동일 & lc+1 채워짐 → 부모행 삭제(자식이 병합으로 라벨 승계)
|
|
URL 무관. 카테고리 텍스트(D/E/F)는 재병합으로 보존.
|
|
|
|
평탄화/재병합/순번/하이퍼링크는 _dedup_all 재사용. 전 컬럼(B~AA, L~Q) 보존.
|
|
|
|
사용:
|
|
python -X utf8 _collapse_landing.py dry <기관명...|경로>
|
|
python -X utf8 _collapse_landing.py run <기관명...|경로>
|
|
--maxlc N : lc<=N 깊이까지만 삭제(기본 9=I, 즉 D~I 랜딩 모두). E만 원하면 --maxlc 5.
|
|
"""
|
|
import os, sys, shutil, importlib.util, warnings
|
|
import openpyxl
|
|
warnings.filterwarnings('ignore')
|
|
|
|
spec = importlib.util.spec_from_file_location('dd', __file__.replace('_collapse_landing.py', '_dedup_all.py'))
|
|
dd = importlib.util.module_from_spec(spec); spec.loader.exec_module(dd)
|
|
|
|
CAT_COLS = dd.CAT_COLS # D E F G H I J = 4..10
|
|
EXCLUDE = {'계룡시'} # 검수완료
|
|
|
|
|
|
def is_safe_shell(v):
|
|
"""순수 메뉴 셸인가: L=페이지(또는 빈칸) & M<=1 & 공공누리 미부착."""
|
|
L = v.get(12); M = v.get(13); O = v.get(15)
|
|
if isinstance(O, str) and '유형' in O:
|
|
return False
|
|
if L not in (None, '', '페이지'):
|
|
return False
|
|
if isinstance(M, (int, float)) and M and M > 1:
|
|
return False
|
|
return True
|
|
|
|
|
|
def collapse(rows, maxlc=9, safe=False):
|
|
"""랜딩행 제거. safe=True면 순수 셸(데이터 무보유)만. 반환 (남은행, 제거목록)."""
|
|
out, removed = [], []
|
|
i = 0
|
|
while i < len(rows):
|
|
v = rows[i]['vals']
|
|
if i + 1 < len(rows):
|
|
nv = rows[i + 1]['vals']
|
|
lc = dd.leaf_depth(v) # 부모의 가장 깊은 카테고리 컬럼
|
|
if 4 <= lc <= maxlc:
|
|
same_upper = all((v.get(c) or '') == (nv.get(c) or '')
|
|
for c in CAT_COLS if c <= lc)
|
|
child_next = nv.get(lc + 1) not in (None, '')
|
|
parent_is_landing = v.get(lc + 1) in (None, '')
|
|
if safe and not is_safe_shell(v):
|
|
same_upper = False # 데이터 보유 랜딩은 보존
|
|
if same_upper and child_next and parent_is_landing:
|
|
removed.append({'src': rows[i]['src'], 'lc': lc,
|
|
'label': v.get(lc), 'child': nv.get(lc + 1),
|
|
'url': v.get(11), 'curl': nv.get(11)})
|
|
i += 1
|
|
continue
|
|
out.append(rows[i]); i += 1
|
|
return out, removed
|
|
|
|
|
|
LV = {4: 'D', 5: 'E', 6: 'F', 7: 'G', 8: 'H', 9: 'I', 10: 'J'}
|
|
|
|
|
|
def resolve(args):
|
|
paths = []
|
|
for a in args:
|
|
if a.lower().endswith('.xlsx') or os.path.sep in a:
|
|
paths.append((os.path.splitext(os.path.basename(a))[0], a))
|
|
names = [a for a in args if not (a.lower().endswith('.xlsx') or os.path.sep in a)]
|
|
for prov, idx, name in dd.SITES:
|
|
if names and name in names:
|
|
paths.append((name, dd.xpath(prov, idx, name)))
|
|
return paths
|
|
|
|
|
|
def main():
|
|
mode = sys.argv[1] if len(sys.argv) > 1 else 'dry'
|
|
rest = [a for a in sys.argv[2:] if not a.startswith('--')]
|
|
maxlc = 9
|
|
if '--maxlc' in sys.argv:
|
|
maxlc = int(sys.argv[sys.argv.index('--maxlc') + 1])
|
|
safe = '--safe' in sys.argv
|
|
targets = resolve(rest)
|
|
if not targets:
|
|
print('대상 없음.'); return
|
|
grand = 0
|
|
for name, xp in targets:
|
|
if name in EXCLUDE:
|
|
print(f'{name}: 검수완료 제외'); continue
|
|
if not os.path.exists(xp):
|
|
print(f'{name}: 엑셀 없음'); continue
|
|
wb = openpyxl.load_workbook(xp); ws = wb.active
|
|
rows = dd.load_flat(ws)
|
|
out, removed = collapse(rows, maxlc, safe)
|
|
grand += len(removed)
|
|
bylv = {}
|
|
for r in removed:
|
|
bylv[r['lc']] = bylv.get(r['lc'], 0) + 1
|
|
lvstr = ' '.join(f'{LV[k]}:{v}' for k, v in sorted(bylv.items()))
|
|
print(f'\n=== {name} === {len(rows)}→{len(out)} (제거 {len(removed)}) [{lvstr}]')
|
|
for r in removed[:6]:
|
|
print(f" [{LV[r['lc']]}] {r['label']} ({r['url']}) → 자식 첫행 {r['child']} ({r['curl']})")
|
|
if len(removed) > 6:
|
|
print(f' ... 외 {len(removed)-6}건')
|
|
if mode == 'run' and removed:
|
|
bak = xp.replace('.xlsx', '_backup_랜딩제거전.xlsx')
|
|
shutil.copy(xp, bak)
|
|
dd.write_back(ws, out)
|
|
try:
|
|
wb.save(xp)
|
|
except PermissionError:
|
|
wb.save(xp.replace('.xlsx', '_LP.xlsx')); print(' !! 잠김→_LP')
|
|
else:
|
|
print(f' 저장 완료. 백업: {os.path.basename(bak)}')
|
|
print(f'\n총 제거 대상: {grand}행 ({mode}, maxlc={maxlc})')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|