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

90 lines
5.6 KiB
Python

# -*- 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))