# -*- coding: utf-8 -*- # 국악방송(27) O 공공누리 전수확인. 마크=본문 라이선스 텍스트("공공누리 [제N유형] 출처표시[조건] 조건에 따라 이용"). # 페이지=본문 / 게시판=상세 표본 union. AI학습 문구 있으면 AI 추가. 사용: [--apply] / 인자없으면 DRY(+probe) import openpyxl, glob, os, re, sys, shutil, time, urllib.request, ssl, http.cookiejar from openpyxl.styles import PatternFill ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE cj=http.cookiejar.CookieJar(); op=urllib.request.build_opener(urllib.request.HTTPSHandler(context=ctx),urllib.request.HTTPCookieProcessor(cj)) op.addheaders=[('User-Agent','Mozilla/5.0')] BLUE=PatternFill(fill_type='solid',fgColor='FFBDD7EE'); APPLY='--apply' in sys.argv; PROBE='--probe' 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') def g(u): try: return op.open(fixdom(u),timeout=20).read().decode('utf-8','replace') except: return '' def types_of(html): """본문 공공누리 라이선스 문구→유형 set + AI. 유형/AI는 '공공누리' 앞·뒤 모두 올 수 있어 '조건에 따라 이용' 기준 앞 160자 윈도우로 파싱.""" txt=re.sub(r'<[^>]+>',' ',html); txt=re.sub(r'\s+',' ',txt) types=set(); ai=False for m in re.finditer(r'조건에\s*따라\s*이용', txt): seg=txt[max(0,m.start()-180):m.start()+10] if '공공누리' not in seg: continue # 실제 라이선스 선언만 num=re.search(r'제\s*([1-4])\s*유형', seg) if num: types.add(int(num.group(1))) else: commerce='상업적' in seg and '금지' in seg change='변경' in seg and '금지' in seg if '출처표시' in seg: if commerce and change: types.add(4) elif change: types.add(3) elif commerce: types.add(2) else: types.add(1) if '인공지능' in seg or re.search(r'AI\s*학습|학습용', seg): ai=True return types, ai def compose(types,ai): if not types and not ai: return '미부착' parts=['%d유형'%t for t in sorted(types)] if ai: parts.append('AI유형') return ','.join(parts) if parts else '미부착' 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] if PROBE: for nm,u in [('우리민요반주(2유형)','http://www.igbf.kr/gugak_web/?sub_num=849'),('국악교육자료','http://www.igbf.kr/gugak_web/?sub_num=770'),('편성표','http://www.igbf.kr/gugak_web/?sub_num=786'),('국악무대소개','http://www.igbf.kr/gugak_web/radio/tv_program_board.jsp?sub_id=3422&idx=RD2020004172&sub_num=787&state=view&bIdx=227577')]: t,ai=types_of(g(u)); print('%s -> %s (types=%s ai=%s)'%(nm,compose(t,ai),t,ai)) sys.exit(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 data=[r for r in range(3,ws.max_row+1) if ws.cell(r,2).value is not None] plan={} for r in data: u=str(ws.cell(r,11).value or ''); L=ws.cell(r,12).value if 'igbf.kr' not in u and 'http' in u: plan[r]=('미부착',); continue # 외부사이트 if not u or 'http' not in u: plan[r]=(str(ws.cell(r,15).value or '미부착'),); continue h=g(u); allt=set(); anyai=False t,ai=types_of(h); allt|=t; anyai=anyai or ai if L=='게시판': base=u.split('&state=')[0].split('#')[0] # 상세 파라미터: bIdx / recordidx / idx (?sub_num 게시판) details=[] for pn in ('bIdx','recordidx'): for v in list(dict.fromkeys(re.findall(pn+r'=(\d+)',h)))[:5]: details.append((pn,v)) for v in list(dict.fromkeys(re.findall(r'state=view[^\"\']*?[?&]idx=(\d+)',h)))[:5]: details.append(('idx',v)) for v in list(dict.fromkeys(re.findall(r'[?&]idx=(\d+)[^\"\']*?state=view',h)))[:5]: details.append(('idx',v)) seen=set() for pn,v in details: if (pn,v) in seen: continue seen.add((pn,v)) sep='&' if '?' in base else '?' vh=g(base+sep+'state=view&'+pn+'='+v) tt,aa=types_of(vh); allt|=tt; anyai=anyai or aa time.sleep(0.02) plan[r]=(compose(allt,anyai),) time.sleep(0.02) chg=[(r,str(ws.cell(r,15).value or ''),plan[r][0]) for r in data if plan[r][0]!=str(ws.cell(r,15).value or '')] print('O 변경 %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)) import json json.dump({str(r):plan[r][0] for r in data},open(os.path.join(DIR,'_gugak_O.json'),'w',encoding='utf-8'),ensure_ascii=False) 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 O변경 %d행'%len(chg)) else: print('ABORT(변경됨)') except PermissionError: print('LOCKED')