공공 API를 활용한 서울시 버스정보 서비스 1

5 분 소요

이번 포스팅에서는 공공데이터 API를 활용한 서울시 버스정보 서비스를 만들어 보겠습니다.

먼저 공공데이터 포털의 여러 버스 API 서비스 중 아래 2개의 서비스를 이용하였습니다.
“서울특별시노선정보조회 서비스”,
“서울특별시정류소정보조회 서비스”
최종적으로는 Tkinter를 통해 Python UI 기반으로 만들어 보겠습니다.

먼저 “서울특별시_노선정보조회 서비스” API를 이용하여 필요한 정보를 읽어 들이기 위한 준비를 해 봅시다.
노선정보조회 서비스의 참고 문서를 확인 하면 아래와 같이 4가지 기능을 확인 할 수 있습니다.

1

1. VO 객체

문서를 참고하여 첫번째로 VO객체(Value Object : 값을 담을 클래스)를 만듭니다.

1번 노선기본정보항목과, 3번 노선번호목록조회의 결과값을 담을 클래스를 다음과 같이 만들었습니다.

class busVo: # 버스 노선 정보를 담을 클래스
    def __init__(self, busRouteId=None, busRouteNm=None, stStationNm=None,\
                 edStationNm=None, term=None, firstBusTm=None, lastBusTm=None, corpNm=None):
        self.busRouteId = busRouteId # 노선 ID
        self.busRouteNm = busRouteNm # 노선명
        self.stStationNm = stStationNm # 기점
        self.edStationNm = edStationNm # 종점
        self.term = term # 배차간격 (분)
        self.firstBusTm = firstBusTm # 금일첫차시간
        self.lastBusTm = lastBusTm # 금일막차시간
        self.corpNm = corpNm # 운수사명

    def __str__(self):
        return 'busRouteId:' + self.busRouteId + ' / busRouteNm:' + self.busRouteNm\
               + ' / stStationNm:' + self.stStationNm + ' / edStationNm:' + self.edStationNm\
               + ' / term:' + self.term + ' / firstBusTm:' + self.firstBusTm\
               + ' / lastBusTm:' + self.lastBusTm + ' / corpNm:' + self.corpNm

서비스 2번 기능인 노선경로목록조회의 결과값을 담을 pointVo객체를 만듭니다.

class pointVo: # 노선 경로 gps 좌표를 담을 클래스
    def __init__(self, no=None, gpsX = None, gpsY=None):
        self.no = no # 순번
        self.gpsX = gpsX # 좌표X
        self.gpsY = gpsY # 좌표Y

    def __str__(self):
        return 'no:' + self.no + ' / gpsX:' + self.gpsX + ' / gpsY:' + self.gpsY

서비스 4번 기능인 노선별경유정료소목록조회의 결과값을 담을 pointVo객체를 만듭니다.

class stationVo:
    def __init__(self, seq=None, stationNm=None, direction=None,\
                 arsId=None, beginTm=None, lastTm=None):
        self.seq = seq  # 순번
        self.stationNm = stationNm  # 정류소 이름
        self.direction = direction  # 진행방향
        self.arsId = arsId  # 정류소 고유번호
        self.beginTm = beginTm  # 정거장의 해당 버스 첫차 시간
        self.lastTm = lastTm  # 정거장의 해당 버스 막차 시간

    def __str__(self):
        return 'seq:' + self.seq + ' / stationNm:' + self.stationNm\
               + ' / direction:' + self.direction + ' / arsId:' + self.arsId\
               + ' / beginTm:' + self.beginTm + ' / lastTm:' + self.lastTm

2. Service 객체

문서의 4가지 기능을 Servcie 객체의 함수를 통해 필요한 값을 읽고 VO객체에 담을 수 있게 구현합니다.


먼저 공공API 사용시 반드시 필요한 serviceKey를 저장하고 URL과 파라미터에 따라서 API의 XML 값을 BeautifulSoup형태로 반환 할 수 있게 합니다.
각각의 기능에서 공통적으로 사용되는 기능이기에 함수로 빼서 구현하였습니다.

import requests
from bs4 import BeautifulSoup

class busRouteInfoService:
    def __init__(self):
        self.api_key = 'qllrU5q/Iy5IEY7QPdyk29YFEKziTWkVrdkGJEGW4bYjZF19Wpbm7P6jel3RuGrAmWWX+HcBVCxYsKFAsxsh2w=='
        self.url = 'http://ws.bus.go.kr/api/rest/busRouteInfo/'

    # 공공 API에 입력한 URL, 파라미터, 파라미터 값의 결과를 bs 형태로 반환한다.
    def getRequestsByParam(self, subURL, param, param_val):
        request_URL = self.url + subURL
        request_param = {'serviceKey': self.api_key, param: param_val}
        html = requests.get(request_URL, params=request_param).text.encode('utf-8')
        content = BeautifulSoup(html, 'lxml-xml')
        return content

첫번째 기능인 아이디에 해당하는 노선 기본정보를 반환하는 기능 입니다.
공공API 에서 결과가 정상적으로 출력 될 시 ‘headerCd’의 값이 0으로 출력되는 것을 이용하여 아래와 같이 조건 확인 후 busVO 객체가 반환 될 수 있도록 구현 합니다.

    # 노선 기본정보 조회
    # 아이디에 해당하는 노선정보를 반환한다. 리턴값은 busVO 객체 한개 반환
    def getRouteInfoItem(self, busRouteId):
        content = self.getRequestsByParam('getRouteInfo', 'busRouteId', busRouteId)
        if content.find('headerCd').get_text() == '0':
            busRouteId = content.find('busRouteId').get_text()
            busRouteNm = content.find('busRouteNm').get_text()
            stStationNm = content.find('stStationNm').get_text()
            edStationNm = content.find('edStationNm').get_text()
            term = content.find('term').get_text()
            firstBusTm = content.find('firstBusTm').get_text()
            lastBusTm = content.find('lastBusTm').get_text()
            corpNm = content.find('corpNm').get_text()
            return busVo(busRouteId, busRouteNm, stStationNm, edStationNm,\
                     term, firstBusTm, lastBusTm, corpNm)
        else:
            print('대상 없음')
            return None

두번째 기능인 노선의 지도상 경로를 반환하는 기능 입니다.
전체적인 흐름은 첫번째 기능과 비슷 하지만 gps의 위도 경도가 여러개의 값이 존재함으로 API에서 ‘itemList’를 find_all() 을 이용하여 전부 검색 한 후 각각의 ‘itemList’ 내의 위도 경도를 pointVO에 담은 후 리스트 형태로 저장하여 반환 합니다.

    # 노선의 지도상 경로를 리턴한다.
    # 아이디에 해당하는 노선의 형상 목록을 조회한다. 리턴값은 pointVO 객체 들을 리스트에 담아서 반환
    def getRoutePathList(self, busRouteId):
        content = self.getRequestsByParam('getRoutePath', 'busRouteId', busRouteId)
        if content.find('headerCd').get_text() == '0':
            pointVoList = []
            itemList = content.find_all('itemList')
            for i in itemList:
                no = i.find('no').get_text()
                gpsX = i.find('gpsX').get_text()
                gpsY = i.find('gpsY').get_text()
                pointVoList.append(pointVo(no, gpsX, gpsY))
            return pointVoList
        else:
            print('대상 없음')
            return None

남은 기능인 노선번호에 해당하는 노선목록 조회 기능과 노선별 경유 정료소 조회 서비스 기능입니다.
앞서 만든 기능과 전체적인 흐름은 비슷함으로 설명은 생략 하겠습니다.

    # 노선번호에 해당하는 노선 목록 조회. 리턴값은 busVO 객체 들을 리스트에 담아서 반환
    def getBusRouteList(self, strSrch):
        content = self.getRequestsByParam('getBusRouteList', 'strSrch', strSrch)
        if content.find('headerCd').get_text() == '0':
            busVOList = []
            itemList = content.find_all('itemList')
            for i in itemList:
                busRouteId = i.find('busRouteId').get_text()
                busRouteNm = i.find('busRouteNm').get_text()
                stStationNm = i.find('stStationNm').get_text()
                edStationNm = i.find('edStationNm').get_text()
                term = i.find('term').get_text()
                firstBusTm = i.find('firstBusTm').get_text()
                lastBusTm = i.find('lastBusTm').get_text()
                corpNm = i.find('corpNm').get_text()
                busVOList.append(busVo(busRouteId, busRouteNm, stStationNm,\
                              edStationNm, term, firstBusTm, lastBusTm, corpNm))
            return busVOList
        else:
            print('대상 없음')
            return None

    # 노선별 경유 정류소 조회 서비스
    # 노선ID에 해당하는 경유 정류소 목록을 조회한다. 리턴값은 stationVo 객체 들을 리스트에 담아서 반환
    def getStaionsByRouteList(self, busRouteId):
        content = self.getRequestsByParam('getStaionByRoute', 'busRouteId', busRouteId)
        if content.find('headerCd').get_text() == '0':
            stationVoList = []
            itemList = content.find_all('itemList')
            for i in itemList:
                seq = i.find('seq').get_text()
                stationNm = i.find('stationNm').get_text()
                direction = i.find('direction').get_text()
                arsId = i.find('arsId').get_text()
                beginTm = i.find('beginTm').get_text()
                lastTm = i.find('lastTm').get_text()
                stationVoList.append(stationVo(seq, stationNm,\
                                        direction,arsId, beginTm, lastTm))
            return stationVoList
        else:
            print('대상 없음')
            return None

최종적으로 각 기능의 결과를 확인해 보면 다음과 같이 출력 되는 것을 확인 할 수 있습니다.

# 1. getRouteInfoItem
def main():
    busService = busRouteInfoService()
    print(busService.getRouteInfoItem('100100112'))
main()

busRouteId:100100112 / busRouteNm:721 / stStationNm:북가좌동 / edStationNm:건대입구역 / term:8 / firstBusTm:20210712042000 / lastBusTm:20210712224000 / corpNm:서부운수 02-372-0221

# 2. getRoutePathList
def main():
    busService = busRouteInfoService()
    rplist = busService.getRoutePathList('100100112')
    for i in rplist:
        print(i)
main()

no:1 / gpsX:126.909074 / gpsY:37.582225
no:2 / gpsX:126.909116 / gpsY:37.582202
no:3 / gpsX:126.909523 / gpsY:37.581984
no:4 / gpsX:126.909592 / gpsY:37.581947
no:5 / gpsX:126.909634 / gpsY:37.581924
… 중간결과 생략 …
no:1416 / gpsX:126.909886 / gpsY:37.577744
no:1417 / gpsX:126.909746 / gpsY:37.577824
no:1418 / gpsX:126.909688 / gpsY:37.577857
no:1419 / gpsX:126.909724 / gpsY:37.577795
no:1420 / gpsX:126.909746 / gpsY:37.577824
no:1421 / gpsX:126.912022 / gpsY:37.580754

# 3. getBusRouteList
def main():
    busService = busRouteInfoService()
    brlist = busService.getBusRouteList('350')
    for i in brlist:
        print(i)
main()

busRouteId:100100553 / busRouteNm:350 / stStationNm:복정역환승센터 / edStationNm:노들역 / term:12 / firstBusTm:20210712040000 / lastBusTm:20210712223000 / corpNm:한서교통 02-407-8505
busRouteId:224000054 / busRouteNm:3500시흥 / stStationNm:오이도차고지 / edStationNm:서울대입구역 / term:30 / firstBusTm:20210712051000 / lastBusTm:20210712235500 / corpNm:경기
busRouteId:236000176 / busRouteNm:3500포천 / stStationNm:차고지대기(경유) / edStationNm:건대역 / term:40 / firstBusTm:20210712052000 / lastBusTm:20210712231000 / corpNm:경기

# 4. getStaionsByRouteList
def main():
    busService = busRouteInfoService()
    sbrlist = busService.getStaionsByRouteList('100100112')
    for i in sbrlist:
        print(i)
main()

seq:1 / stationNm:서부운수기점 / direction:건대입구역 / arsId:13285 / beginTm:04:20 / lastTm:22:40
seq:2 / stationNm:북가좌2동주민센터 / direction:건대입구역 / arsId:13175 / beginTm:04:20 / lastTm:02:50
seq:3 / stationNm:DMC래미안e.편한세상2.4단지 / direction:건대입구역 / arsId:13183 / beginTm:04:20 / lastTm:02:51
seq:4 / stationNm:북가좌삼호.DMC아이파크아파트 / direction:건대입구역 / arsId:13185 / beginTm:04:21 / lastTm:22:41
seq:5 / stationNm:DMC파크뷰자이.별동상가 / direction:건대입구역 / arsId:13189 / beginTm:04:23 / lastTm:22:43
… 중간결과 생략 …
seq:86 / stationNm:동부센트레빌아파트 / direction:북가좌동 / arsId:13188 / beginTm:06:41 / lastTm:01:06
seq:87 / stationNm:북가좌삼호.DMC아이파크아파트 / direction:북가좌동 / arsId:13186 / beginTm:06:43 / lastTm:01:09
seq:88 / stationNm:모래내우체국.증가교회 / direction:북가좌동 / arsId:13184 / beginTm:06:45 / lastTm:01:09
seq:89 / stationNm:북가좌초교사거리 / direction:북가좌동 / arsId:13264 / beginTm:06:45 / lastTm:01:09
seq:90 / stationNm:북가좌2동주민센터 / direction:북가좌동 / arsId:13174 / beginTm:06:46 / lastTm:01:11


이번 포스팅 에서는 공공 API를 이용하여 객체지향적으로 각각의 기능별로 구현을 해 보았습니다.

다음 포스팅에서는 이번 코드를 이용하여 Tkinter를 통해 UI로 출력해 보겠습니다. 감사합니다.