From fb7447d44ed98fb22d67656e5ff242ad66f59584 Mon Sep 17 00:00:00 2001 From: KWON Date: Fri, 27 Jun 2025 12:29:43 +0900 Subject: [PATCH] =?UTF-8?q?StaleElementReferenceException=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C=205=EB=B2=88=EA=B9=8C=EC=A7=80=20?= =?UTF-8?q?=EC=9E=AC=EC=8B=9C=EB=8F=84=ED=95=98=EB=A9=B0=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=A1=9C=EA=B7=B8=20=EC=B6=9C=EB=A0=A5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TimeoutException도 재시도에 포함. finally 블록으로 driver.quit() 보장. 옵션 및 임시 유저 데이터 디렉토리 지정으로 충돌 방지. WebDriverWait + element_to_be_clickable 로 클릭 안정성 향상. --- data/weather_capture.py | 94 +++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/data/weather_capture.py b/data/weather_capture.py index 2ec33f2..2d1411b 100644 --- a/data/weather_capture.py +++ b/data/weather_capture.py @@ -3,68 +3,72 @@ from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import StaleElementReferenceException, TimeoutException from datetime import datetime import os import time import tempfile -# 옵션 객체 생성 및 설정 +# 크롬 옵션 설정 options = Options() options.add_argument('--headless') options.add_argument('--window-size=1802,1467') -options.add_argument('--no-sandbox') # Docker 환경 권장 -options.add_argument('--disable-dev-shm-usage') # Docker 환경 권장 -options.add_argument('--disable-gpu') # GPU 사용 안함 (headless에서 권장) +options.add_argument('--no-sandbox') +options.add_argument('--disable-dev-shm-usage') +options.add_argument('--disable-gpu') -# 임시 사용자 데이터 디렉토리 지정 (매 실행마다 고유한 디렉토리 사용) +# 임시 사용자 데이터 디렉토리 생성 및 지정 (중복 실행 문제 방지) temp_dir = tempfile.mkdtemp() options.add_argument(f'--user-data-dir={temp_dir}') -# 드라이버 실행 driver = webdriver.Chrome(options=options) -# 현재 스크립트 위치 -script_dir = os.path.dirname(os.path.abspath(__file__)) +try: + script_dir = os.path.dirname(os.path.abspath(__file__)) -driver.get('https://www.weather.go.kr/w/weather/forecast/short-term.do#dong/4148026200/37.73208578534846/126.79463099866948') + driver.get('https://www.weather.go.kr/w/weather/forecast/short-term.do#dong/4148026200/37.73208578534846/126.79463099866948') -wait = WebDriverWait(driver, 10) + wait = WebDriverWait(driver, 10) -# 첫 번째 탭 클릭 (element_to_be_clickable 사용) -tab_button = wait.until(EC.element_to_be_clickable( - (By.XPATH, '//*[@id="digital-forecast"]/div[1]/div[3]/div[1]/div/div/a[2]') -)) -tab_button.click() + # 첫 번째 탭 클릭 (안전하게 클릭 대기) + tab_button = wait.until(EC.element_to_be_clickable( + (By.XPATH, '//*[@id="digital-forecast"]/div[1]/div[3]/div[1]/div/div/a[2]') + )) + tab_button.click() -# 두 번째 항목 클릭 (stale element 방지 위해 최대 3회 재시도) -list_button_xpath = '//*[@id="digital-forecast"]/div[1]/div[3]/ul/div[1]/a[2]' + # 두 번째 항목 클릭 - stale element 대비 최대 5회 재시도 + list_button_xpath = '//*[@id="digital-forecast"]/div[1]/div[3]/ul/div[1]/a[2]' + for attempt in range(5): + try: + list_button = wait.until(EC.element_to_be_clickable((By.XPATH, list_button_xpath))) + list_button.click() + break + except StaleElementReferenceException: + print(f"시도 {attempt+1}: stale element 참조 오류 발생, 재시도 중...") + time.sleep(1) + except TimeoutException: + print(f"시도 {attempt+1}: 요소 대기 시간 초과, 재시도 중...") + time.sleep(1) + else: + print("두 번째 항목 클릭 실패. 스크립트 종료.") + driver.quit() + exit(1) -for attempt in range(3): - try: - list_button = wait.until(EC.element_to_be_clickable((By.XPATH, list_button_xpath))) - list_button.click() - break - except Exception as e: - print(f"시도 {attempt+1}: 오류 발생 - {e}. 재시도 중...") - time.sleep(1) -else: - print("요소 클릭 실패. 스크립트 종료.") + time.sleep(2) # 페이지 반영 대기 + + # 캡처 대상 요소 대기 후 찾기 + target_element = wait.until(EC.presence_of_element_located( + (By.XPATH, '/html/body/div[2]/section/div/div[2]') + )) + + # 저장 경로 설정 + timestamp = datetime.now().strftime('%Y%m%d') + save_path = os.path.join(script_dir, f'weather_capture_{timestamp}.png') + + # 요소 스크린샷 저장 + target_element.screenshot(save_path) + + print(f'📸 캡처 완료! 저장 위치: {save_path}') + +finally: driver.quit() - exit(1) - -time.sleep(2) # 페이지 반영 대기 - -# 캡처 대상 요소 찾기 -target_element = wait.until(EC.presence_of_element_located( - (By.XPATH, '/html/body/div[2]/section/div/div[2]') -)) - -# 저장 경로 설정 -timestamp = datetime.now().strftime('%Y%m%d') -save_path = os.path.join(script_dir, f'weather_capture_{timestamp}.png') - -# 요소 스크린샷 저장 -target_element.screenshot(save_path) - -driver.quit() -print(f'📸 캡처 완료! 저장 위치: {save_path}')