DB_JOB/작업파일/완료/공공기관/_tabapply.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

128 lines
6.6 KiB
Python

# -*- coding: utf-8 -*-
# 범용 본문탭 적용: 인페이지#탭→M=탭수 / 별도URL탭→중첩 자식행. 사용: _tabapply.py <no> [--apply]
import openpyxl, urllib.request, ssl, re, glob, os, sys, shutil, time, http.cookiejar
from copy import copy
from openpyxl.styles import PatternFill
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'),('Referer','https://www.google.com')]
def fetch(u): return op.open(u,timeout=20).read().decode('utf-8','ignore')
NO=int(sys.argv[1]); APPLY='--apply' in sys.argv
BLUE=PatternFill(fill_type='solid',fgColor='FFBDD7EE'); MAXC=27
BASE=os.path.dirname(os.path.abspath(__file__))
d=[x for x in glob.glob(os.path.join(BASE,'*')) if os.path.isdir(x) and os.path.basename(x).startswith(str(NO)+'.')][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
def norm(u):
if not u: return ''
u=str(u).split('#')[0]
m=re.search(r'https?://[^/]+(/[^?]*)',u); path=(m.group(1) if m else u).rstrip('/').lower()
qs=u.split('?',1)[1] if '?' in u else ''
params=[kv.lower() for kv in qs.split('&') if kv and '=' in kv and not kv.split('=',1)[0].lower().startswith(('wt.','utm'))]
return path+('?'+'&'.join(sorted(params)) if params else '')
def leafcol(r):
x=4
for c in (4,5,6,7):
if eff(r,c): x=c
return x
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))]
hosts=[m.group(1) for m in (re.match(r'(https?://[^/]+)',str(ws.cell(r,11).value)) for r in data if ws.cell(r,11).value and 'http' in str(ws.cell(r,11).value)) if m]
HOST=max(set(hosts),key=hosts.count)
sheet=set(norm(ws.cell(r,11).value) for r in data if ws.cell(r,11).value)
TABRE=re.compile(r'<ul[^>]*class="([^"]*tab[^"]*)"[^>]*>(.*?)</ul>', re.S)
def fullurl(pu,h):
if h.startswith('http'): return h
if h.startswith('/'): return HOST+h
return pu.rsplit('/',1)[0]+'/'+h
mset=[]; nests=[]; seen=set()
for r in data:
u=ws.cell(r,11).value
if not u or HOST.split('//')[1] not in str(u): continue
try: html=fetch(str(u))
except: continue
for m in TABRE.finditer(html):
raw=[(re.sub(r'<[^>]+>','',a.group(2)).strip(), a.group(1)) for a in re.finditer(r'<a[^>]*href="([^"]*)"[^>]*>(.*?)</a>', m.group(2), re.S)]
raw=[(t,h) for t,h in raw if t and h and 'javascript:void' not in h and not t.endswith(('바로가기','더보기'))]
if len(raw)<2: continue
key=(r,tuple(t for t,_ in raw))
if key in seen: continue
seen.add(key)
inpage=all(h.startswith('#') or h=='' for _,h in raw)
docsec=all(re.match(r'^\s*(\d+\s*[.\)]|제\s*\d+\s*[장조관항])', t) for t,_ in raw) # 정책문서 섹션탭
if inpage or docsec:
mset.append((r,len(raw)))
else:
tabs=[(t, fullurl(str(u),h)) for t,h in raw if not (h.startswith('#') or h=='')]
miss=[(t,h) for t,h in tabs if norm(h) not in sheet]
if miss: nests.append((r, leafcol(r), tabs, miss))
time.sleep(0.03)
print('site %d: 인페이지탭(M세팅) %d · 별도URL중첩 %d그룹'%(NO,len(mset),len(nests)))
for r,c in mset: print(' M r%d F=%s -> M=%d'%(r,ws.cell(r,6).value or eff(r,5),c))
for r,lc,tabs,miss in nests: print(' 중첩 r%d leaf%d F=%s +%d: %s'%(r,lc,ws.cell(r,6).value or eff(r,5),len(miss),' / '.join(t for t,_ in miss)[:50]))
if not APPLY:
print('DRY'); sys.exit(0)
# apply M
mmap={r:c for r,c in mset}
for r,c in mset:
ws.cell(r,13).value=c; ws.cell(r,13).fill=BLUE
if eff(r,12) not in ('게시판','사이트'): ws.cell(r,12).value='페이지'
# apply nest (rebuild)
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:
rec={'src':r,'D':eff(r,4),'E':eff(r,5),'F':ws.cell(r,6).value,'url':ws.cell(r,11).value,'mark':set()}
for c in range(7,MAXC+1): rec[c]=ws.cell(r,c).value
hl=ws.cell(r,11).hyperlink; rec['hl']=hl.target if hl else rec['url']; recs.append(rec)
nestmap={r:(lc,miss) for r,lc,tabs,miss in nests}
out=[]
for rec in recs:
out.append(rec)
if rec['src'] in nestmap:
lc,miss=nestmap[rec['src']]; child=min(lc+1,7)
for t,h in miss:
ch={'src':rec['src'],'D':rec['D'],'E':rec['E'],'mark':set(range(4,16))}
if child==7: ch['F']=rec['F']; ch[7]=t
elif child==6: ch['F']=t; ch[7]=None
else: ch['F']=None; ch[7]=None; ch[child]=t
for c in range(8,MAXC+1): ch[c]=ch.get(c)
ch['url']=h; ch['hl']=h; ch[12]='페이지'; ch[13]=1; ch[14]='어문'; ch[15]='미부착'
out.append(ch)
recs=out
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.get('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(7,[4,5,6]); rem(6,[4,5]); rem(5,[4]); rem(4,[])
if os.path.getmtime(xp)!=mt: print('ABORT changed'); 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))))