# -*- coding: utf-8 -*- # 국립해양생물자원관(21) N 재판정. 사용자기준: 사진=어문,이미지 / 조직도·인포그래픽(alt도식)=어문 / 빈게시판=없음. # 학습 3~47 정확도 검증 + 48~끝 판정. 사용: _mabik_njudge.py [--apply] import openpyxl, glob, os, re, sys, json, shutil, time from playwright.sync_api import sync_playwright from openpyxl.styles import PatternFill BLUE=PatternFill(fill_type='solid',fgColor='FFBDD7EE') APPLY='--apply' in sys.argv BASE=os.path.dirname(os.path.abspath(__file__)) d=[x for x in glob.glob(os.path.join(BASE,'21.*')) 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) and 'backup' not in p][0] SHOT=os.path.join(d,'_nshots'); os.makedirs(SHOT,exist_ok=True) mt=os.path.getmtime(xp); wb=openpyxl.load_workbook(xp); ws=wb.active UIPAT=re.compile(r'(logo|footer|header|banner|btn|icon|bg[_-]|[_-]bg|sns|family|bottom|top[_-]|arrow|blank|no[_-]?img|common|marker|sprite|loading|quick)',re.I) DIAG=re.compile(r'(조직도|체계도|순서도|흐름도|구성도|개념도|자세한\s*내용은|텍스트로\s*보기|아래\s*표\s*참고)') # 조직도/인포그래픽=어문(사진·평면도·지도는 이미지로 유지) # real-photo detector JS (scope #contens) JS_PAGE=r'''()=>{ let root=document.querySelector("#contens")||document.querySelector("#txt")||document.querySelector("#contents")||document.body; let imgs=[...root.querySelectorAll("img")].map(i=>({w:i.clientWidth,h:i.clientHeight,s:(i.currentSrc||i.src||"").split("/").pop(),alt:i.alt||""})); let txt=(root.innerText||"").replace(/\s+/g," ").trim(); return {imgs:imgs, txtlen:txt.length, empty:/등록된\s*(게시물|자료)|게시물이\s*없|검색된\s*결과가\s*없/.test(txt)}; }''' def realphotos(imgs): out=[] for im in imgs: if im['w']<140 or im['h']<100: continue if UIPAT.search(im['s']): continue if DIAG.search(im['alt'] or ''): continue # 접근성 도식=어문 취급 out.append(im) return out rows=[] for r in range(3,ws.max_row+1): if ws.cell(r,2).value is None: continue L=ws.cell(r,12).value if L not in ('페이지','게시판'): continue rows.append({'r':r,'L':L,'M':ws.cell(r,13).value,'N':str(ws.cell(r,14).value or ''),'K':str(ws.cell(r,11).value or ''), 'lab':ws.cell(r,7).value or ws.cell(r,6).value or ws.cell(r,5).value or ''}) def judge(pg,row): u=row['K'] try: pg.goto(u,timeout=30000,wait_until='domcontentloaded'); pg.wait_for_timeout(1000); pg.mouse.wheel(0,2500); pg.wait_for_timeout(500) except Exception as e: return ('?','ERR '+str(e)[:30],[]) info=pg.evaluate(JS_PAGE) if row['L']=='페이지': photos=realphotos(info['imgs']) return ('어문,이미지' if photos else '어문', 'img%d'%len(photos), [p['s'] for p in photos[:4]]) # 게시판: 빈게시판=없음 / 목록에 콘텐츠 사진썸네일(갤러리)=어문,이미지 / 그외 텍스트목록=어문 # (48~끝엔 교육프로그램 포스터보드 없음→list썸네일 신뢰가능) if row['M'] in (0,'0') or info['empty']: return ('없음','빈게시판',[]) photos=realphotos(info['imgs']) if len(photos)>=2: return ('어문,이미지','목록썸네일%d'%len(photos),[p['s'] for p in photos[:4]]) return ('어문','텍스트목록',[]) results=[] with sync_playwright() as pw: br=pw.chromium.launch(); pg=br.new_page(viewport={'width':1200,'height':1600}) for row in rows: pred,why,ev=judge(pg,row) results.append({**row,'pred':pred,'why':why,'ev':ev}) # 몽타주: 48~끝만 저장(검증용) if row['r']>=48: try: el=pg.query_selector('#contens') or pg.query_selector('#txt') (el or pg).screenshot(path=os.path.join(SHOT,'r%d_%s.png'%(row['r'],pred.replace(',','_')))) except Exception: pass br.close() # 학습 정확도(3~47) learn=[x for x in results if x['r']<48] ok=sum(1 for x in learn if x['pred']==x['N']) print('학습 3~47 정확도 %d/%d'%(ok,len(learn))) for x in learn: if x['pred']!=x['N']: print(' XX r%d %s N=%s pred=%s (%s) %s'%(x['r'],x['lab'],x['N'],x['pred'],x['why'],x['ev'][:2])) # 48~끝 변경 tgt=[x for x in results if x['r']>=48] chg=[x for x in tgt if x['pred']!=x['N']] print('48~끝 대상 %d행, 변경 %d행'%(len(tgt),len(chg))) for x in chg: print(' r%d %s : %s -> %s (%s) %s'%(x['r'],x['lab'],x['N'],x['pred'],x['why'],x['ev'][:2])) json.dump(results,open(os.path.join(BASE,'_mabik_nresults.json'),'w',encoding='utf-8'),ensure_ascii=False,indent=0) if not APPLY: print('\nDRY. --apply 로 48~끝 N 반영(몽타주 _nshots\\).'); sys.exit(0) for x in tgt: if x['pred']!='?' and x['pred']!=x['N']: ws.cell(x['r'],14).value=x['pred']; ws.cell(x['r'],14).fill=BLUE 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_N재판정전.xlsx'))) wb.save(xp); print('saved. 48~끝 N 반영 %d행 변경'%len(chg))