[BeautifulSoup&Selenium] 웹사이트 제어하기 (2)

박경민·2023년 1월 27일
0

[DataScience]

목록 보기
14/16

06 Selenium Wait

Wait 을 쓰는 이유는 웹 페이지의 로드 가 다 수행되지 않은 상태에서 css 선택 코드가 작동하면 오류를 일으키기 때문에 로딩이 완료될 때까지의 시간을 일부러 주는 것이다.

implicitly wait : 웹 요소가 로딩될 때까지 시간을 주는 것. 주로 웹 사이트 주소와 find_element 사이에서 동작한다. 주소를 주고, 해당 주소에서 css 요소를 찾는 데 까지 주는 시간을 말한다.

그런데 중요한 것은 요소를 찾앗음에도 오류가 날 수 있는데, (로그인 버튼 같은 것이 클릭할 수 없는 상태에) 메서드가 실행 불가능한 상태일 경우 그렇다. 예컨대

driver.find_element_by_css_selector('a.top-nav__login-link').click()

위 코드의 class 를 잘 찾았음에도 클릭이 불가능한 상태라면 오류가 난다.

이러한 문제를 해결하기 위한 것이 time.sleep 이다.

time.sleep() : 전달된 초만큼 코드를 멈추는 것.

implicitly wait 을 먼저 실행해보고 나서 추가적으로 오류가 나는 곳에 time.sleep 코드를 써주면 된다. 보통 웹 페이지의 코드가 로드되거나 바뀌는 부분이다.


07 Selenium Explicitly Wait

Explicitly wait 을 구현하기 위해서는 임포트를 몇 개 해줘야 한다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

역시나 하고자 하는 목표는 링크나 버튼 같은 것이 존재하지만 클릭하지 못하는 상태를 클릭할 수 있을 때까지 기다려주는 것이다. 코드가 길지만 꽤나 직관적인 메서드들을 사용한다.

login_link = WebDriverWait(driver,3).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))

하나씩 뜯어보자.

WebDriverWait(driver,3)

WebDriverWait 은 단순히 만드는 메서드이고, 드라이버를 최대 3초 동안 기다리겠다는 것이다. 무엇을?

.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))

요소가 클릭가능할때까지 기다리겠다는 것이고, 앞에는 어떤 선택자인지와 뒤에는 어떤 요소 인지가 들어간다.

wait = WebDriverWait(driver, 3)

login_link = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))
login_link.click()

기다리는 시간이 같다면 저렇게 새로운 변수로 뺄 수 있다. 다 선택된다면 클릭을 그제야 수행한다. 전체 코드를 보자!

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()

driver.get('https://workey.codeit.kr/costagram/index')

wait = WebDriverWait(driver, 3)

login_link = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.top-nav__login-link')))
login_link.click()

id_box = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.login-container__login-input')))
id_box.send_keys('codeit')

pw_box = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.login-container__password-input')))
pw_box.send_keys('datascience')

login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.login-container__login-button')))
login_button.click()

driver.quit()

나머지는 다 같지만 id_box 와 pw_box 에는 요소가 클릭 가능할 때까지가 아닌 visibility_of_element 라 되어있는데, 요소가 눈에 보일 때까지라는 것이다. 보통 요소가 눈에 보이면 키를 보내는 것이 가능하다.

✅ Wait 조건들 알아보기

element_to_be_clickable() 웹 요소가 클릭 가능한 상태일 때까지 기다림.
visibility_of_element_located() 웹 요소가 실제로 보일 때까지 기다림.
text_to_be_present_in_element() 웹 요소 안에 텍스트가 로딩될 때까지 기다림.
invisibility_of_element_located() 웹 요소가 안 보일 때까지 기다림.

until() 에 전달해주면 되는 wait 의 조건들이다.


08 Selenium 액션 체인

이제까지 배운 것은 클릭키보드 입력 이었으나 실제로는 그 이외의 동작도 많다. 액션체인은

  • 클릭과 키보드 입력 외의 우리의 동작
  • 그러한 동작을 한 번에 이어 연속적으로 수행 가능하게 도와주는 것이다. 액션체인에는 연결하는 방식과 나열하는 방식 두 가지가 있다.

✅ 연결하는 방식

from selenium.webdriver.common.action_chains import ActionChains

# 사용자 동작에 필요한 웹 요소들 찾기

(ActionChains(driver)
    # 사용자 액션 정의 
    .perform())

웹 요소를 미리 다 찾아두고, 체인을 만든다음, .perform() 으로 실행시키는 것이다. 깔끔하게 보이기 위해 코드를 다음줄로 쳐서 만드는 과정에서 아예 감싸는 괄호가 하나 더 추가된다. 코드를 보자.

import time
from selenium import webdriver
from selenium.webdriver import ActionChains

driver = webdriver.Chrome()
driver.implicitly_wait(3)

driver.get('https://workey.codeit.kr/costagram/index')

# 로그인 링크 클릭
driver.find_element_by_css_selector('.top-nav__login-link').click()
time.sleep(1)

# 아이디 박스, 비밀번호 박스, 로그인 버튼 찾아놓기
id_box = driver.find_element_by_css_selector('.login-container__login-input')
pw_box = driver.find_element_by_css_selector('.login-container__password-input')
login_button = driver.find_element_by_css_selector('.login-container__login-button')

# 액션 실행
(ActionChains(driver)
    .send_keys_to_element(id_box, 'codeit')
    .send_keys_to_element(pw_box, 'datascience')
    .click(login_button)
    .perform())

driver.quit()

하나 조심해야 할 것은 아이디, 비번, 로그인 요소가 찾아지는 건 그 전에 로그인 링크를 클릭하는 명령이 수행되어서이다. 로그인 링크를 클릭하지 않고 요소만 먼저 찾으라는 명령은 오류가 난다.

모두 찾아놓고 각각의 메서드들로 메서드.(요소, '필요시 값') 을 적어주는 것이다.

✅ 나열하는 방식

import time
from selenium import webdriver
from selenium.webdriver import ActionChains

driver = webdriver.Chrome()
driver.implicitly_wait(3)

driver.get('https://workey.codeit.kr/costagram/index')

# 로그인 링크 클릭
driver.find_element_by_css_selector('.top-nav__login-link').click()
time.sleep(1)

# 아이디 박스, 비밀번호 박스, 로그인 버튼 찾아놓기
id_box = driver.find_element_by_css_selector('.login-container__login-input')
pw_box = driver.find_element_by_css_selector('.login-container__password-input')
login_button = driver.find_element_by_css_selector('.login-container__login-button')

# 액션 실행
actions = ActionChains(driver)
actions.send_keys_to_element(id_box, 'codeit')
actions.send_keys_to_element(pw_box, 'datascience')
actions.click(login_button)
actions.perform()

driver.quit() 

actions 라는 하나의 액션 체인 변수를 생성하고 그 변수로만 코드를 실행하는 것이다.

✅ 액션 체인 메서드 정리

클릭
.click(element=None)
웹 요소 element를 파라미터로 전달해 주면 element를 클릭하고 그렇지 않으면 현재 마우스가 위치해 있는 곳을 클릭.

더블 클릭
.double_click(element=None)
웹 요소 element를 파라미터로 전달해 주면 element를 더블 클릭하고 그렇지 않으면 현재 마우스가 위치해 있는 곳을 더블 클릭.

우클릭
.context_click(element=None)
웹 요소 element를 파라미터로 전달해 주면 element를 우클릭하고 그렇지 않으면 현재 마우스가 위치해 있는 곳을 우클릭.

드래그 앤 드롭
.drag_and_drop(source, target)
source 웹 요소를 클릭해서 target 웹 요소까지 드래그한 다음, 드롭한다.

마우스 이동
.move_to_element(element)
element 웹 요소까지 마우스를 움직입니다.

키보드 입력
.send_keys(keys)
.send_keys_to_element(element, keys)
send_keys()는 현재 선택된 웹 요소에, send_keys_to_element()는 element 요소에 키보드 입력을 보낸다.

동작 중지
.pause(seconds)
seconds초 동안 동작을 멈춘다.


09 웹사이트 제어하기 정리


10 Selenium 으로 웹 스크래핑

앞서 학습한 Beautiful Soup 과 Selenium 의 웹 스크래핑 메서드들을 비교하면 다음과 같다.


11 Selenium 으로 자바스크립트 사용하기

driver_execute_script 메서드를 실행하면 그 안에 자바스크립트 코드를 실행시킬 수 있다.

from selenium import webdriver
import time

driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.maximize_window()

driver.get('https://workey.codeit.kr/orangebottle/index')
time.sleep(3)

driver.execute_script("window.scrollTo(0,200);")

scroll_height = driver.execute_script('return document.body.scrollHeight')
print(scroll_height)

scrollTo 는 그 안에 있는 좌표만큼 움직이라는 것이다.
return document.body.scrollHeight 통해서는 문서 바디의 길이를 반환할 수 있다.JS 를 사용해 웹 페이지의 끝까지 스크롤하는 코드는 다음과 같다.

from selenium import webdriver
import time

driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.maximize_window()

driver.get('https://workey.codeit.kr/orangebottle/index')
time.sleep(3)

# 현재 scrollHeight 가져오기
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    # scrollHeight까지 스크롤
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    # 새로운 내용 로딩될때까지 기다림
    time.sleep(0.5)

    # 새로운 내용 로딩됐는지 확인
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

현재 길이를 가져오고 끝가지 스크롤한다. 새로운 길이에 다시 현재 길이를 가져오고 만약 현재 길이와 새로운 길이가 같아진다면 종료한다.


12 플레이리스트 정보 스크래핑

웹 사이트에서 동작이 필요한 웹 스크래핑 을 해보자.

from selenium import webdriver
import time
from openpyxl import Workbook


wb = Workbook(write_only=True)
ws = wb.create_sheet('플레이리스트')
ws.append(['제목', '해시태그', '좋아요 수', '곡 수'])

driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)
driver.maximize_window()

driver.get('https://workey.codeit.kr/music')
time.sleep(3)
# 현재 scrollHeight 가져오기
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    # scrollHeight까지 스크롤
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    # 새로운 내용 로딩될때까지 기다림
    time.sleep(0.5)

    # 새로운 내용 로딩됐는지 확인
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

play_list = driver.find_elements_by_css_selector('div.playlist__meta')
for playlist in play_list:
    title = playlist.find_element_by_css_selector('h3.title').text
    hashtag = playlist.find_element_by_css_selector('p.tags').text
    like_count = playlist.find_element_by_css_selector('span.data__like-count').text
    music_count = playlist.find_element_by_css_selector('span.data__music-count').text

    ws.append([title, hashtag, like_count, music_count])
driver.quit()
wb.save('플레이리스트_정보.xlsx')

코드가 길긴 하지만 이전 코드에서 엑셀 파일을 만드는 코드와 이전에 배웠던 css 선택하고 담는 코드가 추가되었던 것 외에는 없다. 하나 주의해야할 건.. find_elements 와 element 를 조심하자. 여러 선택자를 element 로 끌고오면 타입에러가 나는데 발견하기도 힘들다.


14. selenium 과 Beautiful Soup

코드가 길어지고 복잡해지면 사실 이 두 가지를 결합하는 것이 좋다. 각각의 역할은 다음과 같이 구분한다.

Selenium 은 웹 페이지에 들어가서 동작하는 코드를
BS 는 HTML 파싱하여 필요한 태그들을 가져오고 저장하는 코드를 작성한다.

코드를 결합하면 다음과 같다.

import time
from selenium import webdriver
from openpyxl import Workbook
from bs4 import BeautifulSoup

wb = Workbook(write_only=True)
ws = wb.create_sheet('플레이리스트')
ws.append(['제목', '태그', '좋아요 수', '노래 수'])

driver = webdriver.Chrome()
driver.implicitly_wait(3)

driver.get('https://workey.codeit.kr/music')
time.sleep(1)

# 페이지 끝까지 스크롤
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(0.5)

    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

### 스크롤 완료 ### 
music_page = driver.page_source
driver.quit()

soup = BeautifulSoup(music_page, 'html.parser')

playlists = soup.select('.playlist__meta')

for playlist in playlists:
    title = playlist.select_one('h3.title').get_text()
    hashtags = playlist.select_one('p.tags').get_text()
    like_count = playlist.select_one('span.data__like-count').get_text()
    music_count = playlist.select_one('span.data__music-count').get_text()
    ws.append([title, hashtags, like_count, music_count])

wb.save('플레이리스트.xlsx')

15 정리노트


16 코스타그램 스크래핑 준비하기


최종 코드

import time
from selenium import webdriver
from openpyxl import Workbook


wb = Workbook(write_only=True)
ws = wb.create_sheet('코스타그램')
ws.append(['이미지주소', '제목', '해시태그','좋아요 수', '댓글 수'])


# 웹 드라이버 설정
driver = webdriver.Chrome("C:/Users/82102/Downloads/chromedriver_win32/chromedriver.exe")
driver.implicitly_wait(3)

driver.get('https://workey.codeit.kr/costagram/index')
time.sleep(1)

# 로그인
driver.find_element_by_css_selector('.top-nav__login-link').click()
time.sleep(1)
driver.maximize_window()

driver.find_element_by_css_selector('.login-container__login-input').send_keys('codeit')
driver.find_element_by_css_selector('.login-container__password-input').send_keys('datascience')

driver.find_element_by_css_selector('.login-container__login-button').click()
time.sleep(1)

# 페이지 끝까지 스크롤
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    time.sleep(0.5)

    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

# 모든 썸네일 요소 가져오기
posts = driver.find_elements_by_css_selector('.post-list__post')
image_urls = []
for post in posts:
    # 썸네일 클릭
    post.click()
    time.sleep(0.5)

    # 이미지 주소 가져오기
    style_attr = driver.find_element_by_css_selector('.post-container__image').get_attribute('style')
    image_path = style_attr.split('"')[1]
    image_url = 'https://workey.codeit.kr/costagram/index' + image_path
    content = driver.find_element_by_css_selector('.content__text').text.strip()
    hashtag = driver.find_element_by_css_selector('.content__tag').text.strip()
    like_count = driver.find_element_by_css_selector('.content__like-count').text.strip()
    comment_count = driver.find_element_by_css_selector('.content__comment-count').text.strip()
    ws.append([image_url, content, hashtag, like_count, comment_count])

    # 닫기 버튼 클릭
    driver.find_element_by_css_selector('.close-btn').click()
    time.sleep(0.5)

driver.quit()

wb.save('코스타그램.xlsx')
profile
Mathematics, Algorithm, and IDEA for AI research🦖

0개의 댓글