매일 아침 버스 앱을 켜서 도착 시간을 확인하는 루틴이 귀찮아졌습니다. 내가 자주 타는 버스 몇 개의 도착 시간을 정해진 시간에 카카오톡으로 받으면 어떨까? 그게 이 프로젝트의 시작이었습니다.
서울 버스와 경기 버스 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만 바꾸면 됩니다.