Language/Python

[Python]Selenium: StaleElementReferenceException

retto9522 2024. 9. 23. 10:31

StaleElementReferenceException

StaleElementReferenceException은 요소가 더 이상 DOM의 존재하지 않거나 업데이트 된 경우에 발생하는 오류입니다.

웹 페이지가 변경되거나 요소가 업데이트될 때 발생할 수 있습니다.

 

예시 코드

다음은 UNIQLO의 상품 데이터를 크롤링하는 예시 코드입니다.

def crawling():
    browser = webdriver.Chrome(options = options)
    browser.get(url_list[0])
    WebDriverWait(browser, 10).until(EC.presence_of_all_elements_located((By.XPATH,"//*[@id='root']/div[3]/div[2]/div[2]/section[1]/div/section/div/div[2]/div/div/div/div")))

    products = browser.find_element(By.CLASS_NAME,"fr-ec-product-collection").find_elements(By.CLASS_NAME,"fr-ec-product-tile-resize-wrapper")

    for product in products:
            # 제품 상세 URL
            url = product.find_element(By.TAG_NAME, "a").get_attribute("href")
            browser.get(url)

            # browser가 출력될 때까지 대기
            WebDriverWait(browser, 20).until(EC.presence_of_element_located((By.CLASS_NAME, "fr-ec-template-pdp"))) 

            code = browser.find_element(By.CLASS_NAME,"fr-ec-template-pdp") \
                        .find_element(By.CLASS_NAME,"fr-ec-layout") \
                        .find_element(By.CLASS_NAME,"fr-ec-gutter-container") \
                        .find_element(By.CLASS_NAME,"fr-ec-caption").text

            # 제품 상세 설명 Click
            browser.find_element(By.CLASS_NAME,"fr-ec-template-pdp") \
                        .find_element(By.CLASS_NAME,"fr-ec-layout") \
                        .find_element(By.CLASS_NAME,"fr-ec-gutter-container") \
                        .find_element(By.TAG_NAME,"legend").click()
            WebDriverWait(browser, 20).until(EC.presence_of_all_elements_located((By.ID,"productLongDescription-content")))
            
         
            description = browser.find_element(By.CLASS_NAME,"fr-ec-template-pdp") \
                        .find_element(By.CLASS_NAME,"fr-ec-layout") \
                        .find_element(By.CLASS_NAME,"fr-ec-gutter-container") \
                        .find_element(By.ID,"productLongDescription-content") \
                        .find_element(By.CLASS_NAME,"fr-ec-body").text
            
                        
            print(code)
            print(description)
            print("============================================================\n")

 

 

 

상품 리스트 페이지 (출처: https://www.uniqlo.com/kr/ko/men/outerwear/parkas-and-blousons?path=%2C%2C58035%2C)

상품 리스트 페이지에서 products 리스트 변수에 상품 리스트를 담아 넣은 후, products 리스트를 순회하며 product 요소 안 "href" 속성 값을 추출하여 해당 속성 값을 통해 다시 상품 상세페이지로 browser를 열게 됩니다. 

 

상품 상세 페이지 (출처:https://www.uniqlo.com/kr/ko/products/E464024-000/00?colorDisplayCode=69&sizeDisplayCode=005)

위와 같은 상품 상세 페이지에서 상품코드, 상품설명의 text를 가져오게 됩니다. 첫 번째 상품에 대한 정보를 가져온 이후, 두 번째 for 문 돌 때, 문제가 발생하게 됩니다.

 

크롤링 결과

이러한 문제가 발생한 이유는 products에 담긴 요소들이 더 이상 유효하지 않기 때문에 "href" 속성을 찾을 수 없게 되었기 때문입니다.

문제를 해결하기 위해선 상품 리스트 페이지로 돌아가 다시 상품 목록에 대한 데이터를 가져와야 합니다.

문제 해결

def crawling():
    browser = webdriver.Chrome(options = options)
    browser.get(url_list[0])
    WebDriverWait(browser, 10).until(EC.presence_of_all_elements_located((By.XPATH,"//*[@id='root']/div[3]/div[2]/div[2]/section[1]/div/section/div/div[2]/div/div/div/div")))

    products = browser.find_element(By.CLASS_NAME,"fr-ec-product-collection").find_elements(By.CLASS_NAME,"fr-ec-product-tile-resize-wrapper")

    for i in range(len(products)):
            product = products[i]

            # 제품 상세 URL
            url = product.find_element(By.TAG_NAME, "a").get_attribute("href")
            browser.get(url)

            # browser가 출력될 때까지 대기
            WebDriverWait(browser, 20).until(EC.presence_of_element_located((By.CLASS_NAME, "fr-ec-template-pdp"))) 

            code = browser.find_element(By.CLASS_NAME,"fr-ec-template-pdp") \
                        .find_element(By.CLASS_NAME,"fr-ec-layout") \
                        .find_element(By.CLASS_NAME,"fr-ec-gutter-container") \
                        .find_element(By.CLASS_NAME,"fr-ec-caption").text

            # 제품 상세 설명 Click
            browser.find_element(By.CLASS_NAME,"fr-ec-template-pdp") \
                        .find_element(By.CLASS_NAME,"fr-ec-layout") \
                        .find_element(By.CLASS_NAME,"fr-ec-gutter-container") \
                        .find_element(By.TAG_NAME,"legend").click()
            WebDriverWait(browser, 20).until(EC.presence_of_all_elements_located((By.ID,"productLongDescription-content")))
            
         
            description = browser.find_element(By.CLASS_NAME,"fr-ec-template-pdp") \
                        .find_element(By.CLASS_NAME,"fr-ec-layout") \
                        .find_element(By.CLASS_NAME,"fr-ec-gutter-container") \
                        .find_element(By.ID,"productLongDescription-content") \
                        .find_element(By.CLASS_NAME,"fr-ec-body").text
                      
            print(code)
            print(description)
            print("============================================================\n")

            # 상품 리스트 페이지로 돌아가기
            browser.back()

            # 다시 제품 목록 로드 대기
            WebDriverWait(browser, 10).until(EC.presence_of_all_elements_located((By.XPATH, "//div[contains(@class, 'fr-ec-product-tile-resize-wrapper')]")))
            products = browser.find_elements(By.XPATH, "//div[contains(@class, 'fr-ec-product-tile-resize-wrapper')]")  # 요소를 다시 찾기

첫 번째 for문을 통해 상품 데이터의 정보(상품코드, 상품설명)를 추출한 후 browser.back()를 통해 다시 상품 리스트 페이지로 돌아가  products 변수에 상품 리스트 정보를 담습니다. 

이를 통해 StaleElementReferenceException을 해결할 수 있습니다.

 

코드 수정 후 여러 개의 상품 데이터를 가져온 결과