개발 블로그
← 블로그 목록

공공 API로 버스 도착 알림 시스템 만들기 — 서울·경기 버스


매일 아침 버스 앱을 켜서 도착 시간을 확인하는 루틴이 귀찮아졌습니다. 내가 자주 타는 버스 몇 개의 도착 시간을 정해진 시간에 카카오톡으로 받으면 어떨까? 그게 이 프로젝트의 시작이었습니다.

서울 버스와 경기 버스 API가 다르다

처음에는 API 하나면 될 줄 알았는데, 서울 버스와 경기 버스는 운영 주체가 다르고 API도 별개입니다.

  • 서울 버스: 서울 열린데이터광장 (data.seoul.go.kr)
  • 경기 버스: 경기도 공공데이터포털 (data.gg.go.kr)

응답 형식도 다르고 요청 파라미터도 달라서 각각 별도로 구현해야 했습니다. Factory 패턴으로 버스 종류에 따라 적절한 서비스를 선택하도록 설계했습니다.

서비스 구조

// BusServiceFactory
public BusService getService(String region) {
    return switch (region) {
        case "SEOUL" -> seoulBusService;
        case "GYEONGGI" -> gyeonggiBusService;
        default -> throw new IllegalArgumentException("Unknown region: " + region);
    };
}

// BusService 인터페이스
public interface BusService {
    List<BusArrival> getArrival(String stationId, String routeId);
}

서울 버스 API 호출

서울시 버스도착정보 API는 정류장 ID와 노선 ID를 파라미터로 받아서 도착 예정 시간을 초 단위로 반환합니다.

public List<BusArrival> getArrival(String stationId, String routeId) {
    String url = "http://ws.bus.go.kr/api/rest/arrive/getArrInfoByRouteAll"
        + "?ServiceKey=" + serviceKey
        + "&stId=" + stationId
        + "&busRouteId=" + routeId
        + "&ord=1";

    ResponseEntity<String> resp = restTemplate.getForEntity(url, String.class);
    return parseSeoulResponse(resp.getBody());
}

private List<BusArrival> parseSeoulResponse(String xml) {
    // XML 파싱 후 도착 정보 추출
    // arrmsg1: "3분 후", arrmsg2: "12분 후" 형식으로 반환됨
}

경기 버스 API 호출

경기 버스 API는 응답 구조가 서울과 다르고, 인증 방식도 약간 차이가 있습니다.

public List<BusArrival> getArrival(String stationId, String routeId) {
    String url = "https://apis.data.go.kr/6410000/busarrivalservice/getBusArrivalItem"
        + "?serviceKey=" + serviceKey
        + "&stationId=" + stationId
        + "&routeId=" + routeId;

    ResponseEntity<String> resp = restTemplate.getForEntity(url, String.class);
    return parseGyeonggiResponse(resp.getBody());
}

카카오톡 메시지 포맷

도착 정보를 읽기 좋게 정리해서 카카오톡으로 전송합니다. 여러 버스 노선의 정보를 하나의 메시지로 묶어서 보냅니다.

// 메시지 예시
🚌 버스 도착 알림 (07:45 기준)

📍 강남역 정류장
• 147번: 2분 후 도착 (8분 후)
• 3422번: 11분 후 도착 (19분 후)

📍 선릉역 정류장
• 간선 340번: 4분 후 도착

스케줄 설정

아침 출근 시간대에만 알림이 오도록 cron 표현식으로 스케줄을 잡았습니다. 주말에는 알림이 오지 않도록 요일도 설정했습니다.

// 평일 아침 7:40, 7:50에만 알림
@Scheduled(cron = "0 40 7 * * MON-FRI")
@Scheduled(cron = "0 50 7 * * MON-FRI")
public void morningBusAlert() throws Exception {
    kakaoBusService.sendBusArrivalAlert();
}

정류장 ID 찾기

API 호출에 필요한 정류장 ID는 공공데이터포털에서 정류장 검색 API로 조회할 수 있습니다. 한 번 조회해서 하드코딩해두면 됩니다. 정류장이 없어지거나 ID가 바뀌는 경우는 드물어서 크게 문제되지 않습니다.

팁: 공공 API 서비스키는 URL 인코딩된 버전을 사용해야 하는 경우가 많습니다. 인증 오류가 나면 서비스키를 URLDecoder로 디코딩해서 사용해보세요.

공공 API 사용 시 주의사항

  • 일일 호출 제한: 공공 API는 일일 호출 횟수 제한이 있습니다. 1분마다 호출하면 금방 초과될 수 있으니 필요한 시간대에만 호출하도록 설계해야 합니다.
  • 서비스 불안정: 공공 API는 간헐적으로 응답이 느리거나 오류가 나는 경우가 있습니다. 예외 처리를 꼼꼼히 해두는 게 좋습니다.
  • XML 응답: 대부분의 공공 API는 XML로 응답합니다. JSON이 아니라서 파싱이 번거롭지만 Jsoup이나 JAXB로 처리할 수 있습니다.

결과

이 시스템 덕분에 아침마다 버스 앱을 켜는 습관이 사라졌습니다. 알림이 오면 집을 나설 타이밍을 바로 알 수 있어서 편합니다. 집 주소와 회사 위치가 바뀌더라도 설정 파일에서 정류장 ID만 바꾸면 됩니다.