공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
3.7 KiB
Python
81 lines
3.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""URL 패턴으로 각 사이트의 CMS 지문 산출 → 검수완료(정답) vs 전개대상 커버리지 매핑. (시트만 읽음, 무fetch)"""
|
|
import openpyxl, os, re, sys, glob
|
|
from collections import Counter
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
ROOT = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵'
|
|
EXCLUDE = {'서천군'}
|
|
INSPECTED = {'계룡시','공주시','금산군','논산시','보령시','당진시','부여군',
|
|
'고창군','김제시','남원시','군산시','무주군'}
|
|
|
|
def pick_xlsx(folder):
|
|
cand=[f for f in glob.glob(os.path.join(folder,'*.xlsx'))
|
|
if 'backup' not in os.path.basename(f).lower()
|
|
and not os.path.basename(f).startswith(('_','~$'))]
|
|
cand.sort(key=lambda f:(0 if re.match(r'^[가-힣]+도?_',os.path.basename(f)) else 1,len(f)))
|
|
return cand[0] if cand else None
|
|
|
|
def url_sig(u):
|
|
"""URL을 CMS 패턴 토큰으로 환원."""
|
|
u=u.lower()
|
|
toks=[]
|
|
if 'selectboardlist' in u or 'bbsmstr' in u: toks.append('bbs:BBSMSTR')
|
|
elif '/cop/bbs/' in u: toks.append('bbs:cop')
|
|
elif 'selectbbsnttlist' in u or 'bbsno=' in u: toks.append('bbs:nttList')
|
|
elif '/bbs/' in u and 'list.do' in u: toks.append('bbs:bbsList')
|
|
if 'menucd=' in u: toks.append('pg:menuCd')
|
|
elif 'contents.do?key=' in u or re.search(r'/sub\.do\?.*key=',u): toks.append('pg:key')
|
|
elif re.search(r'/kor/sub[\d_]+\.do',u) or re.search(r'/[a-z]{2,4}/sub[\d_]+\.do',u): toks.append('pg:subNN.do')
|
|
elif 'prog/' in u and 'list.do' in u: toks.append('pg:prog.list')
|
|
elif re.search(r'/\w+\.do\b',u): toks.append('pg:.do')
|
|
elif '.jsp' in u: toks.append('pg:.jsp')
|
|
elif '.php' in u: toks.append('pg:.php')
|
|
return toks
|
|
|
|
def fingerprint(xlsx):
|
|
wb=openpyxl.load_workbook(xlsx); ws=wb.active
|
|
cnt=Counter(); host=Counter()
|
|
for r in range(3,ws.max_row+1):
|
|
c=ws.cell(r,11)
|
|
u=c.hyperlink.target if c.hyperlink else (c.value if isinstance(c.value,str) and c.value.startswith('http') else None)
|
|
if not u: continue
|
|
m=re.match(r'https?://([^/]+)',u)
|
|
if m: host[re.sub(r'^www\.','',m.group(1))]+=1
|
|
for t in url_sig(u): cnt[t]+=1
|
|
dom=host.most_common(1)[0][0] if host else '?'
|
|
# 시그니처 = 상위 토큰 집합
|
|
sig=tuple(sorted(t for t,n in cnt.items() if n>=3))
|
|
return dom, sig, cnt
|
|
|
|
sites=[]
|
|
for prov in sorted(os.listdir(ROOT)):
|
|
pdir=os.path.join(ROOT,prov)
|
|
if not os.path.isdir(pdir) or prov.startswith('__'): continue
|
|
for d in sorted(os.listdir(pdir)):
|
|
sdir=os.path.join(pdir,d)
|
|
if not os.path.isdir(sdir): continue
|
|
nm=re.sub(r'^\d+\.','',d)
|
|
if nm in EXCLUDE: continue
|
|
xl=pick_xlsx(sdir)
|
|
if not xl: continue
|
|
try: dom,sig,cnt=fingerprint(xl)
|
|
except Exception as e: sig=('ERR',); dom=str(e)[:30]; cnt={}
|
|
sites.append({'prov':prov[:2],'name':nm,'dom':dom,'sig':sig,
|
|
'inspected':nm in INSPECTED})
|
|
|
|
# group by sig
|
|
from collections import defaultdict
|
|
groups=defaultdict(list)
|
|
for s in sites: groups[s['sig']].append(s)
|
|
print('=== CMS 시그니처별 그룹 (★=검수완료 선생님 있음) ===')
|
|
for sig,ss in sorted(groups.items(),key=lambda x:-len(x[1])):
|
|
has_teacher=any(s['inspected'] for s in ss)
|
|
teach=[s['name'] for s in ss if s['inspected']]
|
|
targets=[f"{s['name']}({s['prov']})" for s in ss if not s['inspected']]
|
|
star='★' if has_teacher else '✗미지'
|
|
print(f"\n[{star}] sig={'+'.join(sig) if sig else '(무)'} ({len(ss)}곳)")
|
|
if teach: print(f" 선생님(검수완료): {', '.join(teach)}")
|
|
if targets: print(f" 전개대상: {', '.join(targets)}")
|
|
if not has_teacher and targets:
|
|
print(f" ⚠ 정답 선생님 없음 → 미지CMS(별도조사 필요)")
|