공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
175 lines
7.7 KiB
Python
175 lines
7.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""수집완료(✅) 기관 일괄 본문탭(1-5b) 확장 + 신규행 Phase2~4 수집 오케스트레이터.
|
|
|
|
매뉴얼 1-5b 의 _tab_expand.py / _tab_phase234.py 를 기관 표대로 순차 호출한다.
|
|
- 검수완료(계룡시)·미처리(보은군)·이미 탭확장(공주시)는 제외.
|
|
- base/domain 은 각 엑셀 K열 URL에서 자동 도출(가장 흔한 host 기준).
|
|
- 영동군만 가중 SSL(--weak-ssl).
|
|
|
|
모드:
|
|
python -X utf8 _tab_batch.py probe # 설정·행수·base/domain 검증만
|
|
python -X utf8 _tab_batch.py dry # 전 기관 확장계획(읽기전용)만 산출
|
|
python -X utf8 _tab_batch.py run [기관...] # 확장(--write)+Phase234 실제 수행
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
import subprocess
|
|
from collections import Counter
|
|
from urllib.parse import urlsplit
|
|
|
|
import openpyxl
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
ROOT = os.path.dirname(HERE)
|
|
MAP = os.path.join(ROOT, '작업파일', '광역_사이트맵')
|
|
|
|
CN_ALL = os.path.join(HERE, '_chungnam_phase234_all.py')
|
|
CB_ALL = os.path.join(HERE, '_chungbuk_phase234_all.py')
|
|
JB_ALL = os.path.join(HERE, '_jeonbuk_phase234_all.py')
|
|
GEUMSAN = os.path.join(MAP, '충청남도', '3.금산군', '_phase234.py')
|
|
JB_SEL = '#main-contents,#content,#contents,.contents,#txt,main,#container,#sub'
|
|
|
|
# (광역, idx, 기관, 폴더명, phase234모듈, body_sel(콤마/None), weak_ssl)
|
|
SITES = [
|
|
# 충청남도 (계룡=검수완료 제외, 공주=이미 탭확장 제외)
|
|
('충청남도', 3, '금산군', GEUMSAN, None, False),
|
|
('충청남도', 4, '논산시', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 5, '당진시', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 6, '보령시', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 7, '부여군', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 8, '서산시', CN_ALL, '#contents,#txt,main', False),
|
|
('충청남도', 9, '서천군', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 10, '아산시', CN_ALL, '#contents,main,#txt', False),
|
|
('충청남도', 11, '예산군', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 12, '천안시', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 13, '청양군', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 14, '태안군', CN_ALL, '#txt,#contents,main', False),
|
|
('충청남도', 15, '홍성군', CN_ALL, '#txt,#contents,main', False),
|
|
# 충청북도 (보은=미처리 제외)
|
|
('충청북도', 1, '괴산군', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 2, '단양군', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 4, '영동군', CB_ALL, '#txt,#contents,main', True),
|
|
('충청북도', 5, '옥천군', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 6, '음성군', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 7, '제천시', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 8, '증평군', CB_ALL, '#txt,#contents,main', False),
|
|
('충청북도', 9, '진천군', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 10, '청주시', CB_ALL, '#contents,#txt,main', False),
|
|
('충청북도', 11, '충주시', CB_ALL, '#contents,#txt,main', False),
|
|
# 전북특별자치도
|
|
('전북특별자치도', 1, '고창군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 2, '군산시', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 3, '김제시', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 4, '남원시', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 5, '무주군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 6, '부안군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 7, '순창군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 8, '완주군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 9, '익산시', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 10, '임실군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 11, '장수군', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 12, '전주시', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 13, '정읍시', JB_ALL, JB_SEL, False),
|
|
('전북특별자치도', 14, '진안군', JB_ALL, JB_SEL, False),
|
|
# 제주특별자치도
|
|
('제주특별자치도', 1, '서귀포시', JB_ALL, JB_SEL, False),
|
|
('제주특별자치도', 2, '제주시', JB_ALL, JB_SEL, False),
|
|
]
|
|
|
|
|
|
def xlsx_path(prov, idx, name):
|
|
return os.path.join(MAP, prov, f'{idx}.{name}', f'{prov}_{name}.xlsx')
|
|
|
|
|
|
def derive_base_domain(xlsx):
|
|
"""K열 URL에서 가장 흔한 host → base(scheme://host), domain(끝 3라벨)."""
|
|
wb = openpyxl.load_workbook(xlsx, read_only=True)
|
|
ws = wb.active
|
|
hosts = Counter()
|
|
scheme_by_host = {}
|
|
nrow = 0
|
|
for r in ws.iter_rows(min_row=3, min_col=11, max_col=11, values_only=True):
|
|
u = r[0]
|
|
if isinstance(u, str) and u.startswith('http'):
|
|
nrow += 1
|
|
sp = urlsplit(u)
|
|
if sp.hostname:
|
|
hosts[sp.hostname] += 1
|
|
scheme_by_host.setdefault(sp.hostname, sp.scheme)
|
|
# 데이터 행수(K 무관)
|
|
maxrow = ws.max_row
|
|
wb.close()
|
|
if not hosts:
|
|
return None, None, maxrow
|
|
host = hosts.most_common(1)[0][0]
|
|
base = f'{scheme_by_host[host]}://{host}'
|
|
labels = host.split('.')
|
|
domain = '.'.join(labels[-3:]) if len(labels) >= 3 else host
|
|
return base, domain, maxrow
|
|
|
|
|
|
def run(cmd):
|
|
print(' $', ' '.join(os.path.basename(c) if c.endswith('.py') else c for c in cmd[2:]))
|
|
p = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace')
|
|
out = (p.stdout or '') + (p.stderr or '')
|
|
return out
|
|
|
|
|
|
def main():
|
|
mode = sys.argv[1] if len(sys.argv) > 1 else 'probe'
|
|
only = set(sys.argv[2:]) if len(sys.argv) > 2 else None
|
|
py = [sys.executable, '-X', 'utf8']
|
|
|
|
sites = [s for s in SITES if not only or s[2] in only]
|
|
print(f'대상 기관: {len(sites)}개 (모드={mode})\n')
|
|
|
|
for prov, idx, name, mod, sel, weak in sites:
|
|
xp = xlsx_path(prov, idx, name)
|
|
if not os.path.exists(xp):
|
|
print(f'❌ {prov} {name}: 엑셀 없음 → {xp}\n')
|
|
continue
|
|
base, domain, maxrow = derive_base_domain(xp)
|
|
tag = ' [weak-ssl]' if weak else ''
|
|
print(f'━━━ {prov} {name}{tag} (행~{maxrow}, base={base}, domain={domain})')
|
|
|
|
if mode == 'probe':
|
|
print(f' 모듈={os.path.basename(mod)} body_sel={sel}\n')
|
|
continue
|
|
|
|
# 1) 항상 dry 로 먼저 확장계획 산출(읽기전용)
|
|
ex = py + [os.path.join(HERE, '_tab_expand.py'), xp, base, domain]
|
|
if weak:
|
|
ex += ['--weak-ssl']
|
|
out = run(ex)
|
|
m = re.search(r'신규 추가 행:\s*(\d+)개', out)
|
|
newcnt = int(m.group(1)) if m else 0
|
|
grp = re.search(r'탭 확장 계획:\s*(\d+)개', out)
|
|
print(f' → 확장그룹 {grp.group(1) if grp else "?"}개 / 신규행 {newcnt}개')
|
|
if mode == 'dry':
|
|
print()
|
|
continue
|
|
|
|
# 2) run 모드 & 신규행 있을 때만 --write 후 Phase234
|
|
if newcnt > 0:
|
|
exw = ex + ['--write']
|
|
run(exw)
|
|
ph = py + [os.path.join(HERE, '_tab_phase234.py'), xp, mod, domain]
|
|
if sel:
|
|
ph += [sel]
|
|
if weak:
|
|
ph += ['--weak-ssl']
|
|
out2 = run(ph)
|
|
for line in out2.splitlines():
|
|
if any(k in line for k in ('대상 신규행', 'L 분포', 'O 분포', '접근실패', '저장', '대체 저장')):
|
|
print(' ' + line)
|
|
else:
|
|
print(' (신규행 없음 → Phase234 생략)')
|
|
print()
|
|
|
|
print('\n=== 배치 종료 ===')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|