DB_JOB/_스크립트/_buyeo_scate.py
hehihoho3 df16c98366 백업: DB수집 전체 스냅샷 (공공기관2 정리 전)
공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:15:40 +09:00

167 lines
6.5 KiB
Python

# -*- coding: utf-8 -*-
"""부여군 사전정보공개(정보목록공개) fiexd_tab1 scate=1~9 분야탭 전개.
- _tab_expand 의 일반 필터탭 제외 규칙(같은 경로·쿼리만 다름)에 걸려 누락된 케이스.
사용자 지시로 9개 분야탭을 각각 별도 게시판 행으로 전개(보령시 사전정보공표 컨벤션).
- 각 분야 = 게시판 / M=목록 항목수(테이블 데이터행) / N=어문 / O=미부착.
- 평탄화→치환→재구성(D/E/F 재병합·K하이퍼링크·B순번)은 _tab_expand 재사용.
"""
import sys, shutil, warnings, re
from copy import copy
import requests, openpyxl
from bs4 import BeautifulSoup
from openpyxl.styles import Alignment, Font
from openpyxl.utils import get_column_letter
sys.path.insert(0, r'D:\01.프로젝트\DB수집\_스크립트')
from _tab_expand import load_flat, MAXCOL, CAT_COLS, HEADER_MERGES
warnings.filterwarnings('ignore')
H = {'User-Agent': 'Mozilla/5.0'}
XLSX = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\충청남도\7.부여군\충청남도_부여군.xlsx'
BASE = 'https://www.buyeo.go.kr/_prog/service/index.php?scate='
def collect():
"""9개 scate 라벨+항목수 수집."""
r = requests.get(BASE + '1', headers=H, timeout=15, verify=False)
soup = BeautifulSoup(r.content, 'html.parser')
labels = {}
for li in soup.select('.fiexd_tab1 li'):
a = li.find('a'); m = re.search(r'scate=(\d+)', a.get('href') or '')
if m:
labels[int(m.group(1))] = a.get_text(strip=True)
out = []
for sc in sorted(labels):
rr = requests.get(BASE + str(sc), headers=H, timeout=15, verify=False)
s = BeautifulSoup(rr.content, 'html.parser')
t = s.find('table')
cnt = sum(1 for tr in t.find_all('tr') if tr.find_all('td'))
out.append((sc, labels[sc], cnt))
return out
def main():
write = '--write' in sys.argv
scates = collect()
print('=== scate 분야탭 ===')
for sc, lab, cnt in scates:
print(f' scate={sc} | 항목={cnt} | {lab}')
print(f'합계 항목 {sum(c for _,_,c in scates)} / 9행 전개\n')
wb = openpyxl.load_workbook(XLSX)
ws = wb.active
rows = load_flat(ws)
# 대상 행 식별: K가 scate=1
tgt = None
for row in rows:
k = row['vals'].get(11)
if isinstance(k, str) and 'service/index.php?scate=1' in k:
tgt = row; break
if tgt is None:
print('!! 대상(scate=1) 행 없음'); return
lc = 6 # leaf=F (사전정보공개)
childc = lc + 1 # G
print(f'대상 sheet행 {tgt["src"]} (F={tgt["vals"].get(6)}) → 자식 {get_column_letter(childc)} 에 9분야 전개')
if not write:
print('\n(계획만. --write 로 기입)')
return
backup = XLSX.replace('.xlsx', '_backup_scate전.xlsx')
shutil.copy(XLSX, backup)
print('백업:', backup)
out_rows = []
for row in rows:
if row is tgt:
for sc, lab, cnt in scates:
nv = dict(tgt['vals'])
for c in CAT_COLS:
if c > lc:
nv[c] = None
nv[childc] = lab
nv[11] = BASE + str(sc)
nv[12] = '게시판'
nv[13] = cnt
nv[14] = '어문'
nv[15] = '미부착'
for c in (16, 17, 18): # P,Q,R 비움
nv[c] = None
# 신규 행(scate>1)은 비고~ 비움, scate=1은 기존 보존
if sc != 1:
for c in range(19, MAXCOL + 1):
nv[c] = None
out_rows.append({'vals': nv, 'style': tgt, 'url': nv[11]})
else:
out_rows.append({'vals': dict(row['vals']), 'style': row, 'url': row['vals'].get(11)})
# 데이터 클리어
for r in range(3, ws.max_row + 1):
for c in range(1, MAXCOL + 1):
ws.cell(r, c).value = None
ws.cell(r, c).hyperlink = None
START = 3
for i, orow in enumerate(out_rows):
r = START + i
sty = orow['style']['styles']
for c in range(1, MAXCOL + 1):
cell = ws.cell(r, c)
f, fl, bd, al, nf, pr = sty[c]
cell.font = copy(f); cell.fill = copy(fl); cell.border = copy(bd)
cell.alignment = copy(al); cell.number_format = nf; cell.protection = copy(pr)
v = orow['vals']
for c in range(1, MAXCOL + 1):
ws.cell(r, c).value = v.get(c)
ws.cell(r, 2).value = i + 1
END = START + len(out_rows) - 1
center = Alignment(horizontal='center', vertical='center', wrap_text=True)
left = Alignment(horizontal='left', vertical='center', wrap_text=False)
def merge_runs(col_letter, col_idx, group_cols=()):
cur_val = ws.cell(START, col_idx).value
cur_grp = tuple(ws.cell(START, g).value for g in group_cols)
run_start = START; runs = []
for r in range(START + 1, END + 1):
val = ws.cell(r, col_idx).value
grp = tuple(ws.cell(r, gg).value for gg in group_cols)
if val == cur_val and grp == cur_grp:
continue
if cur_val not in (None, '') and r - 1 > run_start:
runs.append((run_start, r - 1))
cur_val, cur_grp, run_start = val, grp, r
if cur_val not in (None, '') and END > run_start:
runs.append((run_start, END))
for s, e in runs:
ws.merge_cells(f'{col_letter}{s}:{col_letter}{e}')
ws.cell(s, col_idx).alignment = center
merge_runs('F', 6, group_cols=(4, 5))
merge_runs('E', 5, group_cols=(4,))
merge_runs('D', 4)
for r in range(START, END + 1):
for c in (4, 5, 6, 7):
if ws.cell(r, c).value is not None:
ws.cell(r, c).alignment = center
for r in range(1, END + 1):
ws.row_dimensions[r].height = 15
for r in range(START, END + 1):
cell = ws.cell(r, 11)
u = cell.value
if isinstance(u, str) and u.startswith(('http://', 'https://')):
cell.hyperlink = u
old = cell.font
cell.font = Font(name=old.name or '맑은 고딕', size=old.size or 11,
bold=old.bold, italic=old.italic, color='0000FF', underline='single')
cell.alignment = left
wb.save(XLSX)
print(f'저장 완료: {XLSX} (총 {len(out_rows)}행)')
if __name__ == '__main__':
main()