DB_JOB/작업파일/_스크립트/_cms_fingerprint.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

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(별도조사 필요)")