공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
5.6 KiB
Python
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))
|