DB_JOB/작업파일/완료_1-2주차/1주차(5.19~5.25)/교육부(완료)/저작물유형수집.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

258 lines
10 KiB
Python

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):
"""
본문 영역(<div id="content">)을 분석하여 저작물 유형 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()