# -*- coding: utf-8 -*-
"""한국원자력환경공단 전용 L/M/N/O 채우기.
- 단일 세션·완만한 페이싱(차단 회피)
- body = div.doc-body
- 게시판: table.bbs + span.bbs-result('총 게시물 : N 건') / td 번호열 최댓값
- 미디어: doc-body 내 이미지. menuResources 인포그래픽/도식 = 어문(이미지 아님), 진짜 사진(data 업로드 jpg with 사진성)만 이미지.
- KOGL: img_open(type|code)0N 마크
출력: L/M/N/O (P/Q는 마크없으면 빈칸). S(비고)는 건드리지 않음(외부링크 유지).
변경 셀 표시: N 파란색.
"""
import sys,io,re,time,warnings
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')
warnings.filterwarnings('ignore')
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import openpyxl
from openpyxl.styles import PatternFill, Font
import urllib3;urllib3.disable_warnings()

P=r'D:\01.프로젝트\DB수집\작업파일\공공기관3\1.한국원자력환경공단\한국원자력환경공단.xlsx'
H={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
   'Accept':'text/html,application/xhtml+xml','Accept-Language':'ko-KR,ko;q=0.9'}
KOGL=re.compile(r'img_open(?:type|code)0*(\d{1,2})\.(?:png|jpe?g|gif)',re.I)
KOGL_LINK=re.compile(r'kogl\.or\.kr/info/licenseType(\d)',re.I)
YT=re.compile(r'(youtube\.com|youtu\.be|vimeo\.com)',re.I)
VIDEXT=re.compile(r'\.(mp4|webm|mov|m3u8)(\?|$)',re.I)
AUDEXT=re.compile(r'\.(mp3|wav|m4a|ogg)(\?|$|["\'&])',re.I)
NOISE=re.compile(r'(ico[_\-/]|/icon|logo|btn|bul[_\-]|bg[_\-/]|banner|sns|blank|spacer|loading|arrow|/dot|line[_\.]|top_|foot|header|common|btn_|_icon|symbol|copyright|qr_|movie_ico|no_img|noimage|share|facebook|insta|youtube_ic|blog|twitter|naver|kakao|navi\.png|nav_)',re.I)
PHOTO=re.compile(r'\.(jpe?g)(\?|$)',re.I)  # 사진 후보 확장자
TOTAL=re.compile(r'(?:총\s*게시물|총|전체)\s*[:\-]?\s*(\d[\d,]*)\s*건?',re.I)

H['Referer']='https://www.korad.or.kr/'
H['Connection']='close'
s=requests.Session();s.headers.update(H)
def fetch(u,t=12):
    for attempt in range(3):
        try:
            r=s.get(u,verify=False,timeout=(6,t))
            if r.status_code==200:
                return BeautifulSoup(r.content,'html.parser'),r.url
            if r.status_code in (403,429,503):
                time.sleep(1.0+attempt)
        except Exception:
            time.sleep(0.5+attempt*0.4)
    return None,None

def get_body(soup):
    for sel in ['div.doc-body','div.body-in','#content','#contents','.contents','.sub_content']:
        e=soup.select_one(sel)
        if e and len(e.get_text(strip=True))>20: return e
    return soup

def detect_form_m(soup,body):
    tb=soup.select_one('table.bbs')
    res=soup.select_one('span.bbs-result')
    gallery=soup.select_one('ul.bbs_gallery_pr')
    movie=soup.select_one('ul.movieList, div.movie, div.search-results')
    has_dt=bool(soup.select('div.dataTables_paginate, span.bbs-result, table.bbs tbody tr'))
    if tb or res or has_dt or gallery or movie:
        m=0; flag=''
        if res:
            mm=TOTAL.search(res.get_text(' ',strip=True))
            if mm: m=int(mm.group(1).replace(',',''))
        if not m and tb:
            nums=[]
            for tr in tb.select('tbody tr'):
                tds=tr.find_all('td')
                if tds:
                    t=tds[0].get_text(strip=True)
                    if t.isdigit(): nums.append(int(t))
            if nums: m=max(nums)
        if not m and (gallery or movie):
            flag='AJAX목록 M브라우저확인'
        kind='movie' if movie and not tb else ('gallery' if gallery and not tb else 'list')
        return '게시판',m,flag,kind
    return '페이지',1,'',''

def media(body,is_board=False):
    txt=len(body.get_text(strip=True))>20
    img=False
    for im in body.find_all('img'):
        src=im.get('src') or im.get('data-src') or ''
        if not src or KOGL.search(src) or NOISE.search(src): continue
        # menuResources 인포그래픽/도식 = 어문(이미지 아님). 진짜 사진만 이미지.
        # 사진성: jpg + 캡션/사이즈 큰 업로드. png/gif menuResources = 도식 → 제외
        if 'menuResources' in src and not PHOTO.search(src):
            continue
        img=True;break
    vid=bool(body.find_all('video')) or bool(YT.search(str(body))) or bool(VIDEXT.search(str(body)))
    aud=bool(body.find_all('audio')) or bool(AUDEXT.search(str(body)))
    return txt,img,vid,aud

def kogl(body):
    types=set();qy=False
    for a in body.find_all('a',href=True):
        m=KOGL_LINK.search(a['href'])
        if m: types.add(int(m.group(1)));qy=True
    for im in body.find_all('img'):
        m=KOGL.search(im.get('src','') or '')
        if m and 1<=int(m.group(1))<=4: types.add(int(m.group(1)))
    if types=={1,2,3,4}: return set(),None
    return types,('Y' if qy else 'N')

def nstr(t,i,v,a):
    p=[]
    if t:p.append('어문')
    if i:p.append('이미지')
    if v:p.append('영상')
    if a:p.append('오디오')
    return ','.join(p) if p else '없음'

def main():
    wb=openpyxl.load_workbook(P);ws=wb.active
    blue=PatternFill('solid',fgColor='FFBDD7EE',bgColor='FFBDD7EE')
    rows=[]
    for r in range(3,ws.max_row+1):
        b=ws.cell(r,2).value
        if b is None:continue
        rows.append((r,ws.cell(r,11).value or '',ws.cell(r,19).value))
    total=len(rows);done=0;t0=time.time()
    for r,k,s19 in rows:
        done+=1
        if s19=='외부링크':
            # 사이트: L=사이트, M~Q 빈칸
            ws.cell(r,12).value='사이트'
            for c in (13,14,15,16,17): ws.cell(r,c).value=None
            continue
        if 'korad.or.kr' not in k:
            ws.cell(r,12).value='사이트'
            for c in (13,14,15,16,17): ws.cell(r,c).value=None
            continue
        soup,final=fetch(k)
        if soup is None:
            ws.cell(r,19).value='접근실패-재시도필요'  # S=비고
            time.sleep(0.5);continue
        body=get_body(soup)
        form,m,flag,kind=detect_form_m(soup,body)
        ws.cell(r,12).value=form
        ws.cell(r,13).value=(m if form=='게시판' else 1)
        if flag: ws.cell(r,19).value=flag  # S=비고
        is_board=(form=='게시판')
        t,i,v,a=media(body,is_board)
        types,qy=kogl(body)
        if is_board:
            # 게시판 상세는 JS링크라 정적 추적 불가 → 목록 위젯으로 미디어 판정
            if kind=='gallery':
                i=True
            if kind=='movie':
                v=True; i=True  # 영상 게시판 썸네일+영상
            gal=soup.select_one('ul.bbs_gallery_pr, ul.gallery, .photo_list, .gallery_list')
            if gal:
                for im in gal.find_all('img'):
                    src=im.get('src') or ''
                    if src and not NOISE.search(src): i=True;break
            if m==0 and not flag:
                nv='없음'  # 빈 게시판
            else:
                nv=nstr(True,i,v,a)
        else:
            nv=nstr(t,i,v,a)
        nc=ws.cell(r,14)
        nc.value=nv; nc.fill=blue
        if not types:
            ws.cell(r,15).value='미부착'
        else:
            ws.cell(r,15).value=','.join(f'{n}유형' for n in sorted(types))
            ws.cell(r,16).value=('게시판' if is_board else '게시물')
            ws.cell(r,17).value=qy or 'N'
        time.sleep(0.1)
        if done%10==0:
            wb.save(P);print(f'  {done}/{total} ({int(time.time()-t0)}s)',flush=True)
    wb.save(P)
    print(f'DONE {done}/{total} ({int(time.time()-t0)}s)')

if __name__=='__main__':
    main()
