본문 바로가기
[파이썬]/데이터 분석

[데이터 분석] selenium 라이브러리를 활용한 동적 웹 크롤링

by sung min_Kim 2023. 12. 5.


 본 글에서는 웹 크롤링에 대한 내용을 다룰 것이다. 그 중에서도 'Selenium' 라이브러리를 사용한, 동적 웹 크롤링에 대해 중점적으로 다루고자 한다. 차례와 사용 툴 및 라이브러리는 아래와 같다.

 [차례]
 첫 번째, 웹 크롤링(Web Crawling )이란
 두 번째, (동적) 웹 크롤링 라이브러리

 세 번째, 동적 웹 크롤링의 일반적인 과정
 네 번째, 예시_10개의 영화에 대한 제목, 평점, 리뷰데이터 수집하기


 [사용 툴]
- Jupyter notebook(웹 기반 대화형 코딩 환경)
 
 [사용 라이브러리]
 - selenium (webdriver, By)
 - time


웹 크롤링(Web Crawling)

 

웹 크롤링(출처 : https://terms.tta.or.kr/dictionary/dictionaryView.do?subject=crawling)


 웹 크롤링이란, 웹 페이지를 방문하고 그 페이지의 정보를 자동으로 수집하는 과정을 말한다. 이는 일반적으로 '웹 크롤러'라는 프로그램을 통해 이루어지며, 이 프로그램은 웹 페이지를 방문하고, 그 페이지의 정보를 가져오고, 그 페이지에서 다른 페이지로의 링크를 따라가는 방식으로 동작한다.

 웹 크롤링은 검색 엔진, 데이터 분석, 마케팅 연구 등 다양한 분야에서 사용된다. 예를 들어, 구글 같은 검색 엔진은 웹 크롤링을 통해 인터넷에 존재하는 수많은 웹 페이지의 정보를 수집하고, 이 정보를 바탕으로 사용자가 검색했을 때 가장 관련성이 높은 결과를 보여준다. 또한, 웹 크롤링은 온라인 쇼핑몰에서 상품의 가격을 수집하거나, SNS에서 특정 키워드에 대한 반응을 분석하는 등의 작업에도 활용된다. 이런 정보들은 시장의 트렌드를 파악하거나, 경쟁사의 전략을 분석하거나, 사용자의 반응을 이해하는데 도움이 된다.

 웹 크롤링을 위한 도구는 다양하며, 파이썬에서는 'Beautifulsoup', 'Selenium' 등의 라이브러리가 널리 사용된다. 이런 도구들을 사용하면 웹 페이지의 HTML 코드를 분석하고, 원하는 정보를 추출하고, 웹 페이지와 상호작용하는 등의 작업을 자동화할 수 있다. 

 그러나 웹 크롤링을 할 때에는 반드시 엡 사이트의 이용 약관을 준수해야 한다. 일부 사이트에서는 크롤링을 금지하거나 제한하고 있으며, 이를 무시하고 크롤링을 진행할 경우 법저인 문제가 발생할 수 있다. 또한, 과도한 크롤링은 웹 서버에 부담을 주어 사이트의 정상적인 운영을 방해할 수 있으므로, 적절한 주기와 방식으로 크롤링을 사용해야 한다.

 


 

· (동적) 웹 크롤링 라이브러리

 

# 동적 웹 크롤링 라이브러리
from selenium import webdriver
from selenium.webdriver.common.by import By 

# 시간 라이브러리
import time


 동적인 웹 크롤링을 수행할 경우 주로 사용되는 라이브러리이다. 로그인, 버튼 클릭, 폼 제출 등 사용자의 입력에 반응하는 웹 페이지에서 정보를 수집할 때 유용하게 사용된다.

 

  • selenium : 셀레니움은 웹 브라우저의 동작을 자동화하기 위한 도구이다. 즉, 웹 브라우저를 직접 제어하여 웹 페이지를 열고, 버튼을 클릭하고, 텍스트를 입력하고, 페이지를 스크롤하는 등의 작업을 자동화할 수 있다. 이를 통해 자바스크립트로 동적으로 변화하는 웹 페이지의 내용을 가져올 수 있다.

  • selenium.webdriver : 'Webdriver'운 셀레니움의 핵심 컴포넌트로, 웹 브라우저를 제어하는 인터페이스를 제공한다. 이를 통해 웹 브라우저를 열고, 웹 페이지를 로드하고, 웹 페이지의 요소를 찾고, 웹 페이지와 상호작용하는 등의 작업을 수행한다.

  • selenium.webdriver.common.by : 'By' 클래스는 웹 페이지의 요소를 찾을 때 사용되는 방법을 정의하는 상수를 제공한다. 예를 들어, By.ID, By.Name, By.CSS_Selector  등의 방법으로 웹 페이지의 요소를 찾을 수 있다.

  • time : 파이썬의 표준 라이브러리인 'Time'은 다양한 시간 관련 기능을 제공한다. 이 중 'time.sleep()' 함수는 주로 웹 크롤링에서 페이지 로딩 대기나 요청 간 지연시간을 설정하는 데 사용된다. 예를 들어, 'time.sleep(1)'은 코드 실행을 1초 동안 일시 정지시키는 기능을 한다. 이를 통해 웹 페이지가 동적으로 변화하는 내용이 완전히 로드될 시간을 확보하고, 웹 서버로부터 IP 차단 등의 문제를 방지함으로써, 웹 크롤링하고자 하는 데이터를 안정적으로 수집할 수 있게 도와준다.

 


· 동적 웹 크롤링의 일반적인 과정



  1. 먼저, 크롬 브라우저를 제어할 수 있는 도구를 준비한다. 이 과정은 'webdriver.Chrome()'을 통해 이루어진다. 이는 셀레니움 웹드라이버의 Chrome 인스턴스를 생성하는 과정으로, 실질적으로 Google Chrome 브라우저를 제어할 수 있는 객체를 만드는 단계이다.


  2. 다음으로, 원하는 웹 페이지를 열어준다. 이는 'driver.get(url)'을 통해 이루어지며, 여기서 url은 접근하고자 하는 웹 페이지의 주소를 나타낸다.


  3. 이후, 웹 페이지에서 크롤링하고자 하는 데이터의 위치를 찾는다. 이는 'driver.find_elements(By.검색기준, 검색 값)'을 사용하여 이루어진다. 여기서 첫 번째 인자로 사용된 'By.검색기준'은 웹 페이지의 요소를 어떤 방법으로 찾을 것인지를 결정하며, 두 번째 인자인 '검색 값'은 실제로 웹 페이지에서 찾고자 하는 요소의 위치나 속성을 나타낸다.

     예를 들어, '검색기준'으로 'By.CSS_SELECTOR'를 사용하고, '검색 값'으로 "#content > div.section.inner_sub > table.type2 > tbody > tr" 같은 CSS 경로를 사용한다면 이는 웹 페이지에서 해당 CSS 경로에 위치한 요소를 찾는 것을 의미한다.

     'find_elements()' 함수는 찾은 요소들을 리스트 형태로 반환한다. 만약 찾고자 하는 요소가 하나라면 'find_element()' 함수를 사용할 수도 있다. 'find_element()' 함수는 첫 번째로 찾은 요소를 반환하며, 만약 해당 요소가 없다면 에러를 발생시킨다. 이를 방지하기 위해 'try~except' 문을 사용하여 예외를 처리할 수 있다.



  4. 위치를 찾은 후에는, 해당 요소를 클릭해야 한다. 이는 'click()' 함수를 통해 이루어지며, 이를 통해 웹 페이지에서 버튼을 누르거나 다른 페이지로 이동하는 등의 동작을 수행한다.


  5. 새로운 웹 페이지로 이동하게 되면, 창의 관리를 해야 한다. 웹 브라우저에서 새로운 탭이나 창이 열리면, 각 각의 창은 고유한 핸들(handle)을 가지게 된다. 여기서 말하는 "핸들(handle)"이란 특정 창을 직접 가리키고 조작하기 위해 사용하는 고유 식별자인 'ID'와 같은 역할을 한다. 따라서, 'driver.window_handles' 명령을 통해 현재 열려 있는 모든 창의 핸들 목록을 가져올 수 있다.

     이 목록은 창이 열린 순서대로 정렬되어 있으므로, '-1' 인덱스를 사용하여 가장 최근에 열린 창의 핸들을 선택할 수 있다. 그리고 'driver.switch_to.window()' 명령을 사용하여, 우리는 특정 창으로 전환할 수 있다. 여기서 인자는 전환하고자 하는 창의 핸들을 사용한다.

     따라서 새로운 탭에서 크롤링을 계속하기 위해서는, 먼저 'driver.window_handles[-1]'을 사용하여 최근에 열린 창의 핸들을 얻고, 그 후 'driver.switch_to.window()'을 사용하여 해당 창으로 전환해야 한다.


  6. 웹 페이지가 전환된 후, 웹 페이지가 완전히 로딩될 때까지 기다려야 한다. 이는 'time.sleep()'을 통해 이루어지는데, 이 함수는 프로그램의 실행을 일정 시간 동안 멈추게 한다. 웹 페이지의 내용이 동적으로 변경되는 경우, 모든 내용이 완전히 로드될 때까지 충분한 시간을 기다려야 한다. 이때, 기다리는 시간을 너무 길게 설정하면 프로그램의 효율성이 떨어질 수 있으므로, 웹 페이지의 로딩 시간과 크롤링할 데이터의 양 등을 고려하여 적절한 시간을 설정해야 한다.


  7. 웹 크롤링 작업을 수행한 후에는 'driver.quit()' 함수를 사용해 'driver'을 닫아주는 것이 중요하다. 'driver'은 사용 중인 컴퓨터의 리소스를 차지하기 때문에, 크롤링 작업이 끝난 후에는 이를 해제하여 시스템 리소스를 효율적으로 사용하기 위함이다. 또한, 'driver'을 제대로 닫지 않으면 브라우저가 계속 열려 있게 되어 원치 않는 동작이나 버그를 유발할 수 있다.

    추가로, 외부 자원을 사용하는 경우, 예외 처리를 통해 프로그램의 안전성을 높일 수 있다. 특히, 웹드라이버와 같은 외부 자원은 사용 후 반드시 제대로 닫아주어야 한다. 이를 위해 'try~except~finally' 구문을 사용할 수 있다. 'try' 블록 안에서는 웹드라이버를 사용하여 웹 크롤링 작업을 수행한다. 만약 이 과정에서 에러가 발생하면, 'except' 블록이 실행되어 에러를 처리하게 된다. 여기에서 'driver.quit()'을 호출하여 에러 발생 시에도 웹드라이버를 안전하게 종료할 수 있다. 마지막으로 'finally' 블록에서는 예외 발생 여부와 관계없이 항상 실행되는 코드를 넣는다. 여기에서도 'driver.quit()'를 호출하여, 웹 크롤링 작업 후에는 반드시 웹드라이버를 종료하도록 한다. 이렇게 함으로써, 예기치 않은 에러로 인한 리소스 누수를 방지하고 프로그램의 안전성을 높일 수 있다.

 


· 예시_10개의 영화에 대한 제목, 평점, 리뷰데이터 수집하기

 

사용할 URL 주소 : https://movie.daum.net/ranking/boxoffice/monthly

1_필요한 라이브러리 호출 | 웹 브라우저 창 띄우기
# 라이브러리 호출
from selenium import webdriver
from selenium.webdriver.common.by import By
import time

# Chrome 브라우저 제어
driver = webdriver.chrome()

# 접근하고자 하는 URL 입력
driver.get("https://movie.daum.net/ranking/boxoffice/monthly")


 동적 웹 크롤링을 수행하기 위해, 필요한 라이브러리를 호출한다. 'selenium'은 동적 웹 크롤링을 위한 도구이고, 'webdriver'은 웹 페이지를 제어하기 위한 모듈이며, 'By'는 웹 페이지의 요소를 찾는 데 사용되는 함수를 포함하고 있다. 'time'은 프로그램의 실행시간을 일정 시간 동안 지연시키는 데 사용된다.

 다음으로, 'webdriber.Chrome()'을 통해 Chrome 브라우저를 제어한다. 이는 웹드라이버가 로컬 시스템에서 Chrome 브라우저를 제어할 수 있게 해 준다. 이후, 'driver.get()' 함수를 사용하여 접근하고자 하는 URL을 입력한다. 이 예시에서는 "랭킹| 다음영화" 페이지에 접근한다.

1_웹 크롤링을 수행할 페이지 방문

 

2_영화제목 요소의 HTML 태그 경로 추출 | 해당 경로의 페이지 열기
try :
    driver = webdriver.Chrome()
    driver.get("https://movie.daum.net/ranking/boxoffice/monthly")
    
    movie_path = "#mainContent > div > div.box_boxoffice > ol > li:nth-child(1) > div > div.thumb_cont > strong > a"
    movie_element = driver.find_element(By.CSS_SELECTOR, movie_path)
    
    movie_element.click()
    movie_handle = driver.window_handles[-1]
    driver.switch_to.window(movie_handle)
    
except Exception as e :
    print(e)
    driver.quit()
    
finally :
    driver.quit()

 
 10개의 영화제목을 추출하기 이전에, 영화제목이 "서울의 밤"인 <a> 태그 요소의 경로를 추출하여, 해당 요소를 클릭함으로써 해당 페이지로 이동하고자 하였다. 클릭 후에는 새로 열린 창으로 포커스를 이동해야 하는데, 이때, 창의 식별자인 핸들(handle)을 사용한다.

 'driver.window_handles[-1]'은 현재 열려 있는 모든 창의 리스트에서 가장 마지막에 있는 창, 즉 가장 최근에 연린 창의 핸들을 반환한다. 그리고 'driver.switch_to.window(movie_handle)'를 통해 이 핸들을 가진 창으로 포커스를 이동시킨다.

 이렇게 함으로써 클릭한 요소의 페이지를 열 수 있게 된다. 추가로 웹드라이버와 같은 외부 자원을 닫아주기 위해 'try~except~finally' 예외처리 문을 사용하였다.

 

2_1_개발자도구(F12)를 통해 HTML 경로 추출

 

2_2_영화제목 <a> 태그 요소의 경로를 추출하여, 클릭-접근-전환

 

3_영화제목 10개 추출하기
try :
    driver = webdriver.Chrome()
    driver.get("https://movie.daum.net/ranking/boxoffice/monthly")
    
    movie_path = "#mainContent > div > div.box_boxoffice > ol > li > div > div.thumb_cont > strong > a"
    movie_elements = driver.find_elements(By.CSS_SELECTOR, movie_path)
    
    # (3번 작업 영역)---------------------------------
    for i in range(10) :
        movie_elements[i].click()
        movie_handle = driver.window_handles[-1]
        driver.switch_to.window(movie_handle)
        time.sleep(1)
        driver.execute_script("window.history.go(-1)")
        time.sleep(1)
     # ------------------------------------------------

except Exception as e :
    print(e)
    driver.quit()
    
finally :
    driver.quit()


 for문을 사용하여 웹 페이지에서 첫 10개의 영화제목을 웹 요소를 추출하였다. 영화제목을 클릭하고 각 영화의 상세 페이지로 이동한 후, 원래의 영화 목록 페이지로 돌아가는 작업을 수행한다. 이는 각 영화의 상세 정보를 수집하는데 필요한 기본 동작을 구현한 것이다.

'driver.execute_script("window.history.go(-1)")'은 셀레니움 웹드라이버에서 자바스크립트를 실행하는 함수인 'execute_script()'를 사용하여 웹 브라우저의 뒤로 가기 기능을 실행하는 코드이다. 이 코드에서 'window.history.go(-1)'은 자바스크립트 코드로, 웹 브라우저의 히스토리에서 한 단계 뒤로 이동하는 동작을 의미한다. 여기서 '-1'은 한 단계 뒤로 가기를 의미한다. 즉, 이 코드는 현재 열려 있는 웹 페이지에서 뒤로 가기 버튼을 누른 것과 같은 동작을 수행한다.

3_ 10개의 영화 제목 추출

 

4_평점 탭 이동하기
try :
    driver = webdriver.Chrome()
    driver.get("https://movie.daum.net/ranking/boxoffice/monthly")
    
    movie_path = "#mainContent > div > div.box_boxoffice > ol > li > div > div.thumb_cont > strong > a"
    movie_elements = driver.find_elements(By.CSS_SELECTOR, movie_path)
    
    # [영화제목 10개 출력하기]
    for i in range(10) :
        movie_elements[i].click()
        movie_handle = driver.window_handles[-1]
        driver.switch_to.window(movie_handle)
        time.sleep(1)
        driver.execute_script("window.history.go(-1)")
        
        # (4번 작업 영역)------------------------------------
        # [평점 탭 이동하기]
        score_tap_path = "#mainContent > div > div.box_detailinfo > div.tabmenu_wrap > ul > li:nth-child(4) > a"
        score_tap_element = driver.find_element(By.CSS_SELECTOR, score_tap_path)

        score_tap_element.click()
        score_tap_handle = driver.window_handles[-1]
        driver.switch_to.window(score_tap_handle)
        time.sleep(1)
        driver.execute_script("window.history.go(-2)")
        time.sleep(1)
        # -----------------------------------------------------

except Exception as e :
    print(e)
    driver.quit()
    
finally :
    driver.quit()


 영화제목을 추출한 방식과 마찬가지로, 영화 상세보기 페이지의 '평점' 탭 요소의 CSS 선택자를 사용하여 해당 탭을 찾고, 클릭하여 '평점' 페이지로 이동하게 하였다. 이후 코드를 실행하면 각 영화의 상세보기 화면으로 이동하고, 영화의 상세보기 화면에서 평점 탭을 클릭한다. 이 동작을 10회 반복 수행하며, 각 반복마다 원래의 영화 상세보기 화면으로 돌아간다.

4_1 _평점 <a> 태그 요소의 경로를 추출하여, 클릭-접근-전환

 

5_평점 | 리뷰 데이터 추출하기

5_CSS 선택자를 통해 '평점' 요소의 위치를 지정

 영화제목과 평점 탭에 대한 CSS 선택자는 '<a>' 태그로 존재하여, 요소를 찾기가 수월하였는데, "평점"의 경우에는 이러한 태그가 사용되지 않아, 다른 방식으로 요소의 위치를 지정해야 한다. 상위 선택자로 거슬러 올라가 "평점" 값을 뽑기에 적합한 선택자들을 살펴보도록 하자.

 각 각의 "평점" 요소는 '<div class="ratings"> 태그로, '<li>' 태그 아래에 자손 형태로 위치한다. 이 '<li>' 태그는 '<ul class=list_comment"> 태그 아래 자식 형태로 위치한다. 따라서 "평점" 요소의 위치를 CSS 선택자를 사용하여 "ul.list_comment div.ratings"로 지정하여, 해당 요소를 출력하도록 한다.

try :
    driver = webdriver.Chrome()
    driver.get("https://movie.daum.net/ranking/boxoffice/monthly")
    
    movie_path = "#mainContent > div > div.box_boxoffice > ol > li > div > div.thumb_cont > strong > a"
    movie_elements = driver.find_elements(By.CSS_SELECTOR, movie_path)
    
    # [영화제목 10개 출력하기]
    for i in range(10) :
        movie_elements[i].click()
        movie_handle = driver.window_handles[-1]
        driver.switch_to.window(movie_handle)
        time.sleep(1)
        
        # [평점 탭 이동하기]
        score_tap_path = "#mainContent > div > div.box_detailinfo > div.tabmenu_wrap > ul > li:nth-child(4) > a"
        score_tap_element = driver.find_element(By.CSS_SELECTOR, score_tap_path)
        score_tap_element.click()
        score_tap_handle = driver.window_handles[-1]
        driver.switch_to.window(score_tap_handle)
        time.sleep(1)

        # (5번 작업 영역)------------------------------------------------
        # [평점 데이터 출력하기]
        score_path = "ul.list_comment div.ratings"
        driver.find_elements(By.CSS_SELECTOR, score_path)
        
        # [리뷰 데이터 출력하기]
        comment_path = "ul.list_comment p.desc_txt"
        comment_lists = driver.find_elements(By.CSS_SELECTOR, comment_path)
        
        # [평점, 리뷰 추출하기]
        for j in range(len(score_lists)):
            score = score_lists[j]
            comment = comment_lists[j].text.strip().replace("\n","")
          # print(f"{title} \t{score} \t{comment} \n")
        # ------------------------------------------------------------------
        
        driver.execute_script("window.history.go(-2)")
		time.sleep(1)
except Exception as e :
    print(e)
    driver.quit()
    
finally :
    driver.quit()


 각 영화 상세페이지의 평점 탭으로 이동하여 평점과 리뷰 데이터를 출력하였다. 각 데이터를 출력한 후에는 원래의 페이지로 돌아가 이와 같은 동작을 10번 반복한다.

 

6_수집데이터 파일로 저장
try :
    driver = webdriver.Chrome()
    driver.get("https://movie.daum.net/ranking/boxoffice/monthly")
    
    movie_path = "#mainContent > div > div.box_boxoffice > ol > li > div > div.thumb_cont > strong > a"
    movie_elements = driver.find_elements(By.CSS_SELECTOR, movie_path)
    
    # [수집데이터 txt 파일로 저장]
    f = open("./data/movie_reviews.txt", "w", encoding="UTF-8")

        
		...

        
        # [평점, 리뷰 추출하기]
        for j in range(len(score_lists)):
            score = score_lists[j]
            comment = comment_lists[j].text.strip().replace("\n","")
           
           # [파일에 수집데이터 입력]
            f.write(f"{title}\t{score}\t{comment}\t{label}\n")
        
        driver.execute_script("window.history.go(-2)")
		time.sleep(1)
except Exception as e :
    print(e)
    f.close()
    driver.quit()
    
finally :
    f.close()
    driver.quit()

 
 수집한 데이터를 파일에 저장하였고, 'close()' 함수를 사용해 파일을 닫았다. 그리고 모든 작업이 끝난 뒤에는 웹 드라이버를 종료하기 위해 'driver.quit()' 함수를 호출하였다.

6_수집데이터 파일로 저장

 



웹 크롤링은 다양한 예외 상황에 직면할 수 있다. 네트워크 문제, 서버 문제, 데이터 구조 변경 등이 발생할 수 있으므로, 이러한 예외 상황을 처리할 수 있는 코드를 작성하는 것이 중요하다.

너무 많은 요청을 짧은 시간에 보내면, 서버에 과부하를 주거나 웹사이트에서 접근을 차단당할 수 있다. 따라서 적절한 요청 간격을 유지하는 것이 중요하다.

크롤링한 데이터는 적절하게 저장하고 관리해야 한다. 데이터를 파일에 저장할 때는 파일을 꼭 닫아주는 것이 중요하며, 웹 드라이버를 사용한 후에는 반드시 종료해야 한다.

웹사이트의 데이터를 크롤링 할 때는 해당 웹사이트의 이용 약관 및 저작권을 반드시 확인하고 준수해야 한다. 무분별한 데이터 수집은 법적 문제를 초래할 수 있다.

이렇게 웹 크롤링에 대한 이해를 바탕으로,
웹에서 원하는 정보를 효율적으로 수집하는 기술을 계속해서 발전시켜 나가도록 하자.