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