import re import time import openpyxl import requests from bs4 import BeautifulSoup # ========================================== # [설정 항목] 파일 경로 및 기본 URL 설정 # ========================================== INPUT_FILE_PATH = r"C:\Users\hehih\ownCloud\알바\교육부\교육부_사이트맵_완료.xlsx" # 원본 엑셀 파일 경로 OUTPUT_FILE_PATH = r"C:\Users\hehih\ownCloud\알바\교육부\교육부_사이트맵_완료_ai.xlsx" # 결과가 저장될 엑셀 파일 경로 BASE_URL = "https://www.moe.go.kr" # 요청 헤더 설정 (보안 방화벽 우회용 일반 User-Agent) HEADERS = { "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" } def parse_javascript_go_view(href_text): """ [이미지 반영 고도화] javascript:goView('72752','104296','0','','W','','','') 형태에서 각 매개변수를 추출하여 실제 시스템 함수와 똑같은 URL 규칙으로 조립합니다. """ # 싱글 쿼테이션 안의 인자값들을 순서대로 추출 pattern = r"goView\s*\(\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*'([^']*)'\s*\)" match = re.search(pattern, href_text) if match: # 이미지에 정의된 변수 순서대로 매핑 reqBoardID = match.group(1) # '72752' reqBoardSeq = match.group(2) # '104296' reqLev = match.group(3) # '0' reqSecYN = match.group(4) # '' reqStatusYN = match.group(5) # 'W' reqCurrPage = match.group(6) # '' reqWriterYN = match.group(7) # '' reqDept = match.group(8) # '' # 기본 예시 주소에 있던 메뉴값(m=010609) 및 사이트구분(s=moe) 고정 반영 # 이미지의 변수 결합 순서 완벽 재현 detail_url = ( f"{BASE_URL}/boardCnts/viewRenew.do?" f"boardID={reqBoardID}" f"&boardSeq={reqBoardSeq}" f"&lev={reqLev}" f"&searchType=null" f"&statusYN={reqStatusYN}" f"&page={reqCurrPage}" f"&s=moe&m=010609" # $("#menuVal").val() 대응 고정값 f"&opType=N" ) return detail_url # 혹시 따옴표가 없거나 형태가 살짝 다른 변칙적 케이스 방어용 서브 패턴 fallback_pattern = r"goView\s*\(\s*'([^']*)'\s*,\s*'([^']*)'" fb_match = re.search(fallback_pattern, href_text) if fb_match: return f"{BASE_URL}/boardCnts/viewRenew.do?boardID={fb_match.group(1)}&boardSeq={fb_match.group(2)}&lev=0&searchType=null&statusYN=W&page=&s=moe&m=010609&opType=N" return None def analyze_content_area(soup): """ 본문 영역(
)을 분석하여 저작물 유형 8종을 판별합니다. """ content_div = soup.find("div", id="content") if not content_div: return "없음" # 공공누리 마크 영역 제거 (오판 방지) for skip_div in content_div.find_all("div", class_="codeView01"): skip_div.decompose() pure_text = content_div.get_text(strip=True) html_source = str(content_div) types = [] # 1. 어문 판별 if pure_text: types.append("어문") # 2. 이미지 판별 has_img = False if content_div.find("img"): has_img = True elif "background-image" in html_source.lower(): has_img = True if has_img: types.append("이미지") # 3. 영상 판별 video_tags = content_div.find_all(["video", "iframe", "embed", "object"]) video_extensions = [".mp4", ".avi", ".mkv", ".wmv", ".mov", ".flv"] has_video = len(video_tags) > 0 or "youtube.com" in html_source or "youtu.be" in html_source if not has_video: for ext in video_extensions: if ext in html_source.lower(): has_video = True break if has_video: types.append("영상") # 4. 오디오 판별 audio_tags = content_div.find_all("audio") audio_extensions = [".mp3", ".wav", ".ogg", ".wma", ".m4a"] has_audio = len(audio_tags) > 0 if not has_audio: for ext in audio_extensions: if ext in html_source.lower(): has_audio = True break if has_audio: types.append("오디오") # 5. 글꼴 판별 has_font = "@font-face" in html_source.lower() font_extensions = [".ttf", ".woff", ".woff2", ".otf", ".eot"] if not has_font: for ext in font_extensions: if ext in html_source.lower(): has_font = True break if has_font: types.append("글꼴") # 6. 3D 판별 canvas_tags = content_div.find_all("canvas") three_d_extensions = [".obj", ".gltf", ".glb", ".fbx", ".3ds"] has_3d = "webgl" in html_source.lower() or len(canvas_tags) > 0 if not has_3d: for ext in three_d_extensions: if ext in html_source.lower(): has_3d = True break if has_3d: types.append("3D") # 7. 기타 판별 if not types: doc_extensions = [".zip", ".pdf", ".hwp", ".docx", ".xlsx", ".pptx"] has_doc = False for ext in doc_extensions: if ext in html_source.lower(): has_doc = True break if has_doc: types.append("기타") # 8. 없음 판별 if not types: return "없음" return ", ".join(types) def process_audit_sheet(): print("🚀 [시스템 연동 버전] 교육부 저작물 유형 수집 프로그램을 시작합니다.") # 엑셀 데이터 로드 (서식 완전 보존) wb = openpyxl.load_workbook(INPUT_FILE_PATH, data_only=False) sheet = wb.active max_row = sheet.max_row for row_idx in range(4, max_row + 1): k_val = sheet.cell(row=row_idx, column=11).value # K열 (주소) l_val = sheet.cell(row=row_idx, column=12).value # L열 (게시판형태) if not l_val or str(l_val).strip() not in ["게시판", "페이지"]: continue url = str(k_val).strip() if k_val else "" if not url.startswith("http"): continue l_type = str(l_val).strip() print(f"\n[행 {row_idx}] 형태: {l_type} | 주소: {url} 처리 중...") row_results = [] try: time.sleep(1) # 방화벽 우회 안전 딜레이 # --- L열이 '게시판' 일 때 --- if l_type == "게시판": response = requests.get(url, headers=HEADERS, timeout=10) if response.status_code != 200: print(f"❌ 목록 페이지 접속 실패 (상태코드: {response.status_code})") sheet.cell(row=row_idx, column=14).value = "목록 접속 실패" continue soup = BeautifulSoup(response.text, "html.parser") td_elements = soup.find_all("td", class_=lambda x: x and "title" in x and "left" in x) detail_links = [] for td in td_elements: a_tag = td.find("a") if a_tag and a_tag.get("href"): href = a_tag.get("href") detail_url = parse_javascript_go_view(href) if detail_url: detail_links.append(detail_url) # 상위 최대 5개 제한 규칙 target_links = detail_links[:5] print(f"👉 발견된 상세 링크 수: {len(detail_links)}개 -> 상위 {len(target_links)}개 엔진 스캔") for d_url in target_links: print(f" └ 🔍 정밀 조립 주소 분석: {d_url}") time.sleep(1) d_res = requests.get(d_url, headers=HEADERS, timeout=10) if d_res.status_code == 200: d_soup = BeautifulSoup(d_res.text, "html.parser") res_type = analyze_content_area(d_soup) row_results.append(res_type) else: print(f" └ ❌ 상세 페이지 접속 실패 ({d_url})") # --- L열이 '페이지' 일 때 --- elif l_type == "페이지": print(f" └ 🔍 실시간 페이지 주소 스캔: {url}") response = requests.get(url, headers=HEADERS, timeout=10) if response.status_code == 200: soup = BeautifulSoup(response.text, "html.parser") res_type = analyze_content_area(soup) row_results.append(res_type) else: print(f" └ ❌ 페이지 접속 실패 (상태코드: {response.status_code})") sheet.cell(row=row_idx, column=14).value = "페이지 접속 실패" continue # --- 결과 기입 (서식 유지 및 N열 타겟팅) --- if row_results: final_types = set() for r in row_results: if r != "없음": for item in r.split(", "): final_types.add(item) result_str = ",".join(sorted(list(final_types))) if final_types else "없음" sheet.cell(row=row_idx, column=14).value = result_str print(f"🎯 [행 {row_idx}] 최종 결과 저장 완료: {result_str}") else: sheet.cell(row=row_idx, column=14).value = "분석 불가" except Exception as e: print(f"❌ [행 {row_idx}] 크리티컬 에러: {e}") sheet.cell(row=row_idx, column=14).value = "에러 발생" # 파일 최종 저장 wb.save(OUTPUT_FILE_PATH) print(f"\n🎉 모든 분석이 성공적으로 끝났습니다!\n결과 경로: {OUTPUT_FILE_PATH}") if __name__ == "__main__": process_audit_sheet()