# -*- coding: utf-8 -*-
import openpyxl, glob, os, re, shutil, urllib.request, ssl, http.cookiejar, sys
from copy import copy
from openpyxl.styles import PatternFill
BLUE=PatternFill(fill_type='solid', fgColor='FFBDD7EE'); MAXC=27
APPLY='--apply' in sys.argv
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
cj=http.cookiejar.CookieJar(); op=urllib.request.build_opener(urllib.request.HTTPSHandler(context=ctx),urllib.request.HTTPCookieProcessor(cj))
op.addheaders=[('User-Agent','Mozilla/5.0')]
html=op.open('https://www.knuh.or.kr/guide/sitemap.asp',timeout=20).read().decode('cp949','ignore')
def cl(s): return re.sub(r'\s+',' ',re.sub(r'<[^>]+>','',s)).replace('&','&').strip()
def norm(u):
m=re.search(r'(/[^?]*\.asp)',str(u)); return (m.group(1).lower() if m else str(u).lower())
# build name->대분류 and urlpath->대분류
name2dae={}; path2dae={}
SKIP={'홈','로그인','회원가입','사이트맵','병원홈','TOP','스킵네비게이션','콘텐츠','주메뉴'}
toks=re.findall(r'
]*>(.*?)|]*>(.*?)', html, re.S)
D=None
for dt,dd in toks:
if dt:
t=cl(dt)
if t and t not in SKIP and '메뉴' not in t: D=t
elif dd and D:
for href,at in re.findall(r']*href=\"(/[^\"]*\.asp[^\"]*)\"[^>]*>(.*?)', dd, re.S):
nm=cl(at)
if nm and nm not in SKIP:
name2dae.setdefault(nm,D); path2dae.setdefault(norm(href),D)
print('사이트맵 매핑: %d 대분류, %d 중분류명'%(len(set(name2dae.values())), len(name2dae)))
# existing sheet
d=[x for x in glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)),'2.*')) if os.path.isdir(x)][0]
xp=[p for p in glob.glob(os.path.join(d,'*.xlsx')) if not os.path.basename(p).startswith(('_','~')) and 'conflict' not in os.path.basename(p)][0]
mt=os.path.getmtime(xp); wb=openpyxl.load_workbook(xp); ws=wb.active
def eff(r,c):
v=ws.cell(r,c).value
if v is not None: return v
for mr in ws.merged_cells.ranges:
if mr.min_col==c and mr.min_row<=r<=mr.max_row: return ws.cell(mr.min_row,c).value
return None
data=[r for r in range(3,ws.max_row+1) if any(ws.cell(r,c).value not in (None,'') for c in (2,4,5,6,7,11))]
# heuristic for unmapped 대분류
EXTRA={'개인정보보호방침':'정보공개','환자권리장전':'병원안내','이메일무단수집거부':'정보공개','ID/PW찾기':'고객서비스','회원정보수정':'고객서비스','회원탈퇴':'고객서비스','마이페이지':'고객서비스','설문조사':'고객서비스','FAQ':'고객서비스','건강상담 Q&A':'건강정보','동영상정보':'건강정보','KNUH 웹진':'건강정보','건강상식':'건강정보'}
matched=0
for r in data:
curD=eff(r,4)
dae=name2dae.get(curD) or path2dae.get(norm(ws.cell(r,11).value)) or EXTRA.get(curD)
if dae and dae!=curD: matched+=1
print('relabel 대상 %d/%d행'%(matched,len(data)))
if not APPLY:
# show coverage
from collections import Counter
un=Counter(eff(r,4) for r in data if not(name2dae.get(eff(r,4)) or path2dae.get(norm(ws.cell(r,11).value)) or EXTRA.get(eff(r,4))))
print('미매핑 대분류:', dict(un)); sys.exit(0)
# apply: D=대분류, E=curD(중), F=curE(소)
snap={(r,c):copy(ws.cell(r,c)._style) for r in data for c in range(1,MAXC+1)}
recs=[]
for r in data:
curD=eff(r,4); curE=eff(r,5); curF=ws.cell(r,6).value
dae=name2dae.get(curD) or path2dae.get(norm(ws.cell(r,11).value)) or EXTRA.get(curD)
rec={'src':r,'mark':set()}
if dae:
rec['D']=dae; rec['E']=curD; rec['F']=curE
rec['mark'].update([4,5,6])
rec[7]=curF if curF else ws.cell(r,7).value
else:
rec['D']=curD; rec['E']=curE; rec['F']=curF; rec[7]=ws.cell(r,7).value
for c in range(8,MAXC+1): rec[c]=ws.cell(r,c).value
rec['url']=ws.cell(r,11).value; hl=ws.cell(r,11).hyperlink; rec['hl']=hl.target if hl else rec['url']
recs.append(rec)
# reorder by 대분류 (sitemap order + extras)
order=['진료과/의료진','예약/조회','건강정보','병원안내','고객서비스','정보공개']
oi={x:i for i,x in enumerate(order)}
for i,rc in enumerate(recs): rc['oi']=i
recs.sort(key=lambda rc:(oi.get(rc['D'],50), rc['oi']))
for mr in list(ws.merged_cells.ranges):
if mr.min_row>=3: ws.unmerge_cells(str(mr))
n=len(recs)
for i,rc in enumerate(recs):
r=3+i; src=rc['src']
for c in range(1,MAXC+1): ws.cell(r,c)._style=copy(snap[(src,c)])
ws.cell(r,2).value=i+1; ws.cell(r,3).value=ws.cell(src,3).value
ws.cell(r,4).value=rc['D']; ws.cell(r,5).value=rc['E']; ws.cell(r,6).value=rc['F']
for c in range(7,MAXC+1): ws.cell(r,c).value=rc.get(c)
kc=ws.cell(r,11); kc.value=rc['url']; kc.hyperlink=rc['hl'] if rc['url'] else None
for c in rc['mark']: ws.cell(r,c).fill=BLUE
for r in range(3+n,ws.max_row+1):
for c in range(1,MAXC+1):
cell=ws.cell(r,c); cell.value=None; cell.hyperlink=None; cell.fill=PatternFill(fill_type=None)
def rem(col,sc):
r=3
while r<3+n:
v=ws.cell(r,col).value
if v is None or v=='': r+=1; continue
r2=r
while r2+1<3+n and ws.cell(r2+1,col).value==v and all(ws.cell(r2+1,s).value==ws.cell(r,s).value for s in sc): r2+=1
if r2>r:
for rr in range(r+1,r2+1): ws.cell(rr,col).value=None
ws.merge_cells(start_row=r,start_column=col,end_row=r2,end_column=col)
r=r2+1
rem(6,[4,5]); rem(5,[4]); rem(4,[])
if os.path.getmtime(xp)!=mt: print('ABORT'); sys.exit(1)
shutil.copy(xp, os.path.join(d,'_backup',os.path.basename(xp).replace('.xlsx','_backup_사이트맵계층화전.xlsx')))
wb.save(xp)
bs=[ws.cell(r,2).value for r in range(3,3+n)]
print('saved %d행 B연속%s 대분류:'%(n,bs==list(range(1,n+1))), list(dict.fromkeys(rc['D'] for rc in recs)))