브라우저 자동화하기, Selenium
Selenium 라이브러리
Selenium은 Python을 이용해 웹 브라우저를 조작할 수 있는 자동화 프레임워크이다.
Selenium, Web Driver 설치
Selenium 프레임워크와 웹 브라우저와 연동하기 위한 WebDriver를 설치한다. WebDriver는 웹 브라우저를 제어할 수 있는 자동화 프레임워크로, 이 실습에서는 Chrome을 기준으로 진행한다.
%pip install selenium
%pip install webdriver-manager
WebDriver의 Chrome() 객체 생성
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
웹 페이지의 HTML 출력
응답을 받은 후, page_source 속성을 통해 응답의 HTML 문서를 확인할 수 있다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("http://www.example.com")
print(driver.page_source)
with-as 구문
프로그램들을 실행하면, Chrome 창이 계속 켜져있게 된다. 이는 우리가 창을 종료하라는 명령을 내린 적이 없기 때문이다. with-as 구문을 통해 주어진 명령이 끝나면 driver를 종료하도록 설정할 수 있다.
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("http://www.example.com")
print(driver.page_source)
Driver에서 특정 요소 추출하기
응답을 가지고 있는 driver에 대해 다음과 같은 메서드를 적용할 수 있다.
- 요소 하나 찾기 : find_element(by, target)
- 요소 여러개 찾기 : find_elements(by, target)
- by(대상을 찾는 기준) : ID, TAG_NAME, CLASS_NAME, ...
- target(대상의 속성) : ID 명, 태그 이름, CLASS 이름, ...
p 태그 요소 하나 찾기
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("http://www.example.com")
print(driver.find_element(By.TAG_NAME, "p").text)
p 태그 요소 여러 개 찾기
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("http://www.example.com")
for element in driver.find_elements(By.TAG_NAME, "p"):
print("text:", element.text)
Wait and call
Selenium은 동적 웹 사이트에 대한 지원을 진행하기 위해 명시적 기다림(Explicit Wait)과 암묵적 기다림(Implicit Wait)을 지원한다.
- Implicit Wait : 로딩이 다 될 때까지 지정한 시간 동안 기다림, ex) 로딩이 다 될 때까지 5초 동안 기다려!
- Explicit Wait : 특정 요소에 대한 제약을 통한 기다림, ex) 이 태그를 가져올 수 있을 때까지 기다려!
IndiStreet 이벤트 스크래핑
https://indistreet.com/live?sortOption=startDate%3AASC << 실습을 진행할 웹 사이트
해당 사이트에 존재하는 행사의 이름들을 스크래핑하는 실습을 진행한다.
XPath
사이트를 확인해 보니 class 이름이 특이하다. 이는 스크래핑을 방지할 목적으로 랜덤 하게 class 이름을 생성하기 때문이다. 이러한 경우 쓸 수 있는 방법이 여러 가지 있는데, 그중 하나인 위치를 활용한 방법을 알아보도록 한다.
Xpath는 XML, HTML 문서 등의 요소의 위치를 경로로 표현하는 것을 의미한다. 예를 들어 Desktop/folder1/folder2/music과 같은 표현이다. 이를 적용해서 데이터를 가져와보자!
아래와 같이 개발자 도구를 열어 Xpath를 알고 싶은 코드를 우클릭하고, Copy -> Copy Xpath를 통해 확인이 가능하다.
"//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[1]/div/a/div[2]/p[1]"가 복사된 것을 확인할 수 있다. 여기서 인덱스는 해당 태그 안의 몇 번째인지 구분한다.
# 스크래핑에 필요한 라이브러리를 불러와봅시다.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
이 웹 페이지는 동적 웹 페이지이기 때문에 get을 통해 요청을 한 이후, 데이터가 로딩되기 전에 find_element를 실행하게 되어 에러가 발생한다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
driver.find_element(By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[1]/div/a/div[2]/p[1]')
암시적 기다림 - Implicit Wait
Implicit Wait를 진행해서 10초까지 기다리는 작업을 추가해 준다. 여기서 10초 동안 기다린다는 것이 아닌 만약에 10초 이내에 렌더링이 끝나면, 기다리는 것을 멈춘다.
from selenium.webdriver.support.ui import WebDriverWait
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
driver.implicitly_wait(10)
print(driver.find_element(By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[1]/div/a/div[2]/p[1]').text)
명시적 기다림 - Explicit Wait
WebDriverWait()과 두 메서드를 활용해 명시적 기다림을 적용할 수 있다.
- until() : 인자의 조건이 만족될 때까지
- until_not() : 인자의 조건이 만족되지 않을 때까지
예를 들어 다음 코드는 id가 target인 요소가 존재할 때까지 기다린 후 다음 명령을 진행한다. Implicit Wait와 다르게 Explicit Wait의 특징으로 특정 요소를 바로 활용하는 방법으로 진행할 수도 있다.
from selenium.webdriver.support import expected_conditions as EC
element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "target")))
아래의 코드는 Explicit Wait를 적용한 것이다.
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[1]/div/a/div[2]/p[1]')))
print(element.text)
암시적/명시적 기다림을 이용한 10개 제목 추출
# Implicit Wait
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
driver.implicitly_wait(10)
for i in range(1, 11):
print(driver.find_element(By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[{}]/div/a/div[2]/p[1]'.format(i)).text)
# Explicit Wait
with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
driver.get("https://indistreet.com/live?sortOption=startDate%3AASC")
for i in range(1, 11):
element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[{}]/div/a/div[2]/p[1]'.format(i))))
print(element.text)
마우스 이벤트 처리하기
https://hashcode.co.kr/ << 실습을 진행할 웹 사이트
이번 실습에서는 로그인 과정을 자동화하는 것을 목표로 한다.
마우스 이벤트
마우스로 일어날 수 있는 대표적인 이벤트는 다음과 같다.
- 마우스 움직이기(move)
- 마우스 누르기(press down)
- 마우스 떼기(press up)
- ...
아래 사이트에서 더 많은 이벤트 종류를 확인할 수 있다.
마우스 입력 과정
- 입력하고자 하는 대상 요소를 찾음, find_element() 이용
- 입력하고자 하는 내용을 click을 통해 전달
- perform()을 통해 동작
아래 예시는 button인 요소를 클릭하는 예제이다.
button = driver.find_element(By.ID, "button")
ActionChains(driver).click(button).perform()
웹 사이트에 마우스 이벤트 적용
강의 영상에서는 find_element를 CLASS_NAME으로 진행했지만, class 이름 형식이 강의와 달라 XPATH를 활용하였다.
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("https://qna.programmers.co.kr/")
driver.implicitly_wait(0.5)
button = driver.find_element(By.XPATH, '//*[@id="main-app-header"]/header/section/div/div/div/a[1]')
ActionChains(driver).click(button).perform()
키보드 이벤트 처리하기
https://hashcode.co.kr/ << 실습을 진행할 웹 사이트
키보드 이벤트
키보드로 일어날 수 있는 대표적인 이벤트는 다음과 같다.
- 키보드 누르기(press down)
- 키보드 떼기(press up)
- ...
키보드 입력 과정
입력 과정은 마우스 입력 과정과 크게 다르지 않다.
- 입력하고자 하는 대상 요소를 찾음, find_element() 이용
- 입력하고자 하는 내용을 send_keys_to_element를 통해 전달
- perform()을 통해 동작
아래 예시는 id가 textInput인 요소에 "abc"를 입력하는 예제이다.
text_input = driver.find_element(By.ID, "textInput")
ActionChains(driver).send_keys_to_element(text_input, "abc").perform()
웹 사이트에 마우스 및 키보드 이벤트 적용
강의 영상에서는 find_element를 ID로 진행했지만, id 이름 형식이 강의와 달라 XPATH를 활용하였다. 또한 이벤트가 너무 빠르게 진행되면, 정상적으로 작동하지 않을 수 있기 때문에 딜레이를 주도록 한다.
from selenium import webdriver
from selenium.webdriver import ActionChains
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver import Keys, ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import time
# driver를 이용해 해당 사이트에 요청을 보내봅시다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("https://hashcode.co.kr")
time.sleep(0.5)
# 내비게이션 바에서 "로그인" 버튼을 찾아 눌러봅시다.
button = driver.find_element(By.XPATH,'//*[@id="main-app-header"]/header/section/div/div/div/a[1]')
ActionChains(driver).click(button).perform()
time.sleep(0.5)
# "아이디" input 요소에 여러분의 아이디를 입력합니다.
text_input = driver.find_element(By.XPATH, '//*[@id="main-app-account"]/div/div[2]/div/div[2]/div[1]/div/div[2]/div[2]/input')
ActionChains(driver).send_keys_to_element(text_input, "id").perform()
time.sleep(0.5)
# "패스워드" input 요소에 여러분의 비밀번호를 입력합니다.
text_input = driver.find_element(By.XPATH, '//*[@id="main-app-account"]/div/div[2]/div/div[2]/div[1]/div/div[2]/div[4]/input')
ActionChains(driver).send_keys_to_element(text_input, "password").perform()
time.sleep(0.5)
# "로그인" 버튼을 눌러서 로그인을 완료합니다.
button = driver.find_element(By.XPATH, '//*[@id="main-app-account"]/div/div[2]/div/div[2]/div[1]/div/div[2]/button')
ActionChains(driver).click(button).perform()
결론
느낀 점
웹을 직접 열어 조작해 보는 경험이 처음이라 신기하고 재미있었다. 그렇지만 새로운 라이브러리가 쏟아져내리고, 메서드도 매우 많아 사용하기 위해서는 이 페이지를 다시 열어봐야 할 것 같다.
어려웠던 점
지금까지 실습을 Colab으로 진행해 왔는데, Selenium 라이브러리를 사용하면서 Service() 메서드에서 자꾸 오류가 발생했다. 해당 문제를 해결하려고 검색을 동원했지만.. 해결하지 못했다. 그래도 Jupyter Lab 환경에서는 정상적으로 작동해서 환경을 옮겨 진행하였다.
'[프로그래머스] 데이터 엔지니어링 데브코스 3기 > TIL(Today I Learn)' 카테고리의 다른 글
[TIL - 11일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (1) (0) | 2024.04.08 |
---|---|
[TIL - 10일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (5) (0) | 2024.04.05 |
[TIL - 8일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (3) (0) | 2024.04.03 |
[TIL - 7일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (2) (0) | 2024.04.02 |
[TIL - 6일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (1) (2) | 2024.04.01 |