# -*- coding: utf-8 -*- # 국악방송(27) O 가시성 재검증: 화면에 실제 노출된(offsetParent!=null) 공공누리 라이선스 텍스트만 인정. # 현재 O!=미부착 행만 재판정(숨김텍스트→미부착). 사용: [--apply] import openpyxl, glob, os, re, sys, 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 def fixdom(u): return str(u).replace('https://new.igbf.kr','http://www.igbf.kr').replace('http://new.igbf.kr','http://www.igbf.kr') DIR=os.path.dirname(os.path.abspath(__file__)) d=[x for x in glob.glob(os.path.join(DIR,'27.*')) 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] 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 # 화면 보이는 공공누리 라이선스 텍스트 수집 JS JS=r'''()=>{ let out=[]; let walk=document.querySelectorAll('body *'); for(const e of walk){ if(e.offsetParent===null && e.tagName!=='BODY') continue; // 숨김 제외 let own=[...e.childNodes].filter(n=>n.nodeType===3).map(n=>n.textContent).join(' '); if(/공공누리/.test(own) && /조건에\s*따라\s*이용|이용\s*가능/.test(own)) out.push(own.replace(/\s+/g,' ').trim()); } return out; }''' def types_from_visible(texts): types=set(); ai=False for t in texts: num=re.search(r'제\s*([1-4])\s*유형', t) if num: types.add(int(num.group(1))) else: commerce='상업적' in t and '금지' in t change='변경' in t and '금지' in t if '출처표시' in t: if commerce and change: types.add(4) elif change: types.add(3) elif commerce: types.add(2) else: types.add(1) if '인공지능' in t or re.search(r'AI\s*학습|학습용',t): ai=True return types, ai def compose(types,ai): parts=['%d유형'%t for t in sorted(types)] if ai: parts.append('AI유형') return ','.join(parts) if parts else '미부착' targets=[r for r in range(3,ws.max_row+1) if ws.cell(r,2).value is not None and str(ws.cell(r,15).value or '') not in ('미부착','None','')] print('재검증 대상(현재 부착) %d행'%len(targets)) plan={} with sync_playwright() as pw: br=pw.chromium.launch(); pg=br.new_page(viewport={'width':1280,'height':2200}) def visit(u): try: pg.goto(fixdom(u),timeout=25000,wait_until='domcontentloaded'); pg.wait_for_timeout(900); pg.mouse.wheel(0,3000); pg.wait_for_timeout(400) return pg.evaluate(JS) except Exception: return [] for r in targets: u=str(ws.cell(r,11).value or ''); L=ws.cell(r,12).value texts=visit(u) if L=='게시판': h=pg.content(); base=u.split('&state=')[0].split('#')[0] ids=[] for pn in ('bIdx','recordidx'): ids+= [(pn,v) for v in list(dict.fromkeys(re.findall(pn+r'=(\d+)',h)))[:4]] ids+=[('idx',v) for v in list(dict.fromkeys(re.findall(r'state=view[^\"\']*?idx=(\d+)|[?&]idx=(\d+)[^\"\']*?state=view',h)))[:0]] for v in list(dict.fromkeys(re.findall(r'[?&]idx=(\d+)',h)))[:4]: ids.append(('idx',v)) seen=set() for pn,v in ids: if (pn,v) in seen: continue seen.add((pn,v)); sep='&' if '?' in base else '?' texts+=visit(base+sep+'state=view&'+pn+'='+v) t,ai=types_from_visible(texts); plan[r]=compose(t,ai) time.sleep(0.02) br.close() chg=[(r,str(ws.cell(r,15).value or ''),plan[r]) for r in targets if plan[r]!=str(ws.cell(r,15).value or '')] print('정정 %d행:'%len(chg)) for r,cur,new in chg: print(' r%d [%s>%s>%s] %s -> %s'%(r,eff(r,4) or '',eff(r,5) or '',ws.cell(r,6).value or '',cur,new)) if not APPLY: print('DRY'); sys.exit(0) mt=os.path.getmtime(xp); wb=openpyxl.load_workbook(xp); ws=wb.active for r,cur,new in chg: if ws.cell(r,2).value is not None: ws.cell(r,15).value=new; ws.cell(r,15).fill=BLUE try: if os.path.getmtime(xp)==mt: shutil.copy(xp, os.path.join(d,'_backup',os.path.basename(xp).replace('.xlsx','_backup_O가시성정정전.xlsx'))); wb.save(xp); print('saved %d행'%len(chg)) else: print('ABORT(변경됨)') except PermissionError: print('LOCKED')