[Crawling] Selenium

웹 상의 정보를 크롤링할 때 동적인 화면을 사용할 경우 셀레니움을 많이 사용한다. 관련 작업을 할 때 유용하게 사용할 수 있는 자료를 정리하였다.

구글 코랩에서 사용하기

  • 코랩에서 사용하기 위해서는 크롬드라이버와 selenium을 별도로 설치해야 한다. 설치 후에도 옵션을 설정해주어야 사용할 수 있다.

      # install chromium, its driver, and selenium
      !apt-get update
      !apt install chromium-chromedriver
      !cp /usr/lib/chromium-browser/chromedriver /usr/bin
      !pip install selenium
    
      # set options to be headless, ..
      from selenium import webdriver
      options = webdriver.ChromeOptions()
      options.add_argument('--headless')
      options.add_argument('--no-sandbox')
      options.add_argument('--disable-dev-shm-usage')
    
      # open it, go to a website, and get results
      driver = webdriver.Chrome('chromedriver',options=options)
    
  • 참고자료

Selenium 옵션

  • 드라이버가 크롤링을 진행하는 과정을 굳이 확인하고 싶지 않다면 headless를 통해 감출 수 있고, 파일을 다운로드 받고자 할 경우 down.default_directory를 사용하여 파일 저장 경로를 미리 설정해두면 지정한 폴더에 파일들을 저장할 수 있다.
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('headless') # 드라이버 화면을 감춤
options.add_experimental_option("prefs", {
 "download.default_directory": r'파일 저장 경로',
  "download.prompt_for_download": True,
  "download.directory_upgrade": True,
  "safebrowsing.enabled": True
})

대기(wait)

  • 동적 페이지를 크롤링하다 보면, 해당 페이지가 제대로 로딩이 된 후에 크롤링을 진행해야 누락되는 정보없이 가져올 수 있다. 특히 페이지 로딩이 늦은 환경에서는 대기 시간을 지정해주지 않으면 오류가 뜰 확률이 높다. 따라서 대기를 중간 중간 해주는 것이 중요한데, 대기 상태는 세 가지로 구분할 수 있다.
  • 명시적 대기(Explicit Waits): 절대적인 대기 시간을 지정하는 방식이다. 가장 간단한 방식은 time.sleep()이지만 지정한 시간동안 무조건적으로 대기한다는 점에서 효율이 떨어지기 때문에 다음 코드(WebDriverWait)를 사용할 수 있다.

      from selenium.webdriver.common.by import By
      from selenium.webdriver.support.ui import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
    
      driver = webdriver.Chrome('chromedriver')
      driver.get(url='https://www.google.com/')
      try:
          element = WebDriverWait(driver, 10).until(
              EC.presence_of_element_located((By.CLASS_NAME , '클래스명'))
          )
      finally:
          driver.quit()
    

    웹페이지에서 ‘클래스명’에 해당하는 요소를 찾을 수 있는지 매 0.5초마다 확인하고, 찾을 수 있다면 True를 반환한다. 위 코드에서는 해당 작업을 최대 10초 진행한다.

  • 암묵적 대기(Implicit Waits): DOM이 로드될 때까지 대기하는 것을 의미한다. 찾으려는 요소가 로드될 때까지 최대 몇 초(time_to_wait)까지 기다릴지 설정한다. Default 값은 0이다.

      driver.implicitly_wait(time_to_wait=5)
    
  • 참고자료

결과 개수 확인

  • 검색을 했을 때 결과의 개수를 확인하고 싶다면 다음 코드를 사용한다. 이 때 XPATH 속성은 검색 결과의 가장 상위 속성이어야 하고, elements로 제대로 입력했는지 다시 한 번 확인한다.
len(driver.find_elements_by_xpath("XPATH"))

로그인 및 입력, 파일 업로드

  • 로그인과 텍스트 데이터를 입력하는 것은 동일한 과정으로 진행한다. 빈 창에 접근하여 원하는 정보를 전송하는 방식이다.

    selenium

  • 로그인을 예시로 들면, 로그인을 진행할 아이디창과 비밀번호창에 접근한다. 그 후 send_keys를 사용해서 정보를 입력한 후 로그인 버튼을 클릭한다.

      driver.find_element_by_id('속성명').send_keys('아이디')
      driver.find_element_by_id('속성명').send_keys('비밀번호')
      driver.find_element_by_xpath('XPATH 속성').click()
    

    find_element_by_id 대신 find_element_by_name, find_element_by_xpath 등 다양하게 사용할 수 있다.

  • 파일 업로드의 경우, send_keys에 들어가는 값을 해당 파일의 경로로 설정하면 된다.

    selenium

      driver.find_element_by_id('속성명').send_keys('파일의 경로')
    
  • 참고자료

드롭다운(Dropdown)

selenium

  • 드롭다운으로 표현된 정보의 경우 다음 코드를 사용하여 선택한다.
from selenium.webdriver.support.ui import Select

select = Select(driver.find_element_by_id('속성'))
select.select_by_index(int('인덱스'))

드롭다운에 포함된 옵션들을 사용하여 dictionary를 작성하면 인덱스를 쉽게 확인할 수 있다. text를 불러오면 ‘\n \n ‘로 연결된 string 데이터가 출력되기 때문에 split 해준 후 사용한다.

options = driver.find_element_by_xpath('XPATH').text.split('\n        \n         ')
option = [option.replace('\n        \n       ', '') for option in options]
options = {idx+1: r for idx, r in enumerate(option)}
# {1: 'A', 2: 'B', 3: 'C'}

체크박스(Checkbox)

selenium

  • 체크박스는 클릭하는 방식과 동일하게 작동한다. 검사도구를 활용하여 몇 번째 박스인지 인덱스를 확인하여 클릭한다.

      click_button = driver.find_element_by_id('속성'+str('인덱스'))
      click_button.click()
    
  • 체크박스가 이미 선택되었는지를 확인하기 위해서는 다음의 코드를 확인한다. True일 경우 이미 선택되었음을 의미한다.

      click_button.is_selected()
    
      # 참고: 클릭이 되었다는 것을 확인하고 싶을 경우
      assert click_button.is_selected()
    
  • 참고자료

팝업창 확인

selenium

  • 내용을 작성하고 등록하거나 저장을 할 때 위와 같은 팝업창이 뜬다면 ‘Inspect’를 통해 속성을 가져오는 것이 불가능하다. 따라서 다음 코드를 사용하여 팝업창을 제거해주어야 한다.

      from selenium.webdriver.common.alert import Alert
    
      # 팝업창1
      al1 = Alert(driver)
      al1.accept()
      time.sleep(1)
      # 팝업창2
      al2 = Alert(driver)
      al2.accept()
    
      # 혹은 간결하게 다음과 같이 사용할 수도 있다.
      Alert(driver).accept()
      Alert(driver).accept()
    

    Alert를 통해 어떤 경고가 떴는지를 알려주고 확인 버튼을 클릭하는 방식이다. 팝업창 수가 더 많다면 Alert 함수를 단순 반복하면 된다. 중간에 대기 시간을 두어 팝업창이 제대로 로딩될 때까지 기다려준다.

  • 참고자료

버튼 반복 클릭

  • 버튼을 반복적으로 클릭해야할 경우도 존재한다. 예를 들어, ‘추가’ 버튼을 누르면 칸이 하나 더 생긴다던지 할 때 사용할 수 있다. 이 때도 앞서 팝업창에서 확인을 클릭했던 방식으로, 여러 번 반복해주면 된다.

      click_button = driver.find_element_by_xpath('XPATH')
    
      for _ in range(클릭횟수):
        click_button.click()
    
  • 참고자료

검색창 제거

selenium

  • 정보를 입력한 후 검색창 혹은 내용 작성칸을 지워야할 경우 (위의 예시에서 ‘안녕하세요’라는 글자를 지우고자 할 때) 다음 코드를 사용할 수 있다.
driver.find_element_by_id('속성').clear()

파일 다운로드

  • 파일을 다운로드 받고자 할 때는 기본적으로 다운로드 경로를 설정한 후 클릭 버튼을 통해 다운로드 한다.
  • 문제는 해당 파일의 종류에 따라 다른 폴더에 저장하고자 할 때인데, 이 때는 다운로드가 완료된 파일의 이름을 변경하는 방식으로 파일의 위치를 옮길 수 있다. 다음 코드는 셀레니움을 사용하여 페이지를 로드한 후 BeautifulSoup를 사용하여 크롤링한 예시이다.

      for f in range(len(soup.select('#bo_v_file > ul > li > a'))): # 해당 경로에 존재하는 파일 수 확인
       file = [] # 파일명 저장을 위한 준비
       tmp = soup.select('#bo_v_file > ul > li > a')[f].text # 파일명 수집
       tmp = tmp.replace('\n', '') # 파일명 정제
       file.append(tmp) # 파일명 저장
       driver.find_element_by_xpath('//*[@id="bo_v_file"]/ul/li['+str(f+1)+']/a').click() # 파일 다운로드
       path_dir = r'G:\내 드라이브\file\test' # 옵션에서 다운로드 경로를 이곳으로 지정한 상태이다. 해당 폴더에는 아무 파일도 존재하지 않는다
       time.sleep(30) # 파일 다운로드 소요 시간
       file_list = os.listdir(path_dir) # 파일이 다운로드된 폴더의 파일 리스트
       os.rename(r'G:\내 드라이브\file\test\{}'.format(file_list[0]), # 다운로드된 폴더에 존재하는 파일의 파일명을 아래와 같이 변경한다.
                 r'G:\내 드라이브\file\{}\{}'.format(f, file_list[0])) # 몇 번째('f') 파일인지를 확인할 수 있는 폴더 내에 해당 파일을 위치한다. 폴더명은 자유롭게 설정한다.
      driver.close()
    

창 전환 (새 창)

  • 새로운 창이 뜨고 해당 창에서 크롤링을 진행해야 할 경우, window_handles를 사용하여 창을 전환한다. window_handles[0]이 가장 첫번째 창, 그 후에 생긴 창들은 인덱스를 변경하면 된다.

      driver.switch_to_.window(driver.window_handles[1])
      driver.get_window_position(driver.window_handles[1])
      # 새로 열린 창에서 작동
    

    닫기 혹은 저장 버튼을 클릭하여 해당 창이 닫히더라도 위 코드를 입력하여 기존의 창으로 돌아가야 한다.

  • 현재 창의 이름을 확인(driver.title)하거나 창의 개수(len(driver.window_handles))를 확인할 수도 있다.
  • 창이 여러 개일 때, 가장 메인 창을 제외하고 모두 닫으려면 다음 코드를 사용한다.

      main = driver.window_handles
      for handle in main:
       if handle != main[0]:
        driver.switch_to_.window(handle)
        driver.close()
    
  • 현재 창을 닫기 위해서는 driver.close()를 사용한다. 다만, 현재 창이 닫고자 하는 창이 맞는지 확인을 꼭 해줘야 한다. (switch_to_.window를 사용하여 닫고자 하는 창으로 돌아간 후 닫는다.)

  • 참고자료

오류

  • ElementClickInterceptedException
    • 해당 요소를 클릭하고자 했을 때 스크롤의 문제나 페이지 로딩의 문제 때문에 특정 위치에 해당 요소가 없다. 새로 페이지에 접속해서 클릭하는 경우 앞서 언급한 ‘대기’의 implicitly_wait을 사용하고, 이미 접속한 페이지에서 사용하는 경우 다음과 같이 execute_script를 사용하여 해결할 수 있다.
      click_button = driver.find_element_by_id('속성')
      driver.execute_script("arguments[0].scrollIntoView();", click_button)
      driver.execute_script("arguments[0].click();", click_button)
    
  • NoSuchElementException
    • 원하는 요소가 페이지에 없을 때 발생했을 수 있는 문제는 다음과 같다.
      • 페이지 로딩 문제
        • 페이지를 제대로 작성하였는지, 해당 요소가 그 위치에 존재하는 것이 맞는지 확인한다.
        • 코드를 제대로 작성하였다면, Scroll을 사용하여 해당 위치로 변경해주는 작업을 추가해줄 경우 발생 확률이 낮아진다.
        • 만약 해당 문제가 발생하여도 상관없다면 Try Except를 사용한다.

            try:
              element = driver.find_element_by_xpath('XPATH')
            except NoSuchElementException as ex:
              print('%d 번째 %s 오류' % (i, ex))
          
      • 새 창에서 작업하는 문제
        • 상위에서 작성한 코드를 참고하여, 기존 창으로 돌아가야 한다.
    • 참고자료
  • Inalid Argument:
    • 사용자가 업로드하고자 하는 파일의 경로나 해당 페이지의 링크에 문제가 있다. 따라서 이 부분을 다시 확인한다.

댓글남기기