이번 시간은 웹을 직접 켜야지만 데이터를 받을 수 있는 동적 웹페이지 크롤링을 해보겠습니다.
(오피넷 관계자님 혹시 너무 트래픽이 많아지는 것 같으면 말씀 주세요... 근데 조회수가 적어서 그럴 일은 없을 겁니다..ㅠ)
웹 스크래핑을 하다 보면, 단순히 URL만 요청해서는 원하는 데이터를 얻을 수 없는 사이트들을 자주 만나게 됩니다.
로그인이 필요하거나, 버튼을 클릭해야 데이터가 나타나고, 스크롤을 내려야 추가 정보가 로드되는 등 사용자의 개입(Interaction)이 강하게 요구되는 곳들이죠.
이런 난이도 높은 '동적 웹페이지'를 크롤링할 때 사용하는 가장 강력하고 대중적인 도구가 바로 Selenium(셀레니움)입니다.
오늘은 Selenium을 이용해 오피넷(한국석유공사)의 전국 주유소 가격 데이터를 자동으로 수집(다운로드)하는 봇을 만들어보겠습니다.
1. 웹 동작 방식의 이해 (사전 지식)
Selenium 코드를 짜기 전에, 타겟 사이트가 어떻게 움직이는지 이해하는 것이 매우 중요합니다.
- Form 전송 (동기 통신): 검색 버튼 등을 눌렀을 때 주소창에 옆 X 표시가 잠깐 나타나며 화면이 완전히 새로고침(깜빡임) 되는 방식입니다. 이때 주의할 점은 새로운 페이지가 요청되면서 기존 메모리에 있던 HTML 구조(DOM Tree)가 모두 초기화된다는 것입니다. 기존에 잡아두었던 요소(Element) 객체들을 재사용하려고 하면 에러(StaleElementReferenceException)가 발생합니다.
- Ajax 전송 (비동기 통신): 화면 깜빡임 없이 백그라운드에서 데이터를 몰래 가져와 화면 일부만 갱신하는 방식입니다. 구글 검색창에서 타자를 칠 때 밑에 추천 검색어가 뜨는 것이 대표적입니다.
- Selenium의 대기 시간(Time 텀) 조절: Selenium은 실제 브라우저를 띄워 사람처럼 클릭하고 타자를 칩니다. 브라우저가 화면을 갱신하기도 전에 파이썬 코드가 다음 클릭을 명령하면 에러가 납니다. 따라서 네트워크 지연과 렌더링 시간을 고려한 적절한 대기(Sleep / Wait)가 웹 자동화의 핵심 성공 요인입니다.
💡 고스트(Headless) 모드란? 테스트 단계에서는 눈으로 브라우저가 움직이는 것을 보는 것이 좋지만, 실제 서버에 올려놓고 매일 밤 12시 10분(배치 방식)에 수집을 돌릴 때는 굳이 화면을 띄울 필요가 없습니다. 화면 렌더링을 끄고 백그라운드에서 빠르게 동작시키는 방식을 Headless 모드라고 부르며, 탐지를 우회하기 위해 selenium-stealth 같은 추가 라이브러리를 함께 사용하기도 합니다.
2. 프로젝트 목표 및 도구 준비

- 목표: 로컬 PC 환경에서 오피넷(https://www.opinet.co.kr/searRgSelect.do)에에) 접속하여, 서울시 26개 구의 주유소 가격 정보를 담은 엑셀 파일을 모두 다운로드(Extract) 합니다.
- 설치 라이브러리:
# 터미널 환경에서 설치
pip install selenium
- 웹 드라이버 준비: 과거에는 Chrome 브라우저 버전에 맞는 chromedriver.exe 파일을 직접 다운로드하여 관리해야 했으나, 최근 Selenium 버전에서는 내부적으로 크롬 드라이버 매니저가 내장되어 별도의 설정 없이 바로 크롬을 띄울 수 있게 되었습니다.
3. Selenium 스크래핑 실전 코드
전체 코드를 단계별로 살펴보며 셀레니움의 문법을 익혀보겠습니다.
Step 1: 웹 드라이버 실행 및 사이트 접속
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
# 1. WebDriver 새 세션 생성 (Chrome 브라우저 팝업됨)
driver = webdriver.Chrome()
# 2. 원하는 페이지(오피넷 주유소 지역별 검색)로 이동
driver.get('https://www.opinet.co.kr/searRgSelect.do')
Step 2: 지역(시/도) 정보 추출
화면 구조를 살펴보면 시/도를 선택하는 콤보박스(<select>)의 id가 SIDO_NM0입니다.

# id값이 SIDO_NM0인 요소 내부의 모든 <option> 태그들을 리스트로 가져옵니다.
# By.CSS_SELECTOR를 사용해 직관적인 선택이 가능합니다.
sidos = driver.find_elements(By.CSS_SELECTOR, "#SIDO_NM0 > option")
# 첫 번째 값은 안내 문구("시/도")이므로 제외하고(1부터 시작)
# 실제 데이터에 접근할 때 쓰이는 value 속성값을 추출합니다.
sidos_value = [sido.get_attribute('value').strip() for sido in sidos[1:]]
print("수집할 시/도 목록:", sidos_value)
Step 3: 시/군/구 순회 및 엑셀 다운로드 (핵심)
가장 복잡한 로직입니다.
- 시/도(예: 서울)를 선택하면 -> 2) Ajax 통신으로 서울의 구(강남구, 강동구...) 목록을 받아와 화면이 갱신됩니다. -> 3) 갱신된 구 목록을 바탕으로 다시 반복문을 돌며 각각의 구를 선택하고 -> 4) 엑셀 저장 버튼을 누릅니다.
이 과정마다 적절한 time.sleep()을 부여하는 것이 핵심입니다.


# 전체 수집 시 시간이 오래 걸리므로, 이번 예제에서는 서울시(sidos_value[:1])만 진행합니다.
for sido_nm in sidos_value[:1]:
print(f"[{sido_nm}] 지역 탐색 시작")
# 1. 콤보박스에 강제로 시/도 값을 입력하여 이벤트를 발생시킵니다.
# 사용자가 마우스로 클릭해서 고르는 행위와 동일합니다.
driver.find_element(By.ID, 'SIDO_NM0').send_keys(sido_nm)
# 2. Ajax 비동기 통신 후 '시/군/구' 데이터가 화면에 세팅될 때까지 잠시 대기합니다.
time.sleep(2)
# 3. 갱신된 DOM 트리에서 시/군/구의 실제 value 값들을 추출합니다.
sigungus = [
sido.get_attribute('value').strip()
for sido in driver.find_elements(By.CSS_SELECTOR, "#SIGUNGU_NM0 > option")[1:]
]
print(f"{sido_nm}의 하위 행정구역: {sigungus}")
# 각 시/군/구를 순회하며 엑셀 파일을 다운로드합니다. (테스트용으로 1개만 진행)
for sigungu_value in sigungus[:1]:
print(f" -> {sigungu_value} 선택 및 다운로드 요청")
# 3-1. 시/군/구 콤보박스에 값 세팅 (Form 전송 발생)
driver.find_element(By.ID, "SIGUNGU_NM0").send_keys(sigungu_value)
# 3-2. 화면 렌더링 세팅 완료 대기
time.sleep(3)
# 3-3. 엑셀 다운로드 버튼 클릭
# HTML 상에서 해당 버튼의 class 속성이 'btn_type6_ex_save' 임을 개발자 도구로 확인했습니다.
driver.find_element(By.CLASS_NAME, "btn_type6_ex_save").click()
# 3-4. 엑셀 파일 다운로드가 완료될 때까지 대기
time.sleep(3)
# 4. 모든 작업이 끝나면 뒷정리
# driver.close()는 현재 활성화된 탭만 닫습니다.
# 전체 브라우저 세션과 프로세스를 메모리에서 완전히 종료하기 위해 quit()을 권장합니다.
driver.quit()
이렇게 다운로드된 수십 개의 엑셀 파일들은 앞서 다루었던 Pandas 라이브러리를 통해 하나의 DataFrame으로 병합(Transform)한 뒤, 데이터베이스나 새로운 통합 CSV 파일로 저장(Load)하면 훌륭한 주유소 가격 ETL 파이프라인이 완성됩니다.
자동화 봇을 구축할 때는 언제나 서버에 과부하를 주지 않는 매너(타임 슬립 설정 등)를 지키는 것을 잊지 마세요!
아 그리고 저 코드를 한 번에 실행하려면 웹이 켜지는 것과 로딩될 때도 time.sleep을 걸어야 합니다.
시간 간격을 주지 않으면 웹이 켜지기도 전에 웹 크롤링을 해서 데이터를 받아오지 못하니까
웹이 켜지는 시간도 계산해서 진행하시는 것이 맞을 것 같습니다.
(아마 시간 간격을 주지 않고 진행해 보시면 느낌을 아실 겁니다.)
'코딩 개발 > Python' 카테고리의 다른 글
| Python - 데이터수집처리 (적재) 3 (0) | 2026.07.28 |
|---|---|
| Python - 데이터수집처리 (feat. 네이버 finance 환율) 2 (0) | 2026.07.27 |
| Python - 데이터수집처리 (feat. 네이버 finance 환율) 1 (0) | 2026.07.24 |
| Python - 데이터 수집, 처리, 적재 (ETL) (feat. NAVER Developer 어플리케이션 사용 법, Google Colab에서 API 사용) (1) | 2026.07.23 |
| Python - 모듈 (from, import) (0) | 2026.07.22 |