본문 바로가기
데이터 시각화 및 애플리케이션 개발

[데이터 시각화] 서울시 따릉이 API를 이용한 실시간 잔여 자전거 대수 확인하기

by 바다의 공간 2024. 6. 27.

실시간으로 서울시에 있는 역에 따릉이가 몇 대 대여됐는지 잔여 자전거 대수를 확인해보려고 합니다.


1. 따릉이 api

(https://www.bikeseoul.com/app/station/getStationRealtimeStatus.do)

형태는 JSON형태로 되어있습니다.

필요한것들을 임포트 하겠습니다.

import requests
import folium
import json
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
  • warnings.filterwarnings('ignore') == warnings을 안뜨게하는 명령어입니다.
데이터 값 내용
stationName  대여소 이름
stationId  고유한 대여소 번호
stationLongitude  대여소 경도
stationLatitude  대여소 위도
rackTotCnt  주차 가능한 전체 자전거 대수
parkingBikeTotCnt  주차된 따릉이 총 대수
parkingQRBikeCnt  주차된 따릉이 qr형 총 대수
parkingELECBikeCnt  주차된 새싹 따릉이 총 대수

 


1-1. 데이터 요청하기

targetSite = 'https://www.bikeseoul.com/app/station/getStationRealtimeStatus.do'
#역데이터 정보 모두 가져오기
request =requests.post(targetSite, data={'stationGrpSeq':'ALL'})
# print(request)
print(request.text)
{"stationImgPath":"/nas_link/spb/attachFiles/file_admin/basePath","appUserSessionVO":{"encPwd":null,"usrClsCd":null,"usrDeviceId":null,"usrSeq":null,"voucherEndDttm":null,"voucherSeq":null,"usrType":null,"loginId":null,"usrMpnNo":null,"snsType":null,"mbId":null,"mpnLostYn":null,"appOsType":null,"regDttm":null,"lang":"LAG_001","orgType":null,"snsId":null,"viewFlg":"list","usrBirthDate":null,"sexCd":null,"rentEncPwd":null,"telecomClsCd":null,"authCiVal":null,"authClsCd":null,"mbTelNo":null,"mbEmailName":null,"mbPostNo":null,"mbAddr1":null,"mbAddr2":null,"parentSexCd":null,"parentBirthDate":null,"parentMpnNo":null,"emailSendAgreeYn":null,"lastLoginDttm":null,"mbWgt":null,"langClsCd":null,"leaveYn":null,"leaveReasonCd":null,"leaveDttm":null,"mbInfoColecAgreeDttm":null,"mpnLostDttm":null,"pagingYn":null,"usrIp":null,"partyVoucherSeq":null,"elecVoucherSeq":null,"requestSeq":null,"usrDeviceType":null,"authDiVal":null,"snsEmail":null,"tCardYn":null,"mCardYn":null,"mainType":null,"firstRecordIndex":0,"totalRecordCount":0,"recordCountPerPage":0,"pageSize":0,"currentPageNo":1,"searchValue":null,"searchParameter":null,"searchDate":null,"searchStartDate":null,"searchEndDate":null,"mode":null},"loginYn":"N","realtimeList":[{"stationId":"ST-4","stationName":"102. 망원역 1번출구 앞","stationImgFileName":"","stationLongitude":"126.91062927","stationLatitude":"37.55564880","rackTotCnt":"15","parkingBikeTotCnt":"0","parkingQRBikeCnt":"9","parkingELECBikeCnt":"2","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-5","stationName":"103. 망원역 2번출구 앞","stationImgFileName":"","stationLongitude":"126.91083527","stationLatitude":"37.55495071","rackTotCnt":"14","parkingBikeTotCnt":"0","parkingQRBikeCnt":"18","parkingELECBikeCnt":"1","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-6","stationName":"104. 합정역 1번출구 앞","stationImgFileName":"","stationLongitude":"126.91508484","stationLatitude":"37.55073929","rackTotCnt":"13","parkingBikeTotCnt":"0","parkingQRBikeCnt":"0","parkingELECBikeCnt":"0","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-7","stationName":"105. 합정역 5번출구 앞","stationImgFileName":"","stationLongitude":"126.91482544","stationLatitude":"37.55000687","rackTotCnt":"5","parkingBikeTotCnt":"0","parkingQRBikeCnt":"1","parkingELECBikeCnt":"0","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-8","stationName":"106. 합정역 7번출구 앞","stationImgFileName":"","stationLongitude":"126.91282654","stationLatitude":"37.54864502","rackTotCnt":"12","parkingBikeTotCnt":"0","parkingQRBikeCnt":"3","parkingELECBikeCnt":"1","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-9","stationName":"107. 신한은행 서교동지점","stationImgFileName":"","stationLongitude":"126.91850281","stationLatitude":"37.55751038","rackTotCnt":"5","parkingBikeTotCnt":"0","parkingQRBikeCnt":"2","parkingELECBikeCnt":"2","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-10","stationName":"108. 서교동 사거리","stationImgFileName":"","stationLongitude":"126.91861725","stationLatitude":"37.55274582","rackTotCnt":"12","parkingBikeTotCnt":"0","parkingQRBikeCnt":"1","parkingELECBikeCnt":"0","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-15","stationName":"111. 상수역 2번출구 앞","stationImgFileName":"","stationLongitude":"126.92353058","stationLatitude":"37.54787064","rackTotCnt":"10","parkingBikeTotCnt":"0","parkingQRBikeCnt":"2","parkingELECBikeCnt":"0","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-16","stationName":"112. 극동방송국 앞","stationImgFileName":"","stationLongitude":"126.92320251","stationLatitude":"37.54920197","rackTotCnt":"10","parkingBikeTotCnt":"0","parkingQRBikeCnt":"0","parkingELECBikeCnt":"1","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-18","stationName":"113. 홍대입구역 2번출구 앞","stationImgFileName":"","stationLongitude":"126.92382050","stationLatitude":"37.55743790","rackTotCnt":"25","parkingBikeTotCnt":"0","parkingQRBikeCnt":"1","parkingELECBikeCnt":"1","stationSeCd":"RAK_002","mode":null},{"stationId":"ST-20","stationName":..............더보기

가 나오게됩니다. 모든 역의 데이터를 가져오니 확실히엄청 많다는것을 확인할 수 있습니다.

앞서 말씀드렸듯이 json형태로 되어있기떄문에 json데이터를 처리하기로 합니다.

 

requests.post(targetSite, data={'stationGrpSeq':'ALL'})

을 사용하게되면 모든 로그인을 하지않아도 1,000개정도의 데이터를 가져올 수 있습니다.

 


1-2. json 데이터 처리하기

  • json.load() : json타입의 문자열 데이터를 파이썬에서 처리할 수있도록 변환(딕셔너리로변환해줌)
bike_json = json.loads(request.text)
print(bike_json)
print(type(bike_json))

당연히 json형태도 위 값처럼 엄청 많이 나오게 되어있을테고

type은 dict로 변경했으니 잘 변경 되어있는걸 확인할 수 있습니다.

 


1-3. 딕셔너리 타입의 데이터를 데이터프레임으로 변환하기

  • json_normalize(): 딕셔너리 자료구조의 타입의 데이터를 판다스 데이터프레임으로 변환_(판다스에있는 매소드)

지금 bike_json을 가지고 바로 df를 만드려고 하면 있는값들이있고 없는값들이 있어서 애매합니다.

그렇지만 realtimeList는 반복되는 배열이기때문에 df할 수 있습니다.

bike_df = pd.json_normalize(bike_json, 'realtimeList')
bike_df

여기서 이해가 가지않았던 부분은 보통 bike_json안의 realtimeList리스트를 가져오는것이기에 , 가 아닌 . 으로 불어와야한다고 생각했는데 여기는 함수의 인자들을 구분하기 위해서 사용됩니다.

 

pd.json_normalize는 함수이고 함수 호출 시 여러개의 인자를 전달 할 수 있습니다.

bike_json는 데이터 전체를 나타내는 첫번째 인자이고 realtimeList는 JSON데이터안에서 평탄화할 리스트의 키를 나타내는 두번째 인자가 됩니다. 

 

pd.json_normalize는 JSON 데이터 구조에서 특정 키('realtimeList')를 찾아 그 안의 리스트를 평탄화하여 데이터프레임으로 만드는 함수입니다. 이 코드에서 pd.json_normalize 함수가 정확히 그 역할을 수행하고 있습니다.

 

즉! bike_josn안에있는 tealtimeList리스트의 인자값을을 가져오는거고 그 값들을 df로 만든 결과물이 바로 아래입니다.

( 휴 이해하는데 좀 걸림)

bike_df

 

bike_df.columns

all_columns

그럼 컬럼들을 가져오게되면 총 11개가 나타나게되고 필요한것들만 뽑아오겠습니다. 

총 8개이고 목록은 아래와 같습니다.

* stationName : 대여소 이름
* stationId : 고유한 대여소 번호
* stationLongitude : 대여소 경도
* stationLatitude : 대여소 위도
* rackTotCnt : 주차 가능한 전체 자전거 대수
* parkingBikeTotCnt : 주차된 따릉이 총 대수
* parkingQRBikeCnt : 주차된 따릉이 qr형 총 대수
* parkingELECBikeCnt : 주차된 새싹 따릉이 총 대수

1-4. 쓸 데이터 추리기

bike_df_map = bike_df[['stationName',
                       'stationId',
                       'stationLongitude',
                       'stationLatitude',
                       'rackTotCnt',
                       'parkingBikeTotCnt',
                       'parkingQRBikeCnt',
                       'parkingELECBikeCnt']]
bike_df_map

 

여기서 우리가 보면 위도는 folat, 총 대수는 int64, 이름이나 고유한 대여소 번호는 object로 변경해야 맞을것 같습니다.

그렇지만 지금 dtypes를 찍어보면

bike_df_map.dtypes

 

로 모두 object로 표현됩니다. 그래서 type을 변경해야합니다.

타입 변경은 .astype(바꿀 형)을 사용합니다.

 

1-5. 데이터타입 변경

#형 변환하기
bike_df_map['stationLongitude'] = bike_df_map['stationLongitude'].astype(float)
bike_df_map['stationLatitude'] = bike_df_map['stationLatitude'].astype(float)

bike_df_map['rackTotCnt'] = bike_df_map['rackTotCnt'].astype(int)
bike_df_map['parkingBikeTotCnt'] = bike_df_map['parkingBikeTotCnt'].astype(int)
bike_df_map['parkingQRBikeCnt'] = bike_df_map['parkingQRBikeCnt'].astype(int)
bike_df_map['parkingELECBikeCnt'] = bike_df_map['parkingELECBikeCnt'].astype(int)

그리고 파생변수로 현재 역에 있는 따릉이의 자전거를 모두 더한 total 파생변수를 만들어줍니다.

vike_df_map['total'] =  bike_df_map['parkingBikeTotCnt'] + bike_df_map['parkingQRBikeCnt'] + bike_df_map['parkingELECBikeCnt']

아직 print를 하지 않았으니 결과값은 나오지 않습니다.

 

여기서 타입이잘 바뀌었는지 확인해보겠습니다.

 

bike_df_map.dtypes

 

잘 변경됐고, 파생변수도 잘 들어갔는지 확인하겠습니다.

bike_df_map.head()

우측 total 파생변수 생성

잘 들어갔고, row가 얼마나있는지 확인해보겠습니다.

bike_df_map.shape

#(2733, 9)

 


1-6. folium을 이용한 지도표시

이제 folium을 이용해서 지도에 표시를 해주겠습니다. 경도 위도는 위에 나와있는 값의 평균을 냈고 zoom_start는 12로 맞췄습니다.

bike_map = folium.Map(location=[bike_df_map['stationLatitude'].mean(),
                                bike_df_map['stationLongitude'].mean()],
                      zoom_start=12)

 

 

for문을 이용하여 데이터 표시하기

▶ popup_str 을 이용하여 일반, qr, 새싹, 총 몇대인지를 넣겠습니다.

.format을 이용하면 뒤에있는 데이터 값이 대체됩니다.

예를들어

  • 첫 번째 {}는 data['stationName'] 값으로 대체됩니다.
  • 두 번째 {}는 data['parkingBikeTotCnt'] 값으로 대체됩니다.
  • 세 번째 {}는 data['parkingQRBikeCnt'] 값으로 대체됩니다.
  • 네 번째 {}는 data['parkingELECBikeCnt'] 값으로 대체됩니다.
  • 다섯 번째 {}는 data['total'] 값으로 대체됩니다.

 

for index, data in bike_df_map.iterrows():
    # print(index, data)
    popup_str = '{} 일반:{}대, QR:{}대, 세싹:{}대, 총:{}대'.format(
        data['stationName'], data['parkingBikeTotCnt'], data['parkingQRBikeCnt'],
        data['parkingELECBikeCnt'],data['total']
    )
    popup = folium.Popup(popup_str, max_width=600)
    folium.Marker(location=[data['stationLatitude'], data['stationLongitude']],
                  popup=popup).add_to(bike_map)

bike_map

 

 

 


매소드 리뷰

매소드 설명
warnings.filterwarnings('ignore') warnings을 안뜨게하는 명령어입니다.
json.load() son타입의 문자열 데이터를 파이썬에서 처리할 수있도록 변환(딕셔너리로변환해줌)
.astype(바꿀 형) dtype을 원하는 형으로 변경해줌.
.format 문자열 포맷팅을 위해 사용됩니다.