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

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')