본문 바로가기
Portfolio & Toy-Project

프로젝트 : 부산항만공사 서비스 제안관련 데이터분석-1

by Mr.DonyStark 2024. 3. 11.

ㅁ 프로젝트 산출물 :  https://busanportservice.streamlit.app/

 

Abstract

Busan Port

busanportservice.streamlit.app

 

ㅁ항구별 체류시간관련 분석을 위한 데이터 크롤링

  ○ 셀레니움 → 크롤링 활용

  ○ 뷰티풀숲 → 크롤링 활용

  ○ re  전처리 활용

  ○ pandas → 전처리 활용

from selenium import webdriver #Selenium의 웹 드라이버를 사용하기 위한 모듈을 임포트
from selenium.webdriver.common.by import By #Selenium에서 사용하는 By 클래스를 임포트합. 웹 요소를 검색하는데 사용.
from selenium.webdriver.common.keys import Keys #키보드 입력 제어를 위해 Keys 클래스 임포트
from selenium.webdriver.chrome.service import Service #Chrome 드라이버 서비스를 사용하기 위한 모듈 임포트
from selenium.webdriver.chrome.options import Options #Chrome 드라이버 옵션을 설정하기 위한 클래스 임포트
from webdriver_manager.chrome import ChromeDriverManager #Chrome 드라이버를 자동으로 설치 및 관리하는데 사용되는 드라이버 매니저 임포트
from bs4 import BeautifulSoup #Beautiful soup 크롤링을위해
import pandas as pd
import requests #request 사용을 위해
import re #정규표현을 위해
import time #timesleep을 위해 사용

myOption = Options() #옵션객체 변수지정
myOption.add_argument("--start-maximized") #크롬 드라이버 창 최대화
myOption.add_argument("--incognito") #크롬 드라이버 시크릿모드로 진행
myOption.add_experimental_option("excludeSwitches", ["enable-automation"]) #드라이버 시작시 불필요문구 미표시되도록 설정
myOption.add_experimental_option("excludeSwitches", ["enable-logging"]) #터미널상의 불필요문구 미표시되도록 설정
myOption.add_experimental_option("detach", True) #드라이버 자동꺼짐 방지

#드라이버 세팅
myService = Service(ChromeDriverManager().install()) #크롬드라이버 설치
myDriver = webdriver.Chrome(service=myService, options=myOption) #드라이버 서비스 및 옵션 지정

targetUrl = "https://www.pnitl.com/infoservice/vessel/vslScheduleList.jsp"
myDriver.get(targetUrl) #타겟링크로부터 정보수신
time.sleep(4)
print(f"{myDriver.current_url} 접속완료")

myDriver.find_element(By.ID, value="strdStDate").clear()
time.sleep(3)
print("시작일자 초기화완료")
myDriver.find_element(By.ID, value="strdStDate").send_keys("2018-01-01")
time.sleep(3)
print("시작일자 입력완료")
myDriver.find_element(By.ID, value="strdEdDate").clear()
time.sleep(3)
print("마감일자 초기화완료")
myDriver.find_element(By.ID, value="strdEdDate").send_keys("2023-12-31")
time.sleep(3)
print("마감일자 입력완료")
myDriver.find_element(By.ID, value="submitbtn").click()
time.sleep(300)
print("검색조회 완료")

webCurrentStatus = myDriver.page_source #셀레니움 자동화 진행완료 후의 상태의 html 저장을 위해 지정
print(webCurrentStatus)

myParser = BeautifulSoup(webCurrentStatus, "html.parser")

tbodyData = myParser.select("div.tblType_08 > table > tbody > tr")

rowData = list()
for i in tbodyData:
    rowData.append(i.text.replace("\n"," "))

#필드명 제거를 위해 데이터
rowDataNoHeader = rowData[1:]

#1. 선사코드, 선사세부코드, 선사명, 선사상태 추출
shipNameCd_list = list()
shipNameFull_list = list()
shipStatus_list = list()
for i in rowDataNoHeader:
    shipNameCd = i.split(" ")[2]
    shipNameCdDetail = i.split(" ")[3]
    shipNameFull = i.split(" ")[8] + " " + i.split(" ")[9]
    shipStatus = i.split(" ")[-2]
    shipNameCd_list.append(shipNameCd)
    shipNameFull_list.append(shipNameFull)
    shipStatus_list.append(shipStatus)
shipNameCD_Series = pd.Series(shipNameCd_list)
shipNameFull_Series = pd.Series(shipNameFull_list)
shipStatus_Series = pd.Series(shipStatus_list)

#2. 선사별 접안(예정)시간, 출항(예정)시간 추출
#  ▶ 정규표현 패턴 지정 : "yyyy-mm-dd HH:mm" 형식의 날짜 및 시간을 추출
#     └(1) \b: 단어 경계를 의미.
#     └(2) (\d{4}-\d{2}-\d{2} \d{2}:\d{2}): 원하는 날짜 및 시간 패턴을 추출그룹
#     └(3) \d{4}: 연도(4자리 숫자)
#     └(4) \d{2}: 월 또는 일(2자리 숫자)
#     └(5) \d{2}:\d{2}: 시간(24시간 형식)
datePattern = r"\b(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\b"

srtSchedule = list()
endSchedule = list()
for i in rowDataNoHeader:
    strEnddate = re.findall(datePattern,i)
    strTime = strEnddate[1:2]
    endTime = strEnddate[2:3]
    srtSchedule.extend(strTime) 
    endSchedule.extend(endTime)

srtSchedule_Series = pd.Series(srtSchedule) #접안(예정)시간 리스트
endSchedule_Series = pd.Series(endSchedule) #출항(예정)시간 리스트

#모든 리스트의 길이를 동일하게 맞추기 : 길이 지정 및 통일화를 하지 않으면 오류발생으로 해당 코드 작성
min_length = min(len(shipNameCD_Series), len(shipNameFull_Series), len(srtSchedule), len(endSchedule), len(shipStatus_Series))
shipNameCD_Series = shipNameCD_Series[:min_length]
shipNameFull_Series = shipNameFull_Series[:min_length]
srtSchedule = srtSchedule[:min_length]
endSchedule = endSchedule[:min_length]
shipStatus_Series = shipStatus_Series[:min_length]

#1번 + 2번을 바탕으로 데이터프레임 생성
newShipData = pd.DataFrame({
    '선사코드' : shipNameCD_Series,
    '선사명' : shipNameFull_Series,
    '접안시간' : srtSchedule,
    '출항시간' : endSchedule,
    '상태' : shipStatus_Series
})

#체류시간 필드 및 시간연산을 위해 데이터 형변환 진행
newShipData['접안시간'] = pd.to_datetime(newShipData['접안시간'])
newShipData['출항시간'] = pd.to_datetime(newShipData['출항시간'])
newShipData['체류시간'] = newShipData['출항시간'] - newShipData['접안시간']
newShipData = newShipData[['선사코드', '선사명', '접안시간', '출항시간', '체류시간', '상태']] #필드순서 지정

#체류시간 일, 시간을 구하기 위해 str로 형변환 후 반복문을 사용해 파싱/추출작업진행
calTime = newShipData['체류시간'].astype(str)
day_list = list()
time_list = list()
for i in calTime:
    day = i.split(" ")[0]
    day_list.append(day)
    time = i.split(" ")[2][:2]
    time_list.append(time)
day_Series = pd.Series(day_list)
time_Series = pd.Series(time_list)

#체류시간 일, 시간, 일을 시간으로 환산 및 나머지 시간필드값과의 연산을 위해 형변환 및 신규필드생성
newShipData['체류시간_day'] = day_Series.astype(int)
newShipData['체류시간_time'] = time_Series.astype(int)
newShipData['체류시간_totalTime(시간)'] = (newShipData['체류시간_day'] * 24) + newShipData['체류시간_time']
#필드순서 지정
newShipData = newShipData[['선사코드', '선사명', '접안시간', '출항시간', '체류시간', '체류시간_day', '체류시간_time', '체류시간_totalTime(시간)','상태']]
#가공데이터 csv 저장
newShipData.to_csv("shipSchedule.csv", encoding="utf-8-sig", index=False)