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열:
본문 영역 라이선스 탐색 --- 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)