공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
183 lines
7.3 KiB
Python
183 lines
7.3 KiB
Python
import os
|
|
import time
|
|
import re
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
import openpyxl
|
|
|
|
def check_media_types(soup, content_area):
|
|
"""
|
|
본문 영역을 분석하여 8종 저작물 유형을 다중 판별하는 함수
|
|
"""
|
|
if not content_area:
|
|
return "없음"
|
|
|
|
types = []
|
|
|
|
# 1. 어문
|
|
text_content = content_area.get_text(strip=True)
|
|
if text_content:
|
|
types.append("어문")
|
|
|
|
# 2. 이미지
|
|
images = content_area.find_all('img')
|
|
has_bg_img = 'background-image' in str(content_area).lower()
|
|
if images or has_bg_img:
|
|
types.append("이미지")
|
|
|
|
# 3. 영상
|
|
video_tags = content_area.find_all(['video', 'iframe', 'embed', 'object'])
|
|
html_str = str(content_area).lower()
|
|
video_ext = re.search(r'\.(mp4|avi|mkv|wmv|mov|flv)\b', html_str)
|
|
if video_tags or 'youtube.com' in html_str or 'youtu.be' in html_str or video_ext:
|
|
types.append("영상")
|
|
|
|
# 4. 오디오
|
|
audio_tags = content_area.find_all('audio')
|
|
audio_ext = re.search(r'\.(mp3|wav|ogg|wma|m4a)\b', html_str)
|
|
if audio_tags or audio_ext:
|
|
types.append("오디오")
|
|
|
|
# 5. 글꼴
|
|
style_tags = soup.find_all('style')
|
|
has_font_face = any('@font-face' in s.text for s in style_tags)
|
|
font_ext = re.search(r'\.(ttf|woff|woff2|otf|eot)\b', html_str)
|
|
if has_font_face or font_ext:
|
|
types.append("글꼴")
|
|
|
|
# 6. 3D
|
|
canvas_tags = content_area.find_all('canvas')
|
|
three_d_ext = re.search(r'\.(obj|gltf|glb|fbx|3ds)\b', html_str)
|
|
if 'webgl' in html_str or canvas_tags or three_d_ext:
|
|
types.append("3D")
|
|
|
|
# 7. 기타
|
|
if not types:
|
|
doc_ext = re.search(r'\.(zip|pdf|hwp|docx|xlsx|pptx)\b', html_str)
|
|
if doc_ext:
|
|
types.append("기타")
|
|
|
|
if not types:
|
|
return "없음"
|
|
|
|
return ", ".join(types)
|
|
|
|
def fetch_page_soup(url, headers):
|
|
try:
|
|
time.sleep(1.2) # 차단 예방 딜레이
|
|
response = requests.get(url, headers=headers, timeout=10)
|
|
if response.status_code == 200:
|
|
return BeautifulSoup(response.content, 'html.parser')
|
|
except Exception as e:
|
|
print(f" [접속 패스] {url} -> {e}")
|
|
return None
|
|
|
|
def main():
|
|
file_path = r"C:\Users\hehih\ownCloud\알바\고용노동부\고용노동부_사이트맵_완료.xlsx"
|
|
|
|
# ★ 기존 파일은 건드리지 않고 _ai를 붙여 다른 파일로 저장하기 위한 경로 조립
|
|
dir_name = os.path.dirname(file_path)
|
|
base_name = os.path.basename(file_path)
|
|
file_title, ext = os.path.splitext(base_name)
|
|
save_file_path = os.path.join(dir_name, f"{file_title}_ai{ext}")
|
|
|
|
if not os.path.exists(file_path):
|
|
print(f"[오류] 원본 파일 경로를 확인해주세요: {file_path}")
|
|
return
|
|
|
|
print("원본 엑셀 파일을 안전하게 읽어오는 중입니다...")
|
|
wb = openpyxl.load_workbook(file_path)
|
|
sheet = wb.active
|
|
|
|
base_url = "https://www.moel.go.kr"
|
|
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'
|
|
}
|
|
|
|
col_url = 11 # K열 (주소)
|
|
col_type = 12 # L열 (게시판형태)
|
|
col_license = 14 # N열 (저작물 유형 수집 목적지)
|
|
|
|
max_row = sheet.max_row
|
|
print(f"총 {max_row}행 분석을 시작합니다. (4행부터 출발)")
|
|
print(f"결과는 새 파일로 분리 저장됩니다:\n-> {save_file_path}\n")
|
|
|
|
for row in range(4, max_row + 1):
|
|
cell_url = sheet.cell(row=row, column=col_url).value
|
|
cell_type = sheet.cell(row=row, column=col_type).value
|
|
|
|
if not cell_type or str(cell_type).strip() not in ['게시판', '페이지']:
|
|
continue
|
|
|
|
if not cell_url:
|
|
continue
|
|
|
|
main_url = str(cell_url).strip()
|
|
if main_url.startswith('/'):
|
|
main_url = base_url + main_url
|
|
elif not main_url.startswith('http'):
|
|
main_url = base_url + '/' + main_url
|
|
|
|
detected_types_list = []
|
|
|
|
# 게시판형 분석 (상위 5개 링크 스캔)
|
|
if str(cell_type).strip() == '게시판':
|
|
print(f"[{row}/{max_row}] 게시판 스캔 시작: {main_url}")
|
|
list_soup = fetch_page_soup(main_url, headers)
|
|
|
|
if list_soup:
|
|
board_list_div = list_soup.find('div', class_='board_list')
|
|
if board_list_div:
|
|
links = board_list_div.select('strong.b_tit a')
|
|
target_links = links[:5] # 최대 5개 제한 규칙
|
|
|
|
for a_tag in target_links:
|
|
href = a_tag.get('href', '').strip()
|
|
if not href:
|
|
continue
|
|
detail_url = href if href.startswith('http') else base_url + href
|
|
|
|
detail_soup = fetch_page_soup(detail_url, headers)
|
|
if detail_soup:
|
|
content_area = detail_soup.find('div', id='contents_body')
|
|
res_type = check_media_types(detail_soup, content_area if content_area else detail_soup)
|
|
if res_type != "없음":
|
|
detected_types_list.extend(res_type.split(", "))
|
|
|
|
# 페이지형 분석 (다이렉트 스캔)
|
|
elif str(cell_type).strip() == '페이지':
|
|
print(f"[{row}/{max_row}] 페이지형 스캔 시작: {main_url}")
|
|
page_soup = fetch_page_soup(main_url, headers)
|
|
if page_soup:
|
|
content_area = page_soup.find('div', id='contents')
|
|
res_type = check_media_types(page_soup, content_area if content_area else page_soup)
|
|
if res_type != "없음":
|
|
detected_types_list.extend(res_type.split(", "))
|
|
|
|
# 중복 제거 후 최종 N열 기록
|
|
if detected_types_list:
|
|
final_types = ", ".join(sorted(list(set(detected_types_list))))
|
|
else:
|
|
final_types = "없음"
|
|
|
|
sheet.cell(row=row, column=col_license).value = final_types
|
|
print(f" => [N열 기록 완료]: {final_types}")
|
|
|
|
# 5행마다 안전하게 새 파일 경로로 백업 저장 (원본 보호)
|
|
if row % 5 == 0:
|
|
try:
|
|
wb.save(save_file_path)
|
|
except PermissionError:
|
|
print(f"[경고] {os.path.basename(save_file_path)} 파일이 열려있어 중간 저장에 실패했습니다. 엑셀을 닫아주세요.")
|
|
|
|
try:
|
|
wb.save(save_file_path)
|
|
print("\n==================================================")
|
|
print("🎉 8종 저작물 유형 자동 분류 및 기입 완료!")
|
|
print(f"기존 파일은 완벽히 보존되었으며, 아래 파일로 새 저장되었습니다:\n-> {save_file_path}")
|
|
print("==================================================")
|
|
except PermissionError:
|
|
print(f"\n[오류] 최종 저장 실패! {os.path.basename(save_file_path)} 파일 창이 열려 있다면 닫고 다시 실행해 주세요.")
|
|
|
|
if __name__ == "__main__":
|
|
main() |