[프로그래머스] 데브코스 데이터엔지니어링 TIL Day 9

주재민·2023년 10월 26일
0
post-thumbnail

📖 학습주제

파이썬으로 웹데이터 크롤하고 분석하기 (4)


Selenium

Python을 이용해서 웹 브라우저를 조작할 수 있는 자동화 프레임워크

실습

  • Webdriver : 웹 브라우저를 제어할 수 있는 자동화 프레임워크, 드라이버 객체는 특정 브라우저에 종속됨

웹 브라우저와 연동하기 위해서는 Webdriver가 필요하다. 실습에서는 Chrome을 사용하였다.

실습에 이용된 사이트는 http://www.example.com 이다.

from selenium import webdriver 
from selenium.webdriver.chrome.service import Service 
from webdriver_manager.chrome import ChromeDriverManager # 현재 사용하고 있는 크롬의 버전과 동일한 버전을 싱크

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

위의 코드를 실행하면 다음과같이 크롬 창이 새로 열린다.

셀레니움도 requests처럼 .get()을 통해 요청을 보낼 수 있다.

driver.get('http://www.example.com')

아까 열린 크롬 창이 아래와 같이 바뀐 것을 볼 수 있다.

page_source 속성을 통해 Response의 HTML 문서도 확인할 수 있다.

print(driver.page_source)

그런데 창을 닫으라는 명렁을 하지 않아 창이 계속 열려있다. with-as 구문을 통해 주어진 명령이 끝나면 driver를 종료하도록 설정할 수 있다.

위의 코드를 이용해서 작성해보자.

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    driver.get('http://www.example.com')
    print(driver.page_source)

위의 주어진 명령이 끝나면 창이 닫아진다.

find_element, find_elements 를 이용해 특정 요소를 찾을 수 있다.

  • 요소 하나 찾기 : .find_element(by, target)
  • 요소 여러개 찾기 : .find_elements(by, target)

by : 대상을 찾는 기준 : ID, TAG_NAME, CLASS_NAME, ...
target : 대상의 속성

<p> 태그에 해당하는 요소 하나를 찾아보자

from selenium.webdriver.common.by import By # by 이용을 위해

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> 태그에 해당하는 요소 여러개를 찾으려면 find_elements를 쓰면 된다. 단, 여러개를 찾을 때 결과값이 리스트이기 때문에 .text를 쓸 수 없으니 코드를 조정해줘야 한다.

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(element.text)

Wait and Call

앞 선 강의에서 동적 웹페이지에 대해 배웠다. 동적 웹페이지의 경우 비동기 처리 때문에 데이터가 로딩되기 전에 find를 진행해 찾고자 하는 element가 없다고 처리되는 경우가 있다. 이 때, 이 페이지를 성공적으로 스크래핑하기 위해서는 Wait을 사용해야한다.

Selenium은 동적 웹 사이트에 대한 지원을 진행하기 위해 명시적 기다림(Explicit Wait)과 암묵적 기다림(Implicit Wait)을 지원한다.

  • Explicit Wait: 다 로딩이 될 때까지 지정한 시간 동안 기다림 ( e.g.)다 로딩이 될 때까지 5초동안 기다려!)
  • Implicit Wait: 특정 요소에 대한 제약을 통한 기다림 ( e.g.) 이 태그를 가져올 수 있을 때까지 기다려!)

XPATH

XML, HTML 문서 등의 요소의 위치를 경로로 표현하는 것

스크래핑을 방지할 목적으로 랜덤하게 class 이름을 생성하는 사이트들이 있는데 이 경우 요소의 변하지 않는 위치를 이용한 XPATH로 스크래핑을 진행 할 수 있다.

다음 사이트에 있는 행사의 이름을 스크래핑 해보려고 한다.
이 사이트는 앞서 설명한 class 이름을 생성하는 사이트 중 하나이다.

https://indistreet.com/live?sortOption=startDate%3AASC

from selenium import webdriver
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://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]').text

이 사이트는 동적 웹 사이트 이기 때문에 위의 코드대로 하면 이와 같이 에러가 난다. 이를 앞서 설명한 wait를 이용해서 스크래핑해보자.

Explicit Wait

.implicitly_wait()을 활용해서 암시적 기다림을 적용할 수 있다.

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() : 인자의 조건이 만족되지 않을 때까지
  • EC : expected_conditions로, selenium에서 정의된 조건

자세한 내용
https://www.selenium.dev/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

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)

마우스/키보드 이벤트 처리

웹 페이지에서 일어나는 일들을 Event라고 한다.

Selenium으로 이런 이벤트를 처리할 수 있다. 다음 사이트에 로그인해보자.
https://hashcode.co.kr/

마우스 입력은 크게 다음과 같은 과정을 거친다.

  1. 입력하고자 하는 대상 요소를 찾는다. (find_element() 이용)
  2. 입력하고자 하는 내용을 click을 통해 전달
  3. .perform()을 통해 동작

키보드 입력은 크게 다음과 같은 과정을 거친다.

  1. 입력하고자 하는 대상 요소를 찾는다. (find_element() 이용)
  2. 입력하고자 하는 내용을 send_keys_to_element를 통해 전달
  3. .perform()을 통해 동작

확인해보니 위 사이트에서 네비게이션 바의 로그인 버튼은 UtilMenustyle__Link-sc-2sjysx-4 ewJwEL클래스, 아이디 입력창은 FymRFM681OjzOdzor5nk클래스, 로그인 버튼은 itAWTII94uCyf9uUgREi클래스를 통해 접근 가능함을 확인하였다. 패스워드 입력창은 아이디 입력창과 같은 클래스를 가지기에 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 = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://hashcode.co.kr/')
time.sleep(5)

# 내비게이션 바에서 "로그인" 버튼을 찾아 누름
button = driver.find_element(By.CLASS_NAME, 'UtilMenustyle__Link-sc-2sjysx-4.ewJwEL')
ActionChains(driver).click(button).perform()
time.sleep(5)


# 아이디를 입력
id_input = driver.find_element(By.CLASS_NAME, "FymRFM681OjzOdzor5nk")
ActionChains(driver).send_keys_to_element(id_input, "id").perform()
time.sleep(5)


# 비밀번호를 입력
pw_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(pw_input, "pw").perform()
time.sleep(5)


# 로그인 버튼을 눌러서 로그인을 완료
login = driver.find_element(By.CLASS_NAME, 'itAWTII94uCyf9uUgREi')
ActionChains(driver).click(login).perform()
time.sleep(5)

정상적으로 작동함을 확인 할 수 있다. 임의로 아이디를 id, 패스워드를 pw로 했으니 당연하게 실패한다 올바른 아이디, 패스워드를 입력하면 로그인이 될 것이다.

0개의 댓글