Team Project: 데이터수집 및 저장 #1

Ohback·2025년 1월 8일

첫 번째 팀프로젝트에 대한 기록

첫 번째 프로젝트는 이틀에 걸쳐 이어지는 16시간짜리의 소규모 프로젝트이다.(강사님의 배려로 사실상 시간이 더 많이 주어졌지만..ㅎ) 부트캠프에 들어온지 2주 만에 하게 된 프로젝트라 어떤 결과물이 나올지 벌써부터 궁금하고 기대된다.

주제와 주요 기술 스택

대주제: 전국 자동차 등록 현황 및 기업FAQ
팀주제: 연도별/지역별 전기차 보조금과 그에 따른 전기차 등록 대수 추이
주요 기술 스택: BeautifulSoup, Selenium으로 Web Crawling을 하고 Python, MySQL을 이용해 결과물을 DB에 저장한 뒤 가져온 데이터를 Streamlit을 활용하여 웹페이지에 구현


Team Project Timeline

1. 기획

  • 주제: 연도별/지역별 전기차 보조금과 전기차 등록 대수 추이
  • 타겟: 각 시/도별 관계자와 전기차 판매 기업
  • 목표: 데이터 기반 정책 및 마케팅 전략 지원
  • 참고 사례 조사 및 아이디어 보완

2. 데이터 수집

  • 연도별 보조금 증감액: KW/SY 담당
  • 연도별 전기차 등록대수: JY/YJ 담당
  • 데이터 검증 및 정리: 크롤링 후 중복/불완전 데이터 제거

3. 데이터 논리화

  • 속성 정의 및 데이터 모델 설계 논의
  • 데이터 통합 및 검토: 크롤링한 데이터를 병합하며 불일치 확인
  • ERD 설계 및 DB 생성

4. 웹페이지 GUI 구현

  • UI/UX 설계: 사용자의 흐름에 따른 페이지 설계
  • Streamlit을 활용한 시각화: 그래프 및 대시보드 구현
  • 기능 테스트: 동작 검토 및 데이터 표현 정확성 확인

5. ReadMe 및 발표 자료 제작과 최종 점검

  • ReadMe 제작 및 작업 내용 정리
  • 발표 자료 준비 및 시뮬레이션
  • 외부 피드백 수집 및 수정
  • 최종 점검 및 백업 파일 준비

6. 협업 및 관리

  • GitHub를 활용한 버전 관리
  • Notion으로 진행 상황 공유 및 일정 관리

연도별 보조금 데이터 가져오기

팀원들과 대략적인 기획을 한 뒤 각자 데이터 수집을 위한 크롤링 코드를 짜보기로 했다. 우리팀이 데이터를 가져오려는 페이지는 버튼을 누르고 들어가야 하는 동적페이지라 Selenium을 사용해야 하는데 아직 배우지 않았기에 Chat GPT의 도움을 받아가며 열심히 짜보았다.

import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# Chrome 웹드라이버 설정
options = webdriver.ChromeOptions()
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)

# 웹사이트 열기
url = "https://ev.or.kr/nportal/buySupprt/initSubsidyPaymentCheckAction.do"
driver.get(url)

try:
    # 'year1' select 요소 로드 대기
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "year1")))
    select_element = Select(driver.find_element(By.ID, "year1"))

    # 옵션 확인
    if not select_element.options:
        print("연도 옵션이 없습니다.")
        driver.quit()
        exit()

    # 옵션 순회
    for option in select_element.options:
        try:
            year = option.text
            print(f"현재 연도 처리 중: {year}")

            if year == "2025":  # 특정 연도 제외
                continue

            # 연도 선택
            select_element.select_by_visible_text(year)
            button = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable((By.ID, "btnLocalCarPrc"))
            )
            button.click()

            # 새 창으로 전환
            WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1)
            driver.switch_to.window(driver.window_handles[-1])

            # 테이블 데이터 로드 대기
            WebDriverWait(driver, 10).until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, "body > form > div > table > tbody > tr"))
            )

            # 데이터 수집
            rows = driver.find_elements(By.CSS_SELECTOR, "body > form > div > table > tbody > tr")
            data = []

            for row in rows:
                cols = row.find_elements(By.TAG_NAME, "td")
                if len(cols) >= 7:
                    entry = {
                        "시도": cols[0].text.strip(),
                        "지역구분": cols[1].text.strip(),
                        "보조금/승용(만원)": cols[3].text.strip(),
                        "보조금/초소형(만원)": cols[4].text.strip()
                    }

                    data.append(entry)

                # 2021년 이상인 경우, 버튼 클릭 후 추가 데이터 수집
                elif int(year) >= 2021:

                    # 추가 테이블 데이터 로드 대기
                    WebDriverWait(driver, 10).until(
                        EC.presence_of_all_elements_located((By.CSS_SELECTOR, "body > form > div > table > tbody > tr")))

                    # 추가 데이터 긁어오기
                    sub_rows = driver.find_elements(By.CSS_SELECTOR, "body > form > div > table > tbody > tr")
                    sub_data = []
                    
                    for sub_row in sub_rows:
                        sub_cols = sub_row.find_elements(By.TAG_NAME, "td")
                        if len(sub_cols) >= 6:  # 차종, 제조사, 모델명 등 추가 항목 포함
                            sub_data.append({
                                "시도": sub_cols[0].text.strip(),
                                "지역구분": sub_cols[1].text.strip(),
                                "보조금/승용(만원)": sub_cols[3].text.strip(),
                                "보조금/소형(만원)": sub_cols[4].text.strip()
                                    })
                        
                            data.append(entry)

                            # 창 닫기 및 원래 창으로 복귀
                            driver.close()
                            driver.switch_to.window(driver.window_handles[-1])

                else:
                    print(f"추가 데이터 수집 중 에러 발생 (연도: {year}, 시도: {entry['시도']}): {str(e)}")
                    driver.switch_to.window(driver.window_handles[-1])


            # 연도별 데이터 저장 (각 연도마다 개별 JSON 파일 생성)
            with open(f"subsidy_data_{year}.json", "w", encoding="utf-8") as json_file:
                json.dump({"연도": year, "데이터": data}, json_file, ensure_ascii=False, indent=4)
            print(f"{year}년 데이터 저장 완료")

            # 창 닫기 및 원래 창으로 복귀
            driver.close()
            driver.switch_to.window(driver.window_handles[0])

        except Exception as e:
            print(f"연도 {year} 처리 중 에러 발생: {str(e)}")
            driver.switch_to.window(driver.window_handles[0])

except Exception as e:
    print(f"스크립트 실행 중 에러 발생: {str(e)}")


finally:
    driver.quit()

위 코드는 페이지를 크롤링 한 뒤 json 파일로 저장하는 코드로 우리팀은 전기차를 주제로 선정했기 때문에 관련 웹페이지에서 2019-2024년도 정보를 가져와야 했는데, select 선택자에서 연도를 선택한 뒤, 아래 보이는 지자체 차종별 보조금을 클릭하면 열리는 새 창의 정보를 가져와야했다.

그런데 데이터를 가져올 때 문제가 두가지 있었다. 첫번째 문제는 아래 사진에서 확인 가능한데 2020, 2021년도를 비교 했을 때, column이 한칸 줄어들었다. 그래서 2019-2020년도의 데이터 수집과 2021-2024년도 데이터 수집 방법을 다르게 주어야 했고 if-elif문으로 나누어줬다.

두번째 문제는 생각보다 시간을 잡아 먹었던 것인데 데이터를 가져올 열의 인덱스 문제였다. 내가 가져오려던 데이터는 시도/지역구분/보조금(승용)/보조금(초소형) 이렇게 네가지였는데 위의 2020년도 사진에서 보이듯 0번에서 3번째 인덱스인 줄 알았으나!!! 데이터가 제대로 들어오지 않아 팀원에서 물어보니 인덱스가 0,1,3,4 였다.


이 문제는 개발자 도구에서 쉽게 확인해 볼 수 있었는데 자세히 보면 빨간색 박스 안에 <td> 태그 하나가 class="tr_car_btn" style="display:none"으로 사용자 화면에 노출되지 않고 숨겨져 있었다. (이 태그가 21년도 이후부터 "모델별 지방비 조회" 라는 버튼으로 사용자 화면에 보여진다. 즉, 사용자 화면에서는 column의 갯수가 한칸 줄었지만 인덱스는 같아서 무척 헷갈렸다.)

이렇듯 처음 마주한 문제들과 씨름하느라 6시간이나 걸렸지만 오히려 데이터를 가져올 때 고려해야할 사항이라던가 미처 생각지 못했던 것들을 알게 되었다. 그리고 결국 문제를 해결하고 정상 작동하는 코드를 완성했다. 뿌듯-!

오늘의 일기👩‍💻

사실 selenium은 월요일에 배웠지만 나는 팀프로젝트 때문에 하루 전날인 일요일에 예습 할 겸 무한 구글링 + chat gpt의 도움을 받아 크롤링을 시도해 보았다. 덕분에 월요일 수업 내용이 더 재밌게 느껴지기도 했고 비록 내 코드가 프로젝트에 쓰이진 않았지만 평소 2-30줄의 코드만 쓰다가 100줄이 넘는 코드를 완성했다는 뿌듯함을 얻음과 동시에 많은 공부가 되었던 하루였다.

profile
기록은 기억을 지배한다.

0개의 댓글