[프로그래머스 인공지능 미니 데브코스] 수업 정리 -7- [웹 스크래핑 기초 III]

3yeong·2022년 7월 27일
0

1. 동적 웹 페이지와의 만남

정적 웹 사이트와 동적 웹 사이트

웹 페이지는 어떻게 생성되냐에 따라 크게 2가지로 구분
1. HTML 내용이 고정된 정적(static) 웹 사이트
-> ex) 변경이 없는 사이트
-> 같은 주소로 보내면 항상 같은 값을 받음
-> HTML 문서가 완전하게 응답
2. HTML 내용이 변하는 동적(dynamic) 웹사이트
-> ex) 인스타그램
-> 페이지가 온 다음 실행됨
-> 응답 후 HTML이 렌더링이 될 때까지의 지연시간존재

동적 웹 사이트의 동작 방식

웹 브라우저에선 JavaScript라는 프로그래밍 언어가 동작한다.
비동기 처리를 통해 필요한 데이터를 채운다.
-> 동기처리 : 요청에 따른 응답을 기다린다
-> 동기 처리된 경우, HTML 로딩에 문제가 없다.

-> 비동기처리 : 요청에 따른 응답을 기다리지 않는다
-> 상황에 따라서 데이터가 완전하지 않은 경우가 발생한다.

지금까지의 스크래퍼의 문제점

requests로 요청 시 발생하는 문제점

  1. 동적 웹 사이트에 적용이 어려움
    -> 요청을 보내면 불완전한 응답을 받게 된다.
  2. UI 상호작용의 어려움
    -> 키보드 입력, 마우스 클릭 등을 requests로는 진행하기 어렵다.

이를 해결하려면

  1. 임의로 시간을 지연한 후, 데이터 처리가 끝난 후 정보를 가져오면 된다.
  2. UI Action을 프로그래밍을 통해 명령을 내린다.
    -> 웹 브라우저를 파이썬으로 조작
    -> 웹 브라우저를 자동화하는 라이브러리 Selenium
    -> 응답 후 시간을 지연시킬 수 있다.
    -> UI와의 상호작용이 가능하다.

동적 웹사이트는 응답 후 바로 정보를 추출하기 어렵다.
또한, 다양한 키보드 입력과 마우스 클릭 등의 상호작용이 존재한다.
이런 상황을 해결하기 위해, 웹 브라우저를 파이썬으로 조작하는 전략을 취하자.

2. 브라우저 자동화하기, Selenium

Selenium 설치 & 사용하기

Selenium 라이브러리

selenium은 Python을 이용해서 웹 브라우저를 조작할 수 잇는 자동화 프레임워크이다.
이를 사용하기 위해서 selenium 프레임워크를 설치해야한다.
pip install을 통해 설치할 수 있다.

pip install selenium

Web Driver

  • 웹 브라우저와 연동을 위해서는 WebDriver가 필요하다
    WebDriver는 웹 브라우저를 제어할 수 있는 프레임워크이다.
    pip install을 통해 webdriver를 관리하는 라이브러리 webdriver-manager를 설치한다.

    pip install webdriver-manager

설치가 완료되면 Webdriver를 사용할 수 잇다.
컴퓨터에 Chrome이 깔려있어야 한다.

Selenium 시작하기

Selenium을 사용하기 위해 우선 Selenium을 불러와야한다.

# selenium으로 부터 webdriver 모듈 불러오기

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

불러온 모듈 webdriver에서 Chrome()객체를 생성한다.

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

크롬이 실행된다. 명령어를 통해 파이썬과 연동한 것이다.

.get(url)을 활용하여 요청을 보낸다.

#http://www.example.com으로 요청 보내기
driver = webdriver.Chrome(service = Service(ChromeDriverManager().install()))
driver.get('http://www.example.com')


응답을 받은 후, page_source속성을 통해 Response의 HTML 문서를 확인할 수 있다.

#page_source 속성 확인
driver = webdriver.Chrome(service = Service(ChromeDriverManager().install()))
driver.get('http://www.example.com')
print(driver.page_source)


프로그램들을 실행하다보면 Chrome 창이 계속 켜져 있는다. 이는 해당 창을 끄라고 명령을 한 적이 없기 때문이다.
with-as 구문을 통해 명령이 끝나면 driver를 종료하도록 설정할 수 있다.

#with-as를 사용하기
with webdriver.Chrome(service = Service(ChromeDriverManager().install())) as driver:
    driver.get('http://www.example.com')
    print(driver.page_source)

Driver에서 특정 요소 추출하기

selenium은 받아온 응답으로붜 특정 요소를 추출할 수 있다.

응답을 가지고 있는 dirver/ 요소에 대해 다음 메서드를 적용할 수 있다.
요소 하나 찾기

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

3. Wait and Call

인디스트릿 - 행사 리스트 가져오기

Implicit / Explicit Wait

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

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

Target: IndieStreet 이벤트 스크래핑

다음 사이트에 있는 행사의 이름들을 스크래핑 하기 : 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

XPath?

스크래핑 방지 목적으로 랜덤하게 class 이름을 생성한다.

이러한 경우 쓸 수 있는 방법이 여러가지 있는데,
그 중 한 가지 방법이, 위치를 활용한 방법이다.

XPath는 XMAL, HTML 문서 요소의 위치를 경로로 표현하는 것을 의미한다.
데스크탑/폴더1/폴더2/음악.mp3 와 비슷하다.

이를 적용하여 데이터를 가져오자.

  • hint: XPath는 //*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div/a/div[2]/p[1] 이다.
# 예시 사이트에 요청을 진행하고, 예시 사이트의 첫 번째 이벤트 제목 가져오기
# 스크래핑 원하는 위치에서 우클릭, copy, copy Xpath

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를 사용해야한다.

2가지 Wait 방법을 활용해보려한다.

Implicit Wait

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

  • 주의 : 반드시 해당 시간을 기다리는 것이 아니라, 로딩이 다 될 때까지 한계 시간의 의미를 가진다.
# 10초 동안 Implicit Wait 를 진행하도록 해서  스크래핑이 이루어지도록.

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) #요청이 완벽하게 오거나 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인 요소가 존재할 때까지 기다린 후 다음 명령을 진행한다.

element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID,"target")))

이때, EC 는 expenced_conditions로 , selenium에서 정의된 조건이다.(~가 존재하면)

주어진 XPath가 등장할 때까지 Wait 진행하기 EC.presence_of_element_located()활용

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)

여러 공연 제목 가져오기
여러 공연의 제목들을 가져오기 위해 XPath 관찰하기
//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[1]/div/a/div[2]/p[1]
//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[2]/div/a/div[2]/p[1]
//*[@id="__next"]/div/main/div[2]/div/div[4]/div[1]/div[3]/div/a/div[2]/p[1]

# 10개의 이름을 스크래핑하는 코드 작성
#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)
        
#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):
        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)

4. 마우스 이벤트 처리하기

해시코드 로그인 창 진입하기

Target: 해시코드 "로그인" 창 접소하기

로그인 과정을 자동화 하는 것을 목표
다음 사이트에 로그인 하기 위해 "로그인" 버튼 눌러보기 : https://hashcode.co.kr/

Mouse Event

웹 페이지에서 일어나는 일을 Event라고 한다.
마우스로 일어날 수 있는 대표적인 이벤트는 다음과 같다.

  • 마우스 움직이기(move)

  • 마우스 누르기(press down)

  • 마우스 떼기(press up)

  • ...

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

  1. 입력하고자 하는 대상 요소를 찾는다.(find_element()이용)

  2. 입력하고자 하는 내용을 click을 통해 전달한다.

  3. .perform()을 통해 동작한다.

    id가 button인 요소를 클릭하는 예시이다.

button = driver.find_element(By.ID,"button")
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()

5. 키보드 이벤트 처리하기

해시코드 로그인 창 진입하기

Keyboard Event

웹 페이지에서 일어나는 일을 Event라고 한다.
키보드로 일어날 수 있는 대표적인 이벤트는 다음과 같다.

  • 키보드 누르기(press down)

  • 키보드 떼기(press up)

  • ...

    입력창(input 태그의 form)에 키보드 입력을 진행하는 것을 목표로 한다.
    키보드 입력은 다음과 같은 과정을 거친다.

  1. 입력하고자 하는 대상 요소를 찾는다.(find_element()이용)

  2. 입력하고자 하는 내용을 send_keys_to_element를 통해 전달한다.

  3. .perform()을 통해 동작한다.

    id가 textInput인 요소에 "abc"를 입력하는 예시이다.

text_input = driver.find_element(By.ID, "textInput")
ActionChains(driver).send_keys_to_element(text_input, "abc").perform()

코드 짜보기

# 스크래핑에 필요한 라이브러리 불러오기
from selenium import webdriver 
from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager

import time

driver = webdriver.Chrome(service = Service(ChromeDriverManager().install()))
driver.get("https://www.naver.com")
time.sleep(0.5)

#내비게이션 바에서 "로그인" 버튼 찾아 누르기
button = driver.find_element(By.CLASS_NAME, "link_login")
ActionChains(driver).click(button).perform()
time.sleep(0.5)

#"아이디"  input 요소에 아이디 입력
id_input = driver.find_element(By.ID,"id")
ActionChains(driver).send_keys_to_element(id_input,"hello").perform()
time.sleep(1)

#"패스워드" input 요소에 비밀번호 입력
pw_input = driver.find_element(By.ID,"pw")
ActionChains(driver).send_keys_to_element(id_input,"hello").perform()
time.sleep(1)

#"로그인" 버튼 눌러서 로그인 완료
login_button = driver.find_element(By.ID, "log.login")
ActionChains(driver).click(login_button).perform()
time.sleep(0.5)
profile
초보 컴공

0개의 댓글