[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"))
로그인 및 입력, 파일 업로드
-
로그인과 텍스트 데이터를 입력하는 것은 동일한 과정으로 진행한다. 빈 창에 접근하여 원하는 정보를 전송하는 방식이다.
-
로그인을 예시로 들면, 로그인을 진행할 아이디창과 비밀번호창에 접근한다. 그 후
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
에 들어가는 값을 해당 파일의 경로로 설정하면 된다.driver.find_element_by_id('속성명').send_keys('파일의 경로')
-
참고자료
드롭다운(Dropdown)
- 드롭다운으로 표현된 정보의 경우 다음 코드를 사용하여 선택한다.
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)
-
체크박스는 클릭하는 방식과 동일하게 작동한다. 검사도구를 활용하여 몇 번째 박스인지 인덱스를 확인하여 클릭한다.
click_button = driver.find_element_by_id('속성'+str('인덱스')) click_button.click()
-
체크박스가 이미 선택되었는지를 확인하기 위해서는 다음의 코드를 확인한다. True일 경우 이미 선택되었음을 의미한다.
click_button.is_selected() # 참고: 클릭이 되었다는 것을 확인하고 싶을 경우 assert click_button.is_selected()
-
참고자료
팝업창 확인
-
내용을 작성하고 등록하거나 저장을 할 때 위와 같은 팝업창이 뜬다면 ‘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()
-
참고자료
검색창 제거
- 정보를 입력한 후 검색창 혹은 내용 작성칸을 지워야할 경우 (위의 예시에서 ‘안녕하세요’라는 글자를 지우고자 할 때) 다음 코드를 사용할 수 있다.
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:
- 사용자가 업로드하고자 하는 파일의 경로나 해당 페이지의 링크에 문제가 있다. 따라서 이 부분을 다시 확인한다.
- 결과적으로 셀레니움을 사용할 때는 몇 가지 기능만 알고 있어도 유용하게 사용할 수 있을 것이다.
click
,clear
,send_keys
,select
,scrollIntoView
등
- 기타 참고자료
댓글남기기