오늘 한 것: 출발 시간표 크롤링 - 걸린시간이 전부 있는 버스 40개의 도착 예정 시간을 구하려고 하니.. 출발 시간 데이터가 없었다.. 평일, 주말에 언제언제 기점에서 출발하는 시간이다. 그래서 구미 BIS(버스시스템) 홈페이지에서 크롤링을 할 수 밖에 없었다
<버스 출발 시간표 크롤링>
프로세스
크게 3가지 단계
웹 드라이버 생성 -> HTML 태그에서 정보 찾아서 리스트에 담기 -> 시간표 웹 사이트로 이동해서 정보 크롤링
자세히는 9가지 단계이다.
- 셀레니움으로 웹드라이버 생성(나 대신 웹페이지 열어서 크롤링 해줄 친구)
- 웹페이지 HTML에서 모든 버스 노선 찾기
- 버스 노선별로 시간표로 들어가는 URL 찾기(웹페이지 한 번 더 열어야 시간표 확인 가능)
- 버스별로 번호, 주요경유지, 시간표 URL, filename 만들기
- 시간표 수집함수 정의
- 평일 시간표 수집
- 평일 시간표 전처리
- 주말 시간표 수집
- 주말 시간표 전처리
셀레니움으로 웹 드라이버를 생성하여 진행
- bis.gumi.go.kr 구미 버스시스템 사이트로 이동
HTML 태그 찾기
우클릭 - 검사로 HTML 태그 확인 가능
복잡해보이지만 2가지만 기억
- Elements = 웹페이지의 HTML 정보 다 들어있음
- 마우스 갖다대면 HTML 문장이 웹페이지의 어떤 부분인지 확인 가능 -> 이걸로 내가 크롤링 하기 원하는 것 찾기
- 노션 단추 내리듯이 단추를 계속 내려서 내가 원하는 element copy -> 해당 요소까지 접근하는 모든 HTML 태그 복사됨
버스 리스트 먼저 찾기
시간표 링크를 사이트에 담기
# 시간표 사이트 담기
from urllib.parse import urljoin
base_url = "https://bis.gumi.go.kr"
bus_timetable_links = []
for row in rows:
tds = row.find_elements(By.TAG_NAME, "td")
if tds and len(tds) >= 6:
bus_number = tds[0].text.strip()
route_name = tds[1].text.strip() # 주요 경유지 추출
time_link_tag = tds[5].find_element(By.TAG_NAME, "a")
relative_url = time_link_tag.get_attribute("href")
full_url = urljoin(base_url, relative_url)
# 저장 (번호, 경유지, 링크)
bus_timetable_links.append((bus_number, route_name, full_url))
# 출력 확인
for bus, route, link in bus_timetable_links:
print(f"{bus} | {route} → {link}")
- 결과
마찬가지로 HTML에서 크롤링하여 버스별 정보 리스트 만들기
시간표 수집함수 만들기
# 시간표 수집 함수
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from selenium.webdriver.common.by import By
import time
def get_timetable_by_day(driver, info, tab_index, day_label):
"""
info: bus_info_list의 한 항목 (dict)
tab_index: 0=평일, 1=토요일, 2=공휴일
day_label: 'weekday', 'holiday' 등
"""
data = []
driver.get(info["url"])
try:
WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".tab a"))
)
tabs = driver.find_elements(By.CSS_SELECTOR, ".tab a")
if tab_index < len(tabs):
tabs[tab_index].click()
time.sleep(0.5)
WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CLASS_NAME, "common_tb"))
)
table = driver.find_element(By.CLASS_NAME, "common_tb")
rows = table.find_elements(By.TAG_NAME, "tr")
for row in rows[1:]:
tds = row.find_elements(By.TAG_NAME, "td")
if len(tds) >= 2:
seq1 = tds[0].text.strip()
time1 = tds[1].text.strip()
if seq1 and time1:
data.append({
"bus_number": info["bus_number"],
"route_name": info["route_name"],
"departure": info["departure"],
"arrival": info["arrival"],
"sequence": seq1,
"departure_time": time1,
"day": day_label
})
if len(tds) >= 4:
seq2 = tds[2].text.strip()
time2 = tds[3].text.strip()
if seq2 and time2:
data.append({
"bus_number": info["bus_number"],
"route_name": info["route_name"],
"departure": info["departure"],
"arrival": info["arrival"],
"sequence": seq2,
"departure_time": time2,
"day": day_label
})
print(f"[OK] {day_label} | {info['bus_number']} | {info['departure']} → {info['arrival']} | {len(data)}건")
except (TimeoutException, NoSuchElementException):
print(f"[!] 수집 실패: {day_label} | {info['bus_number']} | {info['departure']} → {info['arrival']}")
return data
해당 함수를 활용하여 수집 -> DF -> 전처리
# 평일 시간표 수집
from tqdm import tqdm
weekday_data = []
for info in tqdm(bus_info_list):
result = get_timetable_by_day(driver, info, tab_index=0, day_label="weekday")
weekday_data.extend(result)
# DataFrame으로 변환
import pandas as pd
df_weekday = pd.DataFrame(weekday_data)
df_weekday.to_csv("gumi_bus_timetable_weekday_with_departure.csv", index=False, encoding="utf-8-sig")
- 13번 버스는 주요경유지(route_name)에 운행 정보가 담겨있어서 출발 시간표를 따로 만들어줌
# time_13 times_13 = expand_schedule("06:00", "23:20", 1.5)
13번 평일용 시간표 만들기
data_13_weekday = []
for i, t in enumerate(times_13, 1):
data_13_weekday.append({
"bus_number": "13",
"route_name": "06:00부터23:20 까지 약1.5분 간격 운행",
"departure": "구미역", # 필요 시 실제 값으로 수정
"arrival": "구미역", # 필요 시 실제 값으로 수정
"sequence": str(i),
"departure_time": t,
"day": "weekday"
})
기존 dummy 제거
df_weekday_cleaned = df_weekday[~(
(df_weekday['bus_number'] == '13') &
(df_weekday['departure_time'] == '00:00')
)]
추가
df_weekday_final = pd.concat([df_weekday_cleaned, pd.DataFrame(data_13_weekday)], ignore_index=True)
저장
df_weekday_final.to_csv("gumi_bus_timetable_weekday_final.csv", index=False, encoding="utf-8-sig")
```
- 총 3100개 정도의 평일 출발시간표가 만들어짐
- 같은 과정을 거쳐 총 3100개 정도의 주말 출발시간표가 만들어짐
'Today I Learned' 카테고리의 다른 글
[TIL] 25.03.20 구미 정류소 좌표 데이터 다시 정리 (0) | 2025.03.20 |
---|---|
[TIL] 25.03.19 노선, 좌표 데이터 무결성 확인 (0) | 2025.03.19 |
[TIL] 25.03.18 Geopandas를 사용하여 점 좌표를 선 좌표에 매핑하기 (0) | 2025.03.19 |
[TIL] 25.03.15 구미버스 도착시간 데이터 요청 (0) | 2025.03.15 |
[TIL] 25.03.13 API로 불러온 데이터 하나로 합치기 (0) | 2025.03.13 |