기상청 날씨 데이터를 무사히 정복하셨다면, 이제 실생활에 가장 밀접하면서도 데이터의 변화 속도가 빠른 '교통 정보'로 시선을 돌릴 차례입니다. 매일 아침 출근길이나 등교길에 스마트폰 앱으로 확인하는 "버스가 몇 분 뒤에 도착하는지"에 대한 데이터를 내 파이썬 코드로 직접 가져와 모니터링하는 프로그램을 만들어보겠습니다.

국토교통부나 각 지자체(서울시, 경기도 등)에서 제공하는 버스 정보 시스템(BIS) API는 데이터가 실시간으로 수시로 업데이트되기 때문에 주기적인 데이터 수집 로직을 연습하기에 가장 좋은 교재입니다. 다만, 버스 API는 기상청 API와는 또 다른 고유의 식별자(ID) 체계를 가지고 있어서 처음 접근할 때 개념을 명확히 잡지 않으면 엉뚱한 데이터를 출력하기 쉽습니다.

버스 정보 API를 다룰 때 핵심인 3가지 식별자(ID) 개념

버스 도착 정보를 정확하게 끄집어내려면 API 가이드 문서에 등장하는 세 가지 핵심 ID의 차이점을 반드시 머릿속에 가인해야 합니다. 우리가 눈으로 보는 버스 번호나 정류소 이름은 컴퓨터가 인식하지 못하므로, 파이썬 코드에는 고유한 숫자 ID를 넘겨주어야 합니다.

첫 번째는 정류소 ID(Station ID / ARS-ID)입니다. 예를 들어 '강남역'이라는 정류소는 전국에 수없이 많고, 같은 강남역이라도 중앙차로냐 가로변이냐에 따라 위치가 완전히 다릅니다. 따라서 각 정류소마다 부여된 고유한 5자리 또는 9자리의 숫자로 된 ID가 필요합니다. 길거리 정류장 표지판에 적힌 번호를 활용하거나, 사전 조회 API를 통해 얻을 수 있습니다.

두 번째는 노선 ID(Route ID)입니다. 우리가 흔히 타는 '100번 버스', '9401번 버스'는 사람이 부르는 이름(노선명)일 뿐입니다. 시스템 내부적으로 이 버스들을 구별하기 위해 부여한 고유 번호가 노선 ID입니다.

세 번째는 노선별 정류소 순번(Sta Order)입니다. 버스가 기점에서 출발해 종점까지 갈 때, 내가 기다리는 정류소가 몇 번째 순서에 위치해 있는지를 나타내는 인덱스 번호입니다. 특정 버스가 내 정류소에 언제 오는지 콕 집어내려면 이 세 가지 정보가 유기적으로 결합되어야 합니다.

특정 정류소의 버스 도착 정보 수집 실전 코드

아래 코드는 국토교통부(또는 공공데이터포털)에서 제공하는 '전국 버스 도착 정보 조회 서비스'의 구조를 바탕으로, 특정 정류소에 도착 예정인 모든 버스의 정보를 가져와 가독성 있게 정렬하는 파이썬 스크립트입니다.

Python
import requests
import pandas as pd

# 1. 버스 도착 정보 조회 API 주소 및 디코딩 인증키 설정
url = "http://apis.data.go.kr/1613000/ArvlInfoInqireService/getSttnAcctoArvlPregeInfoList"
decoding_key = "여러분의_디코딩_인증키_입력"

# 2. 요청 파라미터 구성
# 실습을 위해 국토교통부 표준 정류소 ID를 예시로 설정했습니다.
#도시코드(예: 서울 11, 경기 31 등)와 정류소ID는 공공데이터 가이드를 참조하세요.
params = {
    'serviceKey': decoding_key,
    'pageNo': '1',
    'numOfRows': '50',
    'dataType': 'JSON',
    'cityCode': '25',        # 예시: 대전광역시 도시코드
    'nodeId': 'DJB8001793'   # 예시: 특정 정류소 고유 ID
}

try:
    response = requests.get(url, params=params, timeout=10)
    response.raise_for_status()
    raw_data = response.json()
    
    # 공공데이터 특유의 데이터 계층 구조 접근
    header = raw_data['response']['header']
    if header['resultCode'] != '00':
        print(f"[서버 오류] 메시지: {header['resultMsg']}")
    else:
        body = raw_data['response']['body']
        
        # 정류소에 대기 중인 버스가 없으면 items가 비어있을 수 있습니다.
        if body['totalCount'] == 0:
            print("현재 해당 정류소에 도착 예정인 버스가 없습니다.")
        else:
            items = body['items']['item']
            
            # 데이터가 1개만 있을 때는 딕셔너리로, 여러 개일 때는 리스트로 오는 경우를 방지
            if isinstance(items, dict):
                items = [items]
                
            parsed_buses = []
            for item in items:
                # 남은 시간을 분과 초로 계산
                arr_time_seconds = int(item['arrtime'])
                minutes = arr_time_seconds // 60
                seconds = arr_time_seconds % 60
                
                parsed_buses.append({
                    '버스번호': item['routeno'],
                    '남은시간': f"{minutes}{seconds}초",
                    '남은정류소수': item['arrprevstationcnt'],
                    '차량유형': item['routeid']  # 필요한 경우 노선 ID 매핑용
                })
            
            # 3. 판다스 데이터프레임 전환 및 화면 출력
            df = pd.DataFrame(parsed_buses)
            # 도착 시간이 짧은 순서대로 정렬
            df = df.sort_values(by='남은시간')
            
            print("\n--- 해당 정류소 실시간 버스 도착 정보 ---")
            print(df.to_string(index=False))
            
            # 4. 엑셀 파일로 임시 백업
            df.to_excel("bus_arrival_info.xlsx", index=False)
            print("\n[성공] 'bus_arrival_info.xlsx' 파일로 데이터가 저장되었습니다.")

except Exception as e:
    print(f"데이터 수집 중 오류가 발생했습니다: {e}")

데이터 수집 시 발생하는 공공데이터 특유의 딕셔너리/리스트 예외 대처법

위 실전 코드를 작성할 때 제가 중간에 집어넣은 isinstance(items, dict) 라는 조건문 구조가 있습니다. 이 부분은 공공데이터 API를 다룰 때 정말 뼈가 되고 살이 되는 팁입니다.

대다수의 공공데이터포털 API들은 결과 데이터가 2개 이상이면 대괄호[]로 감싸진 리스트(List) 형태로 데이터를 보내줍니다. 그래서 우리는 보통 코드 안에서 for item in items: 형태의 반복문으로 데이터를 처리하죠.

그런데 하필 내가 조회한 그 순간에 도착 예정인 버스가 딱 '1대'밖에 없다면, 일부 공공데이터 서버는 대괄호를 빼버리고 그냥 단일 딕셔너리(Dict) 형태로 데이터를 툭 던져줍니다. 이 특성을 모른 채 반복문을 돌리면 파이썬은 딕셔너리의 'Key' 값들을 하나씩 꺼내오는 치명적인 파싱 오류를 일으키며 프로그램이 멈춰버립니다. 따라서 데이터 개수가 유동적인 실시간 API를 다룰 때는 들어온 데이터의 타입이 리스트인지 딕셔너리인지 체크하여 강제로 리스트화해주는 방어벽 코드가 반드시 들어가야 장기간 안정적인 모니터링이 가능합니다.

핵심 요약

  • 버스 정보 API를 다룰 때는 정류소 ID, 노선 ID 등 시스템이 인식하는 고유 식별자 번호를 먼저 파악해야 합니다.

  • 실시간 응답 데이터의 남은 시간(초)은 사용자가 읽기 편하게 분과 초 단위로 변환해 가공하는 단계가 필요합니다.

  • 응답 데이터가 1개일 때와 여러 개일 때 데이터 타입이 리스트와 딕셔너리로 변하는 공공데이터 고유의 예외 상황을 방어 코드로 제어해야 합니다.