개인(팀) 프로젝트/해상물류 통합 데이터 플랫폼 프로젝트

07. Selenium - 동적 사이트 테이블 Crawling

한소희DE 2021. 6. 28. 23:34

 

 

 

목차

크롤링 사용 목적

Selenium 구현 과정

 


 

 

 

01. 크롤링 사용 목적

 

우리는 동적 페이지 내 데이터를 실시간 수집을 해와야 한다. 대시보드에 실시간으로 데이터를 띄워주어야 하기 때문이다. 따라서 데이터 크롤링 작업이 필요하다.

 

동적 페이지란?

같은 URL에 접속해도 정보가 조금씩 달라지는 페이지를 말한다. 대부분의 페이지는 동적 페이지며, 백과사전과 같이 데이터가 수정이 잘 되지 않는 페이지가 정적 페이지다.

 

 

1-1. Selenium을 사용한 이유

 

이때, Selenium과 BeautifulSoup4 중 Selenium을 사용하기로 했다. 왜냐하면, 우리가 크롤링해야 하는 사이트인 Port-Mis는 URL로만 데이터 접근이 어려운 사이트기 때문이다.

= 즉, 같은 URL로 접속해도, 입력장치에 의한 접속이 추가적으로 필요하다.

 

따라서, Selenium을 중심으로 크롤링을 진행해야겠다고 판단했다.

 

 

 

🔥 여기서 잠깐! Selenium과 BeautifulSoup4의 차이점? 

Selenium BeautifulSoup4
웹 동작을 수행해 데이터를 크롤링하는 라이브러리다.

라이브러리 자체가 무거워 간혹 다운되는 경우가 있다.
HTML과 XML을 파싱해 데이터를 수집하는 파이썬 라이브러리다.

사용자의 행동을 특정해서 데이터를 가져올 수 없다.

 

 

 

Selenium을 사용하기 위해서는, 우선적으로 1) Selenium이 설치되어야 한다. (너무 기본 of 기본적인 말이다.. 하하)

그리고, 2) 크롬 드라이버를 설치해야 한다. 

 

설치 작업의 경우, 구글링을 통해 간단히 알 수 있으므로 설치 과정은 생략하도록 하겠다.

 

※ 크롬 드라이버 설치 시 주의사항?
반드시 각자 크롬의 버전에 맞는 크롬 드라이버를 다운로드해주어야 한다.

 

 


 

02. Selenium 구현 과정

 

2-1. 크롤링 목표

 

우리는 Port-Mis 홈페이지 - 시설사용허가현황에서의 울산항 데이터만을 추출해 크롤링할 것이다.

 

 

홈페이지는 위 사진과 같이 생겼고, 이곳에서 나오는 테이블은 전부 동적 테이블이다. 따라서 별도의 작업을 통해 데이터 수집을 해야 한다는 어려움이 있다.

 

이런 특징을 고려하여 크롤링을 공부해 진행해보겠다!

 

이 게시물은, 사용한 코드를 모두 올리기보단, 공부한 내용을 중점으로 핵심 코드만 추려 정리하도록 하겠다! 셀레늄 특성상 반복되는 코드가 많기 때문이기도 하고, 이 포스팅의 목적이 핵심 개념 정리기 때문이다.

(만약 전체 코드가 궁금하다면 댓글을 남겨주세요!)

 

 

 

2-2. 초기 설정

우선, 필요한 라이브러리를 import 한 뒤 크롬 드라이버를 사용하겠다고 지정한다. 

 

implicitly_wait의 역할?
최대 3초를 기다려 파싱을 기다린다. 만약 3초가 지났는데도 파싱이 진행되지 않으면, 에러가 출력된다.

 

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time

options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-logging"])

driver = webdriver.Chrome("C:/Modules/chromedriver.exe")
driver.implicitly_wait(3)

 

 

 

 

2-3. 원하는 URL 접근

원하는 URL을 지정한 뒤, wait 명령을 걸어준다.

driver.get('https://new.portmis.go.kr/portmis/websquare/websquare.jsp?w2xPath=/portmis/w2/main/intro.xml')
driver.implicitly_wait(3)

 

 

 

 

2-4. 버튼 클릭 명령

버튼 클릭 명령은 find_element_by_id 명령어를 이용해 사용할 수 있다. 만약 클래스명을 지정해 클릭을 하고 싶다면, find_element_by_class_name 명령어를 사용하면 된다.

btn = driver.find_element_by_id('mf_btnSiteMap')
btn.click()

 

 

2-5. 항 코드 입력(데이터 입력 명령)

이 부분을 해결하는 데에 많은 고민을 했다. 

왜냐하면 이렇게, 1) 새로운 페이지를 띄워 데이터를 기입하고, 2) 820 숫자에 해당하는 값을 클릭해도 820이 출력되지 않았기 때문이다. 

 

2) 였던 이유는, 테이블이 지정되어있지 않고, html에는 10개의 테이블 raw가 있는데 table 스크롤을 내릴 때마다 10개의 테이블 raw 내 값이 변경되는 형식이었기 때문이다... 동적...이었는데 정말 답답했다.

 

1)을 해결하는 방법은 쉽지만 2)는 쉬이 해결되지 않았다.

따라서 돋보기 모양을 눌러 새 창을 띄우지 않고 청 코드 자체에 820을 기입하는 방법을 찾았다. 코드는 아래와 같다.

 

# 청코드 입력버튼 클릭
selected_tag=driver.find_element_by_css_selector('input#mf_tacMain_contents_M1554_body_srchPrtAgCd')
selected_tag.click()

# 820 기입
time.sleep(1.5)
driver.implicitly_wait(10)
selected_tag.send_keys('820')
time.sleep(1.5)의 의미 및 사용 이유?

대기를 걸어놓은 것이다. 매크로가 너무 빨리 돌아가서, 클릭이 채 되기도 전에 넘어가는 경우가 왕왕 생겨 time.sleep(1.5)를 걸어두었다.

 

 

 

 

2-6. 50000개씩 보기 선택 명령(🔥 Option 선택 방법)

한 번에 모든 테이블 값을 출력하기 위해, 10개씩 보기 -> 50000개씩 보기를 선택했다. 이는 option으로 html에 설정되어있는 값이다. 이는 for문을 통해 찾아서 선택하면 되고, 코드는 아래와 같다.

 

el = driver.find_element_by_id('mf_tacMain_contents_M1554_body_udcGridPageView_sbxRecordCount_input_0')
for option in el.find_elements_by_tag_name('option'):
    if option.text == '50000개씩 보기':
        option.click() # select() in earlier versions of webdriver
        break

 

 

 

 

 

2-7. 키보드 제어 DOWN 명령

Selenium은 키보드 명령도 가능하다. 아까 말했듯, 우리가 출력하고자 한 테이블은 동적 테이블이므로, 스크롤을 다운해가며 계속 첫 번째 raw 값을 stg_list에 저장해보겠다.

(원래는 num 값 말고도 많은 데이터 값이 존재한다. 하지만, 이 포스팅에서는 코드에서 (샘플로써) num만 보여보도록 하겠다. num만 봐도 코드의 구성이 이해되기 때문이다.) 

 

코드 설명?
1. stg_list라는 리스트에, 각각의 raw 값을 dict 형태로 저장한다.

2. 첫 번째 raw값이 이미 stg_list에 존재하면, 그냥 스크롤을 다운한다. 하지만 첫 번째 raw값이 stg_list에 존재하지 않다면, 데이터를 stg_list에 저장해준 뒤 스크롤을 다운한다.
🔥 이렇게 설계한 이유: 테이블이 동적 테이블이기 때문에, 스크롤을 다운할 때마다 첫 번째 raw값이 바뀌지만, 처음 페이지에서는 스크롤을 다운해도 첫 번째 raw값이 바뀌지 않기 때문에(9번을 스크롤 다운해야만 첫 번째 raw값이 바뀐다.) 고민 끝에 이렇게 구성하였다... 

3. 이 작업을 15번 (우선 샘플이므로 15회) 반복한다.
리스트 형태의 데이터 저장 공간에 딕셔너리를 저장한 이유?

성능을 고려했기 때문이다. 딕셔너리 내에서는 순서가 필요 없지만, 리스트 내에서는 순서가 존재한다. 따라서 제일 최근에 집계된 데이터를 출력하기 위해, 딕셔너리의 저장소는 리스트를 택했다. 또한 각각의 데이터를 딕셔너리로 저장한 이유는, 리스트보다 딕셔너리가 저장공간 및 성능이 좋기 때문에 고민 끝에 이렇게 설계했다...

 

# 리스트 형태(각 데이터는 딕셔너리)의 데이터 저장 공간
stg_list=[{'num':'-1231'}]

# DOWN 키보드 제어 명령어
DOWN = '/ue015'

# test로 15개만 반복 진행
for _ in range(0,15):
    num=driver.find_element_by_id('mf_tacMain_contents_M1554_body_grpSrchList_cell_0_0').text

    if stg_list[-1]['num'] == num:
        btn.send_keys(Keys.DOWN)
        continue
    dataset = {'num':num}
    
    stg_list.append(dataset)
    btn.send_keys(Keys.DOWN)

 

 

 

2-8. 코드 결과 살펴보기

코드를 전체적으로 실행하면, 아래와 같은 결과가 출력된다!

 

 

오늘은 여기서 크롤링을 마쳤다. 하지만, 여기서 문제가 생겼다.

바로 12번째 데이터 이후(app_from부터)부터는 데이터가 크롤링이 잘 되지 않는 것이다. id 명을 정확히 기입했는데도 잘 되지 않아서, 이 부분은 추가적인 구글링 후 문제를 해결해야 할 것 같다.

 

다음 셀레니움 포스팅에서는 이 문제를 해결해 데이터를 추출하여 + MongoDB로 데이터를 적재하는 방법을 구현해보고 포스팅을 해볼 것이다! 오늘의 공부 끝!!! (힘들었다...! 휴)

 

 

 

 

(+ 문제를 해결한 과정에 대한 다음 포스팅 링크는 아래와 같다!)

https://eng-sohee.tistory.com/92

 

08. Selenium - 동적 사이트 테이블 Crawling : 오류 해결

목차 Selenium으로 인식되지 않는 text를 print 하는 방법 동적 테이블 값을 Selenium + BeautifulSoup4 로 출력하는 방법 01. Selenium으로 인식되지 않는 text를 print 하는 방법 나는 이전동안, Selenium을 이..

eng-sohee.tistory.com