# -*- coding: utf-8 -*- # 국립생태원(18) N 재판별(업뎃 스킬기준). 페이지=#content 사진검출 / 게시판=목록 사진썸네일·빈게시판없음. # 사진=이미지 / 조직도·인포그래픽(alt도식)=어문 / 게시판 사진썸네일1장↑=어문,이미지. 영상·오디오는 기존N 보존. # 사용: _nie_njudge.py [--apply] import openpyxl, glob, os, re, sys, json, shutil 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,'18.*')) 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|opentype|ccl_|kogl)',re.I) DIAG=re.compile(r'(조직도|체계도|순서도|흐름도|구성도|개념도|자세한\s*내용은|텍스트로\s*보기)') JS=r'''()=>{ let root=document.querySelector("#content")||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(); let vid=root.querySelectorAll("video,iframe[src*='youtube'],iframe[src*='youtu.be']").length; return {imgs:imgs, empty:/등록된\s*(게시물|자료|글)이?\s*없|게시물이\s*없|검색된\s*결과가\s*없|조회된\s*데이터가\s*없/.test(txt), vid:vid}; }''' def realphotos(imgs): return [im for im in imgs if im['w']>=140 and im['h']>=100 and not UIPAT.search(im['s']) and not DIAG.search(im['alt'] or '')] def base_extra(curN): # 기존 영상/오디오/글꼴/3D 보존태그 ex=[] for t in ['영상','오디오','글꼴','3D']: if t in str(curN): ex.append(t) return ex 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 compose(hasimg,curN,empty=False): if empty: return '없음' parts=['어문'] if hasimg: parts.append('이미지') parts+=base_extra(curN) # 어문→이미지→영상→오디오 순 order={'어문':0,'이미지':1,'영상':2,'오디오':3,'글꼴':4,'3D':5} parts=sorted(set(parts),key=lambda x:order.get(x,9)) return ','.join(parts) def judge(pg,row): u=row['K'] if 'nie.re.kr' not in u: return (row['N'],'비NIE',[]) try: pg.goto(u,timeout=30000,wait_until='domcontentloaded'); pg.wait_for_timeout(900); pg.mouse.wheel(0,2500); pg.wait_for_timeout(400) info=pg.evaluate(JS) except Exception as e: return ('?','ERR'+str(e)[:25],[]) if row['L']=='게시판': if row['M'] in (0,'0') or info['empty']: return ('없음','빈게시판',[]) photos=realphotos(info['imgs']) if photos: return (compose(True,row['N']),'목록썸네일%d'%len(photos),[p['s'] for p in photos[:3]]) # 목록 썸네일無 → 첫글 본문 확인(발행물·갤러리형 보정) try: html=pg.content() bids=re.findall(r'view\.do\?boardId=(\d+)',html) base=u.split('/list.do')[0]; menu=re.search(r'menuNo=(\d+)',u); menu=menu.group(1) if menu else '' for b in bids[:2]: pg.goto(base+'/view.do?boardId=%s&menuNo=%s'%(b,menu),timeout=25000,wait_until='domcontentloaded'); pg.wait_for_timeout(800); pg.mouse.wheel(0,2000); pg.wait_for_timeout(400) pinfo=pg.evaluate(JS); ph=realphotos(pinfo['imgs']) if ph: return (compose(True,row['N']),'첫글사진%d'%len(ph),[p['s'] for p in ph[:3]]) return (compose(False,row['N']),'첫글텍스트',[]) except Exception: return (compose(False,row['N']),'목록텍스트',[]) photos=realphotos(info['imgs']) n=compose(len(photos)>=1,row['N'],empty=False) why='사진%d'%len(photos)+(('·vid%d'%info['vid']) if info['vid'] else '') return (n,why,[p['s'] for p in photos[:3]]) 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}) br.close() chg=[x for x in results if x['pred'] not in ('?',x['N']) and x['pred']!='?'] print('대상 %d행, 변경 %d행'%(len(results),len(chg))) for x in chg: print(' r%d [%s] %s : %s -> %s (%s) %s'%(x['r'],x['L'],x['lab'],x['N'],x['pred'],x['why'],x['ev'][:2])) json.dump(results,open(os.path.join(BASE,'_nie_nresults.json'),'w',encoding='utf-8'),ensure_ascii=False,indent=0) if not APPLY: print('\nDRY.'); sys.exit(0) for x in results: if x['pred'] not in ('?',x['N']) and x['pred']!='?': 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 변경 %d행'%len(chg))