DB_JOB/작업파일/완료_1-2주차/1주차(5.19~5.25)/전라남도(완료)/test.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

230 lines
9.9 KiB
Python

import os
import re
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
import openpyxl
# ==========================================
# [설정 항목] 환경에 맞게 파일명을 수정하세요
# ==========================================
INPUT_EXCEL_PATH = r"C:\Users\hehih\ownCloud\알바\전라남도\전라남도_홈페이지_사이트_r4.xlsx" # 원본 엑셀 파일 경로
OUTPUT_EXCEL_PATH = r"C:\Users\hehih\ownCloud\알바\전라남도\전라남도_홈페이지_사이트_r4_ai.xlsx" # 결과 저장 파일 경로
BASE_DOMAIN = "https://www.jeonnam.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 analyze_content(soup_element):
"""
지정된 본문 BeautifulSoup 엘리먼트를 분석하여 저작물 유형 8종을 판별합니다.
"""
if not soup_element:
return "없음"
detected_types = []
# 전체 텍스트 및 HTML 소스 추출
text_content = soup_element.get_text(strip=True)
html_source = str(soup_element).lower()
# 1. 어문 판별 (공백 제외 순수 텍스트 존재 여부)
if text_content:
detected_types.append("어문")
# 2. 이미지 판별 (인라인 배경 이미지 또는 유효한 img 태그)
has_valid_image = False
# 인라인 스타일 background-image 검사
if "background-image" in html_source:
has_valid_image = True
else:
# img 태그 검사 (지정된 예외 패턴 처리)
for img in soup_element.find_all("img"):
src = img.get("src", "")
if src and "/home/bbs/img/new_img_opentype" not in src:
has_valid_image = True
break
if has_valid_image:
detected_types.append("이미지")
# 3. 영상 판별
video_extensions = [".mp4", ".avi", ".mkv", ".wmv", ".mov", ".flv"]
has_video = any(tag in html_source for tag in ["<video", "<iframe", "<embed", "<object", "youtube.com", "youtu.be"])
if not has_video:
# 확장자 링크 검사
for a in soup_element.find_all("a", href=True):
if any(a["href"].lower().endswith(ext) for ext in video_extensions):
has_video = True
break
if has_video:
detected_types.append("영상")
# 4. 오디오 판별
audio_extensions = [".mp3", ".wav", ".ogg", ".wma", ".m4a"]
has_audio = "<audio" in html_source
if not has_audio:
for a in soup_element.find_all("a", href=True):
if any(a["href"].lower().endswith(ext) for ext in audio_extensions):
has_audio = True
break
if has_audio:
detected_types.append("오디오")
# 5. 글꼴 판별
font_extensions = [".ttf", ".woff", ".woff2", ".otf", ".eot"]
has_font = "@font-face" in html_source
if not has_font:
for link in soup_element.find_all(["link", "a"], href=True):
if any(link["href"].lower().endswith(ext) for ext in font_extensions):
has_font = True
break
if has_font:
detected_types.append("글꼴")
# 6. 3D 판별
d_extensions = [".obj", ".gltf", ".glb", ".fbx", ".3ds"]
has_3d = "webgl" in html_source or "<canvas" in html_source
if not has_3d:
for a in soup_element.find_all("a", href=True):
if any(a["href"].lower().endswith(ext) for ext in d_extensions):
has_3d = True
break
if has_3d:
detected_types.append("3D")
# 7. 기타 판별 (본문에 글/미디어가 없으나 문서 첨부파일만 덩그러니 있는 경우)
if not text_content and not has_valid_image and not has_video and not has_audio and not has_font and not has_3d:
doc_extensions = [".zip", ".pdf", ".hwp", ".docx", ".xlsx", ".pptx"]
for a in soup_element.find_all("a", href=True):
if any(a["href"].lower().endswith(ext) for ext in doc_extensions):
detected_types.append("기타")
break
# 8. 없음 판별
if not detected_types:
return "없음"
# 중복 제거 후 콤마로 연결하여 반환
return ", ".join(dict.fromkeys(detected_types))
def main():
if not os.path.exists(INPUT_EXCEL_PATH):
print(f"[오류] 입력 파일 '{INPUT_EXCEL_PATH}'을 찾을 수 없습니다.")
return
# openpyxl로 기존 서식을 유지하며 파일 로드
wb = openpyxl.load_workbook(INPUT_EXCEL_PATH, data_only=False)
sheet = wb.active
print("==================================================")
print("🚀 웹 스크래이핑 및 저작물 유형 자동 분류를 시작합니다.")
print("==================================================")
# 4행부터 데이터 영역 순회
for row_idx in range(4, sheet.max_row + 1):
k_val = sheet.cell(row=row_idx, column=11).value # K열: URL 주소
l_val = sheet.cell(row=row_idx, column=12).value # L열: 게시판형태
# 조건 필터링: L열이 '게시판' 또는 '페이지'이고 K열에 주소가 있는 경우만 진행
if not k_val or l_val not in ["게시판", "페이지"]:
continue
target_url = str(k_val).strip()
# 상대 경로일 경우 기본 도메인과 결합
if target_url.startswith("/"):
target_url = urljoin(BASE_DOMAIN, target_url)
row_results = []
try:
if l_val == "게시판":
print(f"\n[행 {row_idx}] 📋 게시판 목록 스캔 중: {target_url}")
res = requests.get(target_url, headers=HEADERS, timeout=10)
if res.status_code != 200:
print(f" └ [접속 실패] Status Code: {res.status_code}")
continue
soup = BeautifulSoup(res.text, "html.parser")
# 목록에서 상세 페이지로 이동하는 <a> 태그 수집
detail_links = []
title_tds = soup.find_all("td", class_="title")
for td in title_tds:
a_tag = td.find("a", href=True)
if a_tag:
href = a_tag["href"]
full_detail_url = urljoin(BASE_DOMAIN, href)
detail_links.append(full_detail_url)
# 상위 최대 5개로 제한 규칙 적용
detail_links = detail_links[:5]
print(f" └ 수집된 상세글 링크 개수: {len(detail_links)}")
# 수집된 상세 페이지 순회 분석
for d_url in detail_links:
print(f" └ 🔍 상세 페이지 분석 중: {d_url}")
d_res = requests.get(d_url, headers=HEADERS, timeout=10)
if d_res.status_code == 200:
d_soup = BeautifulSoup(d_res.text, "html.parser")
# 사용자 지정 게시판 본문 영역 태그 탐색
content_area = d_soup.find(class_="bbs_view_contnet")
if content_area:
res_type = analyze_content(content_area)
row_results.append(res_type)
else:
row_results.append("없음")
else:
print(f" └ [상세 페이지 접속 실패] Status Code: {d_res.status_code}")
elif l_val == "페이지":
print(f"\n[행 {row_idx}] 🌐 페이지 다이렉트 스캔 중: {target_url}")
res = requests.get(target_url, headers=HEADERS, timeout=10)
if res.status_code != 200:
print(f" └ [접속 실패] Status Code: {res.status_code}")
continue
soup = BeautifulSoup(res.text, "html.parser")
# 사용자 지정 일반 페이지 본문 영역 태그 탐색
content_area = soup.find("article", class_="container")
if content_area:
res_type = analyze_content(content_area)
row_results.append(res_type)
else:
row_results.append("없음")
# 행별 최종 결과 도출 (다중 상세글 결과 병합 및 중복 제거)
if row_results:
combined_items = []
for res_str in row_results:
if res_str and res_str != "없음":
combined_items.extend([item.strip() for item in res_str.split(",")])
final_status = ", ".join(dict.fromkeys(combined_items)) if combined_items else "없음"
else:
final_status = "없음"
# 기존 스타일 유지하며 N열(14번째 열)에 데이터 기록
sheet.cell(row=row_idx, column=14).value = final_status
print(f" 🎯 [분류 완료] 행 {row_idx} -> N열 기입 값: {final_status}")
except Exception as e:
print(f" ❌ [에러 발생] 행 {row_idx} 처리 중 오류: {e}")
sheet.cell(row=row_idx, column=14).value = "오류 발생"
# 기존 서식을 유지한 채 최종 파일 저장
wb.save(OUTPUT_EXCEL_PATH)
wb.close()
print("\n==================================================")
print(f"🎉 모든 작업이 완료되었습니다! 파일이 안전하게 저장되었습니다.")
print(f"📂 결과 파일 경로: {os.path.abspath(OUTPUT_EXCEL_PATH)}")
print("==================================================")
if __name__ == "__main__":
main()