1. 정적 vs. 동적 웹사이트

  • 어떻게 생성되냐에 따라 구분

  • 정적(static) 웹사이트
    • HTML 내용이 고정됨
    • 같은 주소로 요청을 보내면 항상 같은 응답을 받을 수 있음
    • 웹 스크래퍼 적용이 쉬움
    • HTML 문서가 완전하게 응답됨
      • 파싱을 해도 문제가 없음
  • 동적(dynamic) 웹사이트
    • HTML 내용이 변함
    • ex) 인스타그램
      • 새로고침 할 때마다 자동으로 서버가 피드의 내용을 업데이트함
      • 페이지가 온 다음에야 요청이 실행됨
    • 응답 후 HTML이 렌더링 될 때까지의 지연시간이 존재!
      • 바로 파싱을 하면 안될 수도 있음, HTML 구조가 바뀔 수 있음

2. 동적 웹사이트의 동작 방식

  • 웹 브라우저에서는 JavaScript가 동작
  • 비동기 처리를 통해서 필요한 데이터를 채움
    • 동기 처리: 요청에 따른 응답을 기다린다
      • 렌더링 - 끝 -> 데이터 처리
      • 렌더링을 다 해야만 데이터 처리를 시작
    • 비동기 처리: 요청에 따른 응답을 기다리지 않음
      • 렌더링을 시켜놓고, 실행되는 와중에 데이터 처리를 시킬 수 있음
  • 동기 처리된 경우, HTML 로딩에 문제가 없음
  • 비동기 처리된 경우, 상황에 따라서 데이터가 완전하지 않은 경우가 발생함!

3. requests로 요청 시 발생하는 문제점 및 해결

스크린샷 2023-10-25 오후 5 44 01

  • 데이터 처리가 끝나지 않은 위 상황에서 요청을 보내면 불완전한 응답을 받게 됨
    • 임의로 시간을 지연한 후, 데이터 처리가 끝난 후 정보를 가져오면 됨
  • 키보드 입력, 마우스 클릭 등을 requests로는 진행하기 어려움
    • 키보드 입력, 마우스 클릭 등 UI Action(event)을 프로그래밍 할 수 없을까?
    • 웹 브라우저를 파이썬으로 조작!
    • Selenium
  • 응답 후 시간 지연
from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("http://www.example.com")
  • UI와의 상호 작용
from selenium import webdriver

elem = driver.find_element_by_tag_name("hello-input")
elem.send_keys("Hello!")

4. Selenium으로 브라우저 자동화하기

브라우저 열기

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# Chrome 객체 생성
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

# 요청 보내기
driver.get("http://www.example.com")

# 페이지 속성 확인
print(driver.page_source)

명령이 끝나면 자동으로 driver 종료 with-as

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

특정 요소 추출

  • .find_element(by, target)
    • 요소 한 개 찾기
    • by : 대상을 찾는 기준
      • ID, TAG_NAME, CLASS_NAME, XPATH, ..
    • target : 대상의 속성
  • .find_elements(by, target)
    • 요소 여러 개 찾기
    • by : 대상을 찾는 기준
      • ID, TAG_NAME, CLASS_NAME, XPATH, ..
    • target : 대상의 속성
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")
    for element in driver.find_elements(By.TAG_NAME, "p"):
        print("Text: ", element.text)

XPATH

  • 요즘 웹 브라우저는 스크래핑을 방지할 목적으로 class 명을 랜덤하게 생성함
  • XPATH
    • XML, HTML 문서 등의 요소의 위치를 경로로 표현하는 것
    • ex) 데스크탑/폴더1/폴더2/음악.mp3
  • 원하는 요소의 HTML 태그에서 (요소 검사 누르고) [copy] - [copy XPATH]

wait and call

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

Implicit Wait

  • 특정 요소에 대한 제약을 통한 기다림
  • e.g. 이 태그를 가져올 수 있을 때까지 기다려
  • .implicitly_wait(time)
    • time은 반드시 이 시간을 기다리는 것이 아니라, 로딩이 다 될 때까지의 한계 시간을 의미
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)

Knock Live Rock band

Emplicit Wait

  • 다 로딩이 될 때까지 지정한 시간 동안 기다림
  • e.g. 다 로딩이 될 때까지 5초 동안 기다려

  • WebDriverWait()
    • until() : 인자의 조건이 만족될 때까지 기다림
    • until_not() : 인자의 조건이 만족되지 않을 때까지 기다림
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)

Knock Live Rock band


여러 공연의 제목 가져오기

  • XPATH의 일반화
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):
        element = 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))
        print(element.text)

Knock Live Rock band
Sign up
이디어츠 1st EP 발매기념 공연
도라페스트 4탄
NO PASARAN! Vol.1
LIVE in NOV 2019
LIVE in DEC 2019
오롯한 라이브와 함께
LIVE in FEB
2020 PUNK Marathon

마우스 이벤트 처리

https://hashcode.co.kr/

  • 해시코드 “로그인” 창 접속하기
  • 로그인을 자동화하는 이유
    • 로그인을 해야만 스크래핑이 가능한 웹 사이트들이 있기 때문!


Mouse Event

1) 입력하고자 하는 대상 요소 찾기 (.find_element())
2) 입력하고자 하는 내용을 click 을 통해 전달
3) .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.XPATH, '//*[@id="main-app-header"]/header/section/div/div/div/a[1]')
ActionChains(driver).click(button).perform()

스크린샷 2023-10-26 오후 2 35 41
로그인 창 이동 성공!

키보드 이벤트 처리하기

Keyboard Event

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

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(1)

# 내비게이션 바에서 "로그인" 버튼을 찾아 누르기

button = driver.find_element(By.XPATH, '//*[@id="main-app-header"]/header/section/div/div/div/a[1]')
ActionChains(driver).click(button).perform()
time.sleep(1)

# "아이디" input 요소에 아이디 입력

id_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(id_input, "아이디").perform()
time.sleep(1)

# "패스워드" input 요소에 비밀번호 입력

pwd_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(pwd_input, "비밀번호").perform()
time.sleep(1)

# "로그인" 버튼을 눌러 로그인 완료

login_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(login_button).perform()

스크린샷 2023-10-26 오후 2 39 15

스크린샷 2023-10-26 오후 2 39 28

자동으로 입력 후, 로그인 성공!