공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
139 lines
6.5 KiB
Python
139 lines
6.5 KiB
Python
import openpyxl
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
import re
|
|
import time
|
|
|
|
def crawl_moe_license_separate_save(origin_path, save_path, sheet_name=None):
|
|
"""
|
|
교육부 엑셀 리스트 맞춤형 크롤러 (원본 읽기 -> 크롤링 -> 별도 파일로 저장)
|
|
"""
|
|
try:
|
|
# 1. 원본 엑셀 파일 로드
|
|
wb = openpyxl.load_workbook(origin_path)
|
|
ws = wb[sheet_name] if sheet_name else wb.active
|
|
print(f"📂 원본 파일을 성공적으로 불러왔습니다: {origin_path}")
|
|
except Exception as e:
|
|
print(f"❌ 원본 엑셀 파일을 열 수 없습니다. 경로를 확인해주세요. 에러: {e}")
|
|
return
|
|
|
|
# 웹 서버 차단 방지용 User-Agent 설정
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
|
|
}
|
|
|
|
print("🚀 교육부 공공누리 라이선스 크롤링을 시작합니다. (대상: 3행 ~ 마지막 행)")
|
|
|
|
# 3행부터 데이터가 있는 마지막 행까지 순회
|
|
for row in range(3, ws.max_row + 1):
|
|
url_cell = ws.cell(row=row, column=11).value # K열 (11번째 열)
|
|
|
|
if not url_cell:
|
|
continue
|
|
url = str(url_cell).strip()
|
|
|
|
# [예외 처리] URL에 특정 도메인(www.moe.go.kr)이 포함되어 있지 않은 경우 처리
|
|
if 'www.moe.go.kr' not in url:
|
|
ws.cell(row=row, column=12, value="사이트") # L열: 사이트 명시
|
|
ws.cell(row=row, column=13, value="") # M열: 빈칸
|
|
ws.cell(row=row, column=15, value="") # O열: 빈칸
|
|
ws.cell(row=row, column=16, value="") # P열: 빈칸
|
|
ws.cell(row=row, column=17, value="") # Q열: 빈칸
|
|
print(f"[{row}행] 외부 도메인 필터링 -> '사이트' 분류 처리")
|
|
continue
|
|
|
|
try:
|
|
# 웹페이지 요청 (타임아웃 10초 설정)
|
|
response = requests.get(url, headers=headers, timeout=10)
|
|
response.raise_for_status()
|
|
|
|
# 인코딩 설정
|
|
response.encoding = response.apparent_encoding
|
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
|
|
# --- 1단계. L열 / M열: 게시판 형태 및 수량 판별 ---
|
|
board_top_div = soup.find('div', {'data-board': 'top'})
|
|
|
|
is_board = False
|
|
total_count = None
|
|
|
|
if board_top_div:
|
|
strong_tag = board_top_div.find('strong')
|
|
if strong_tag:
|
|
span_tag = strong_tag.find('span')
|
|
if span_tag:
|
|
# 숫자 데이터만 추출
|
|
count_text = re.sub(r'[^0-9]', '', span_tag.text)
|
|
if count_text:
|
|
total_count = int(count_text)
|
|
is_board = True
|
|
|
|
# 형태 판별 결과 저장 (대시 대신 무조건 빈칸 규칙 적용)
|
|
if is_board:
|
|
ws.cell(row=row, column=12, value="게시판") # L열
|
|
ws.cell(row=row, column=13, value=total_count) # M열
|
|
else:
|
|
ws.cell(row=row, column=12, value="페이지") # L열
|
|
ws.cell(row=row, column=13, value="") # M열 (빈칸)
|
|
|
|
# --- 2단계. O, P, Q열: <div id="content"> 본문 영역 라이선스 탐색 ---
|
|
content_area = soup.find('div', id='content')
|
|
|
|
license_type = "미부착"
|
|
mark_location = "" # 미부착 시 기본 빈칸
|
|
has_license = "N"
|
|
|
|
if content_area:
|
|
content_str = str(content_area)
|
|
|
|
# 공공누리 통합 식별 패턴
|
|
type_match = re.search(r'kogl_img([1-4])|licenseType=([1-4])|type([1-4])_bg|kogl_type([1-4])', content_str, re.IGNORECASE)
|
|
|
|
if type_match:
|
|
matched_num = next(g for g in type_match.groups() if g is not None)
|
|
license_type = f"{matched_num}유형"
|
|
mark_location = "게시판" if is_board else "페이지"
|
|
has_license = "Y"
|
|
|
|
# 규정된 열 자리에 매칭 결과 입력
|
|
ws.cell(row=row, column=15, value=license_type) # O열
|
|
ws.cell(row=row, column=16, value=mark_location) # P열
|
|
ws.cell(row=row, column=17, value=has_license) # Q열
|
|
|
|
print(f"[{row}행] 형태: {ws.cell(row=row, column=12).value} | 건수: {ws.cell(row=row, column=13).value} | 결과: {license_type}")
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
# 접속 에러나 사이트 다운 시 대시 대신 빈칸 처리 규칙 엄수
|
|
ws.cell(row=row, column=12, value="에러")
|
|
ws.cell(row=row, column=13, value="")
|
|
ws.cell(row=row, column=15, value="접속불가")
|
|
ws.cell(row=row, column=16, value="")
|
|
ws.cell(row=row, column=17, value="N")
|
|
print(f"[{row}행] 웹 접속 실패 (Error: {e})")
|
|
|
|
# 서버 부하 방지를 위한 미세 지연 설정 (0.5초)
|
|
time.sleep(0.5)
|
|
|
|
# 새로운 파일 경로로 안전하게 저장
|
|
try:
|
|
wb.save(save_path)
|
|
wb.close()
|
|
print(f"🎯 작업 완료! 새로운 파일이 성공적으로 저장되었습니다: {save_path}")
|
|
except PermissionError:
|
|
print("❌ 에러: 저장하려는 새 파일이 이미 열려 있어 저장할 수 없습니다. 파일을 닫고 다시 실행해주세요.")
|
|
|
|
# ================================================================= #
|
|
# [실행부] r"" 안에 불러올 파일과 저장할 파일 경로를 각각 적어주세요.
|
|
# ================================================================= #
|
|
if __name__ == "__main__":
|
|
|
|
# 1. 읽어올 원본 파일 경로
|
|
ORIGIN_EXCEL_PATH = r"C:\Users\hehih\ownCloud\알바\교육부\교육부_사이트맵_r2.xlsx"
|
|
|
|
# 2. 결과물이 저장될 새로운 파일 경로
|
|
SAVE_EXCEL_PATH = r"C:\Users\hehih\ownCloud\알바\교육부\교육부_사이트맵_r3.xlsx"
|
|
|
|
TARGET_SHEET_NAME = "조사양식" # 작업할 시트 이름
|
|
|
|
# 실행
|
|
crawl_moe_license_separate_save(ORIGIN_EXCEL_PATH, SAVE_EXCEL_PATH, TARGET_SHEET_NAME) |