# -*- coding: utf-8 -*- """공주시 305행(장애인 단일행, 순번311, sub06_01_02_01.do)을 크롤링한 탭 트리(1단계10→잎30)로 분해. - 305행을 30행(305~334)으로 확장, 306~414행은 +29 시프트(335~443) - 순번(B) 305~443 연속 재번호(311..), 305 이전(검수본·삭제갭) 불변 - 병합셀 재조정(>=306행 +29) + 장애인 F/G 병합 신규 - K열 하이퍼링크 유지/재설정 사용: --write 로 적용(미지정 시 계획만 출력) """ import sys, shutil from copy import copy import openpyxl XLSX = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\충청남도\2.공주시\충청남도_공주시.xlsx' SPLIT_ROW = 305 # 장애인 행 N = 30 # 새 행 수 SHIFT = N - 1 # 29 DATA_LAST = 414 # 현재 마지막 데이터행 B_START = 311 # 305행 순번 BASE = 'https://www.gongju.go.kr' # (G, H, url, L, M) H=None 이면 단일(leaf) ROWS = [ ('장애인인권헌장', None, '/kr/sub06_01_02_01.do', '페이지', 1), ('장애인등록안내', '장애인이란', '/kr/sub06_01_02_03_01.do', '페이지', 1), ('장애인등록안내', '장애인등록/심사제도', '/kr/sub06_01_02_03_02.do', '페이지', 1), ('장애인등록안내', '장애인등록현황', '/kr/sub06_01_02_03_03.do', '페이지', 1), ('장애인등록안내', '장애인관련법률', '/kr/sub06_01_02_03_04.do', '페이지', 1), ('장애인복지카드안내', None, '/kr/sub06_01_02_04_01.do', '페이지', 1), ('장애인생활안정지원', '장애수당지급', '/kr/sub06_01_02_05_01.do', '페이지', 1), ('장애인생활안정지원', '장애인의료비지원', '/kr/sub06_01_02_05_02.do', '페이지', 1), ('장애인생활안정지원', '자립자금대여', '/kr/sub06_01_02_05_03.do', '페이지', 1), ('장애인생활안정지원', '재활보조기구 무료교부', '/kr/sub06_01_02_05_04.do', '페이지', 1), ('장애인생활안정지원', '장애아동수당', '/kr/sub06_01_02_05_05.do', '페이지', 1), ('장애인생활안정지원', '장애인일자리', '/kr/sub06_01_02_05_06.do', '페이지', 1), ('자동차관련시책', '장애인자동차표지 발급', '/kr/sub06_01_02_06_01.do', '페이지', 1), ('자동차관련시책', '고속도로통행료 할인', '/kr/sub06_01_02_06_02.do', '페이지', 1), ('자동차관련시책', '자동차특별소비세 면제', '/kr/sub06_01_02_06_03.do', '페이지', 1), ('세금감면시책', '소득세공제', '/kr/sub06_01_02_07_01.do', '페이지', 1), ('세금감면시책', '상속세공제', '/kr/sub06_01_02_07_02.do', '페이지', 1), ('각종요금할인', '전화요금할인', '/kr/sub06_01_02_08_01.do', '페이지', 1), ('각종요금할인', 'TV수신료면제', '/kr/sub06_01_02_08_02.do', '페이지', 1), ('각종요금할인', '이동통신 요금할인', '/kr/sub06_01_02_08_03.do', '페이지', 1), ('각종요금할인', '교통요금 할인', '/kr/sub06_01_02_08_04.do', '페이지', 1), ('각종요금할인', '공공시설 이용요금 감면', '/kr/sub06_01_02_08_05.do', '페이지', 1), ('각종요금할인', '장애인 전기요금 감면', '/kr/sub06_01_02_08_06.do', '페이지', 1), ('각종요금할인', '초고속인터넷 요금할인', '/kr/sub06_01_02_08_07.do', '페이지', 1), ('장애인전화상담', '장애인전화상담소', '/kr/sub06_01_02_09_01.do', '페이지', 1), ('장애인전화상담', '이용안내', '/kr/sub06_01_02_09_02.do', '페이지', 1), ('장애인 전동휠체어 급속충전기 설치장소', None, '/kr/sub06_01_02_10.do', '페이지', 1), ('한눈에 보는 장애인복지 서비스', '시설소개', '/kr/sub06_01_02_11_01.do', '페이지', 1), ('한눈에 보는 장애인복지 서비스', '사업홍보', 'https://www.gongju.go.kr/bbs/BBSMSTR_000000001671/list.do?mno=sub06_01_02_11_02', '게시판', 96), ('한눈에 보는 장애인복지 서비스', '채용정보', 'https://www.gongju.go.kr/bbs/BBSMSTR_000000001672/list.do?mno=sub06_01_02_11_03', '게시판', 11), ] assert len(ROWS) == N def full(u): return u if u.startswith('http') else BASE + u def main(): write = '--write' in sys.argv wb = openpyxl.load_workbook(XLSX) ws = wb.active if write: # 0) 모든 병합 먼저 해제(시프트 시 MergedCell 쓰기불가 방지) old_merges = [str(mr) for mr in list(ws.merged_cells.ranges)] for mr in old_merges: ws.unmerge_cells(mr) # 1) 시프트: source 414..306 -> dest +29 (아래에서 위로) for sr in range(DATA_LAST, SPLIT_ROW, -1): # 414..306 dr = sr + SHIFT for c in range(1, 28): src = ws.cell(sr, c) dst = ws.cell(dr, c) dst.value = src.value if src.has_style: dst._style = copy(src._style) # hyperlink 이동 if src.hyperlink is not None: dst.hyperlink = copy(src.hyperlink) dst.hyperlink.ref = dst.coordinate src.hyperlink = None else: dst.hyperlink = None # 2) 305~334 영역 클리어 + 스타일 템플릿(원래 305행) 적용 # 원래 305행 스타일은 시프트 안했으므로 그대로 305에 남아있음(템플릿) tmpl = {c: copy(ws.cell(SPLIT_ROW, c)._style) for c in range(1, 28) if ws.cell(SPLIT_ROW, c).has_style} for r in range(SPLIT_ROW, SPLIT_ROW + N): for c in range(1, 28): cell = ws.cell(r, c) cell.value = None cell.hyperlink = None if c in tmpl: cell._style = copy(tmpl[c]) # 3) 30행 데이터 기입 (G는 그룹 최상단에만) prev_g = None for i, (g, h, u, L, M) in enumerate(ROWS): r = SPLIT_ROW + i if g != prev_g: ws.cell(r, 7).value = g # G 소 (그룹 최상단) prev_g = g if h: ws.cell(r, 8).value = h # H 세부 fu = full(u) kc = ws.cell(r, 11) kc.value = fu kc.hyperlink = fu ws.cell(r, 12).value = L # L ws.cell(r, 13).value = M # M ws.cell(r, 14).value = '어문' # N ws.cell(r, 15).value = '미부착' # O # F=장애인 (병합 최상단) ws.cell(SPLIT_ROW, 6).value = '장애인' # 4) 순번(B) 305~443 연속 재번호 for r in range(SPLIT_ROW, DATA_LAST + SHIFT + 1): ws.cell(r, 2).value = B_START + (r - SPLIT_ROW) # 5) 병합셀 재조정 (0단계서 해제한 old_merges를 시프트해 재생성) from openpyxl.utils.cell import range_boundaries def shift(rr): return rr + SHIFT if rr >= SPLIT_ROW + 1 else rr for rng in old_merges: c1, r1, c2, r2 = range_boundaries(rng) nr1, nr2 = shift(r1), shift(r2) ws.merge_cells(start_row=nr1, start_column=c1, end_row=nr2, end_column=c2) # 신규 병합: F305:F334 ws.merge_cells(start_row=305, start_column=6, end_row=334, end_column=6) # G 병합(다중 H 그룹) gmerges = [(306, 309), (311, 316), (317, 319), (320, 321), (322, 328), (329, 330), (332, 334)] for a, b in gmerges: ws.merge_cells(start_row=a, start_column=7, end_row=b, end_column=7) bak = XLSX.replace('.xlsx', '_backup_장애인분해전.xlsx') shutil.copy(XLSX, bak) wb.save(XLSX) print('저장 완료. 백업:', bak) else: print('=== 계획(DRY) ===') print('305행(장애인) → 30행 확장, 306~414 → +29 시프트') print('순번 305~443 = 311..%d' % (B_START + (DATA_LAST + SHIFT - SPLIT_ROW))) print('F305:F334 병합, G병합 7개') for i, (g, h, u, L, M) in enumerate(ROWS): print(' r%d G=%s H=%s L=%s M=%s' % (SPLIT_ROW + i, g, h or '', L, M)) if __name__ == '__main__': main()