웹 브라우저 자동화 (TIL 9)

석형원·2024년 4월 4일

TIL

목록 보기
9/52

✏️ 오늘 학습한 내용

1. Selenium
2. Wait and Call
3. 이벤트 처리


🔎 Selenium

  • Selenium이란

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

  • 설치

# 셀레니움 설치
%pip install selenium

# 웹 브라우저를 제어할 수 있는 자동화 프레임 워크 : WebDriver
%pip install webdriver-manager
  • 시작
# selenium으로부터 webdriver 모듈을 불러옵니다.
from selenium import webdriver

# webdriver 안에 있는 chorme.service에 있는 Service 호출
# Chrome 객체 생성시 필요한 인자로 사용
from selenium.webdriver.chrome.service import Service

# webdriver_manager로 Chrome 버젼 연동
from webdriver_manager.chrome import ChromeDriverManager

# selenium을 사용하기 위해선 driver 객체를 만들어야함
# driver 객체는 특정 웹 브라우저에 종속되어 있음 ( 이 경우 Chrome )
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

# 만든 Chrome 브라우저에서 요청을 보냄
driver.get("http://www.example.com")

# 응답 받은 page_source : html 코드 확인
print(driver.page_source)

  • with-as

    with-as 구문을 통해 주어진 명령이 끝나면 driver를 종료하도록 설정 가능

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver :
    driver.get("http://www.example.com")
    print(driver.page_source)
  • Driver에서 특정 요소 추출

    7일차에서
    브라우저가 html코드를 parsing해서 DOM 모델로 만드는 것과
    DOM Tree를 순회해서 특정 원소를 조회할 수 있다는 것도 배웠음

    따라서 selenium은 이미 브라우저를 활용하고 있기에,
    받아온 응답으로부터 특정 요소를 추출할 수 있음을 알 수 있다.

# By를 import
from selenium.webdriver.common.by import By

# 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")
    # 다수이므로 list형태로 반환됨
    for element in driver.find_elements(By.TAG_NAME,"p") :
        print("Text:",element.text)
  • Driver의 요소 찾기 메소드

    • 요소 하나 찾기
      .find_element(by, target)
      by : 대상을 찾는 기준 : ID, TAG_NAME, CLASS_NAME, ...
      target : 대상의 속성
      e.g.) driver.find_element(By.TAG_NAME,"p")

    • 요소 여러 개 찾기
      .find_elements(by, target)
      by : 대상을 찾는 기준 : ID, TAG_NAME, CLASS_NAME, ...
      target : 대상의 속성
      e.g.) driver.find_elements(By.TAG_NAME,"p")


🔎 Wait and Call

  • Implicit Wait / Explicit Wait

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

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

    https://indistreet.com/live?sortOption=startDate%3AASC
    ( 관찰 결과 : 스크래핑 방지 목적으로 class 이름을 랜덤하게 생성하는 것을 확인 )
    => 위치를 활용한 스크래핑 진행

    • XPath

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

      • 개발자 도구를 통한 XPath 추출
    • 첫 시도

      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")
      # 위에서 복사한 XPath를 경로에 넣어줌
      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

      # 주의사항 : 반드시 해당 시간을 기다리는 것이 아니라, 로딩이 다 될때까지의 한계 시간을 의미 
      
      # selenium의 WebDriverWait을 호출
      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")
      
          # 요청이 완벽하게 응답이 되면 다음 것을 실행
          # 혹은 10초 간 기다림
          # => 정리하면 일단 10초 간 기다리지만, 완벽한 응답이 왔다 싶으면 다음으로 넘어감
          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()과 두 메서드를 활용해서 Explicit Wait을 적용할 수 있습니다.

      1. until(): 인자의 조건이 만족될 때까지
      2. until_not() 인자의 조건이 만족되지 않을 때까지

      e.g.)

      # id가 `target`인 요소가 존재할 때까지 10초 간 기다린 후 다음 명령을 진행
      element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "target")))
    • EC

      • expected_conditions
      • selenium에서 정의된 조건
    • EC.presence_of_element_located()

      • ( ) 안의 조건에 해당하는 원소의 위치가 존재하는 경우
      # Explicit Wait
      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")
          # Explicit Wait
          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개의 이름 추출
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)
    
    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)
    

🔎 이벤트 처리

  • 목표

    해시코드 로그인 과정을 자동화하는 것

  • 목표에 대한 의미

    1. 최근 서비스는 간편 로그인 기능을 제공하는 경우가 많음

      간편 로그인 특성상,
      특정 플랫폼에서 로그인을 요청하는 경우가 많다.
      이를 편리하게 진행하기 위해선 Web Browser 상에서,
      즉, Selenium 상에서 진행하는 것이 간편하다.

    2. 왜 굳이 Selenium 로그인까지 해야하나?

      로그인을 해야 Scraping이 가능한 웹 사이트도 있기 때문.

  • Event

    웹 페이지에서 일어나는 일들을 뜻함

  • Mouse Event

    • 마우스 입력의 과정

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

      e.g.)

      # id가 button인 요소를 호출
      button = driver.find_element(By.ID, "button")
      
      # ActionChain : Action에 Chain을 건다.
      # 여러 Action을 연쇄적으로 수행할 수 있도록 하는 것
      # 예를 들어, ctrl-c 같은 것을 가능하게 해주는 것
      ActionChains(driver).click(button).perform()
    • 로그인 버튼 누르기

    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://hashcode.co.kr/")
    driver.implicitly_wait(0.5)
    
    # 로그인 버튼을 실행 시킴
    button = driver.find_element(By.CLASS_NAME, "nav-link.nav-signin")
    ActionChains(driver).click(button).perform()
  • Keyboard Event

    • 키보드 입력 과정

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

      e.g.)

       text_input = driver.find_element(By.ID, "textInput")
      ActionChains(driver).send_keys_to_element(text_input, "abc").perform()
    • 로그인 창에서 ID, PW 입력하고 로그인하기

    # driver를 이용해 해당 사이트에 요청을 보내봅시다.
    import time
    
    # 웹 사이트 접속
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
    driver.get("https://hashcode.co.kr/")
    time.sleep(1)
    
    # 메뉴를 실행 시킴
    button = driver.find_element(By.XPATH, '//*[@id="main-app-header"]/header/section/div/div/button')
    ActionChains(driver).click(button).perform()
    time.sleep(1)
    
    # 로그인 버튼을 실행 시킴
    button = driver.find_element(By.XPATH, '//*[@id="main-app-header"]/header/div[5]/div/section/div[1]/a[2]')
    ActionChains(driver).click(button).perform()
    time.sleep(1)
    
    # "아이디" input 요소에 여러분의 아이디를 입력합니다.
    id_input = driver.find_element(By.ID,"user_email")
    ActionChains(driver).send_keys_to_element(id_input, "아이디").perform()
    time.sleep(1)
    
    # "패스워드" input 요소에 여러분의 비밀번호를 입력합니다.
    pw_input = driver.find_element(By.ID,"user_password")
    ActionChains(driver).send_keys_to_element(pw_input, "password").perform()
    time.sleep(1)
    
    # "로그인" 버튼을 눌러서 로그인을 완료합니다.
    button = driver.find_element(By.ID,"btn-sign-in")
    ActionChains(driver).click(button).perform()
    
profile
데이터 엔지니어를 꿈꾸는 거북이, 한걸음 한걸음

0개의 댓글