슬기로운 개발자생활/크롤링

Python Selenium (Chrome) wrapper 사용가이드

개발자 소신 2024. 1. 4. 20:42
반응형

자동화 프로그램 제작 시 대부분은 requests로 API를 직접 요청하는것을 선호하지만, 그것이 불가한 경우에는 selenium을 활용한다.

Selenium

  • 셀레니움은 웹 애플리케이션 자동화 및 테스트를 위한 포터블 프레임워크이다. 자바, C#, 펄 루비 등 다양한 언어들로 제공되며, 윈도우, 리눅스, macOS 플랫폼에서 사용가능하다.

순서

1.  Selenium 클래스화 코드
2.  유용한 Selenium 클래스, 함수
3.  Javascript
4.  자주발생하는 에러와 해결방법

1. Selenium 클래스화 코드

  • web_setting.py (모듈명은 임의 부여)
    # ------------------------------------------------------------------------------------
    # Selnium Setting
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from webdriver_manager.chrome import ChromeDriverManager
    
    class WebDriver:
    	"""
    	Selenium WebDriver 클래스
    	"""
        def init(self, engine='chrome') -> None:
        	self.engine = engine.lower().strip()
            if self.engine == 'chrome':
            	self.options = webdriver.ChromeOptions()
            
        def set_argument(self, headless=True, linux=True, maximize=True, 
                        user_agent=False, lang_kr=False, secret_mode=False, download_path=None):
            """
            셀레니움 드라이버 옵션 설정
            headless: GUI 사용 안함
            gui: GUI 사용안할 시 추가
            maximize: 브라우저 최대화
            lang_kr: 한국어 설정
            secret_mode: 시크릿 모드
            download_path: headless 시 파일 다운로드 경로 설정
            """
    
            # 시스템에 부착된 ... 로그 제거
            self.options.add_experimental_option('excludeSwitches', ['enable-logging'])
    
            if headless:
                self.options.add_argument('--headless')
    
                if linux:
                    # Bypass OS security model
                    self.options.add_argument('--no-sandbox')
                    self.options.add_argument("--disable-gpu")
                    # overcome limited resource problems. 메모리가 부족해서 에러가 발생하는 것 막음
                    self.options.add_argument('--disable-dev-shm-usage')
    
                    # 크롬 드라이버에 setuid를 하지 않음으로써 크롬의 충돌 막음
                    self.options.add_argument('--disable-setuid-sandbox')
    
                    # disabling extensions
                    self.options.add_argument("--disable-extensions")
    
                    # 일단 좀 더 확인 필요
                    self.options.add_argument('--single-process')
    
                if download_path:
                    p = {
                    "download.default_directory": download_path,
                    "download.prompt_for_download": False,
                    "download.directory_upgrade": True,
                    "safebrowsing.enabled": True
                    }
                    self.options.add_experimental_option("prefs", p)
    
            if maximize:
                self.options.add_argument("--start-maximized")
            if user_agent:
                self.options.add_argument("user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36")
            if lang_kr:
                self.options.add_argument("--lang=ko_KR")
            if secret_mode:
                self.options.add_argument("--incognito")
    
    
        def get_driver(self) -> webdriver.Chrome:
            """
            셀레니움 웹드라이버 실행
            """
            if self.engine == 'chrome':
                driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=self.options)
                driver.maximize_window()
                driver.implicitly_wait(5)
                return driver

 

  • 셀레니움 4버전으로 넘어오면서, chrome_path를 인자로 전달하는 것은 deprecated되어, Service 객체를 전달하도록 바뀌었음
  • 크롬의 경우 webdriver-manager 패키지로 인해 chrome 버전에 맞춰서 chromedriver를 다시 설치해야 할 필요가 없어졌다.
  • 이 Service객체에  ChromeDriverManager 객체 생성 후 chromedriver를 설치하도록 하면 ~/.wdm 하위에 크롬버전에 맞는 driver가 설치됨

사용법

webdriver = WebDriver()
webdriver.set_argument()
driver = webdriver.get_driver()
  • 기본적으로 자주사용하는 argument는 True로 기본값을 주었고, 추가적으로 넣을만한 argument들은 기본값은 False로 두었다.
  • 특정 파일을 다운로드 받는 로직이 들어갈 경우 headless모드에서는 download_path를 설정하여 해당 경로에 파일이 들어가도록 설정할 수 있다.

2. 유용한 Selenium 클래스, 함수

# ------------------------------------------------------------------------------------
# Selenium Utils
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

import time

MAX_RETRY_COUNT = 5
def click_alert(d):
    '''
    알림창 처리
    '''
    retry = 0
    while True:
        try:
            Alert(d).accept()
            break
        except:
            time.sleep(1)
            retry+=1

        if retry == MAX_RETRY_COUNT:
            # 에러 발생
            assert False, '알림창 클릭 실패 !!'
  • By: webdriver element 탐색 시 셀렉터로 검색할 것인지, xpath로 검색할 것인지 등을 정의할 수 있고, 실제로 찾을땐 결국 CSS 셀렉터로 변환
  • Keys: 다양한 키 입력 (Ctrl, Shift, Enter 등)
  • Alert: 사이트 알림창 처리
  • WebDriverWait: Element를 찾는데 특정 시간만큼 기다림
  • EC: WebDriverWait 보조클래스
  • click_alert(): 특정 로직 실행 후 알림창이 뜨는것을 처리하기 위한 함수

3. Javascript

  • Selenium은 웹 드라이버를 관리하는 클래스인 만큼, 자바스크립트 코드도 실행 가능
    # Y까지 스크롤
    driver.execute_script("window.scrollTo(0, Y)") 
    
    # 끝까지 스크롤
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    
    # 특정 element로 스크롤
    driver.execute_script("arguments[0].scrollIntoView();", element)
    
    # 클릭
    driver.execute_script("arguments[0].click();", element)
  • execute_script에는 element를 전달할 수 있다. (WebElement 객체)
  • 특정 위치로 scroll하는 경우를 주로 사용. headless여도 element가 보이지 않을 경우 element안의 텍스트를 가져오지 못하는 경우 발생. 또는 클릭이 불가능한 상태도 있을 수 있음
  • 그 외에는 사이트 내 함수 실행 정도
  • 스크롤이 불가능한 경우, text 속성을 가져와야한다면,
        1. get_attribute("innerText")
        2. get_attribute("textContent")를 사용하는것도 해결방법이 될 수 있음

4. 자주발생하는 에러와 해결방법

1. Console 창 없애기

  • 사실 이건 어느순간부터 설정안해도 된 것 같음 옛날버전 셀레니움 사용 시 발생
from selenium.webdriver.chrome.service import Service
from subprocess import CREATE_NO_WINDOW

service = Service('path/to/chromedriver')
service.creationflags = CREATE_NO_WINDOW

driver = webdriver.Chrome(service=service)

 

2. Missing X server or $DISPLAY The platform failed to initialize. Exiting.

  • GUI가 사용불가능한 환경에서 발생했음 (AWS EC2)
    options.add_argument('--headless')

3. Chrome 미설치 오류

  • 에러 메시지는 확인 못함, 확인 시 추가하겠음
# Chrome 설치
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb

google-chrome --version

 

4. This version of ChromeDriver only supports Chrome version xxx

  • chromedriver와 chrome 버전이 다를경우 발생
  • webdriver_manager 패키지 사용하면 해결, 혹은 google-chrome --version 명령어로 버전 확인 후, 맞는 chromedriver 설치

5. DevToolsActivePort file doesn't exist (ChromeDriver is assuming that Chrome has crashed)

  • 구글링 결과 정확한 원인은 파악되지 않았으나 경험에 근거하면 Ubuntu EC2에서 발생한 문제, 웹드라이버 여러개 실행했을 때 발생했던 것 같음, 혹은 드라이버를 완전히 종료하지 않았을 경우 발생할수도 있음
    options.add_argument('--headless')
    self.options.add_argument('--disable-dev-shm-usage')
    self.options.add_argument('--single-process')
  • 대충 위의 세가지 옵션으로 해결이 가능하다. 혹은 프로그램이 에러로 인해 종료될 때, driver를 종료해주는 옵션으로도 어느정도는 해결이 가능할 것
    # webdriver 클래스로 driver객체를 생성했다고 가정
    
    # 1. 예외처리문 활용
    try:
    	driver = webdriver.get_driver()
    except Exception as e:
    	print(e)
    finally:
    	driver.quit()
    
    # 2. 클래스의 생성자, 소멸자 활용
    class Selenium:
    	def init(self):
    		self.driver = webdriver.get_driver()
    	
    	def __del__(self):
    		self.driver.quit()

 

그 외 기타 오류 댓글, 오류발견 시 추가하겠음...

  • 오류들을 해결하기만하고 따로 적어두진 않았음..
  • 대부분은 환경에 맞게 argument 설정만 잘해줘도 해결되는 경우가 대부분
반응형