ai-creator

OpenAPI 활용 - 날씨 정보를 사용한 맛집 추천 프로젝트 본문

오늘 배워 오늘 쓰는 OpenAPI/프로젝트

OpenAPI 활용 - 날씨 정보를 사용한 맛집 추천 프로젝트

ai-creator 2020. 2. 22. 19:52
반응형

/* 21년 09월 30일 부터 "동네예보"가 "단기예보"로 변경됩니다. 변경버전으로 업데이트 하였습니다." */

<< 문제 정의 >>

"오늘은 뭘 먹어야 하지?"

> "비 오는 날엔 파전이지!"

 

오늘 무엇을 먹어야 할지 고민이 될 때, 달라지는 날씨 정보를 이용해 주변에 맛집을 추천해주는 프로그램이 있다면 얼마나 좋을까요?

 

OpenAPI를 사용해서 만들어보겠습니다!

<< 목표 >>

  • 날씨 정보(기온, 기상, 미세먼지)를 파악하여 맛집 추천해주기

<< 사전 준비 >>

- 카카오 / 네이버 인증 키 발급 

- 카카오앱 사용할 도메인 추가(https://search.naver.com)

 

사용할 API: 공공데이터 포털(단기예보, 대기오염), 네이버 검색(지역) API, 카카오톡(텍스트, 리스트 템플릿)

언어 & 환경(IDE) : Python3.6 & Jupter notebook

예제파일명 : food_recommender.ipynb 

공유폴더 : https://drive.google.com/drive/folders/10KXyPrJ_KEesvAvjnMpx1DS_md7gaN6H?usp=sharing 

<< 구현 순서 >>

 

Step 1 공공데이터포털 API 신청 및 인증키 얻기
Step 2 날씨 정보 가져오기 (공공데이터포털)
Step 3 미세먼지 정보 가져오기 (공공데이터포털 - )
Step 4 날씨에 따른 음식 데이터 구하기
 Step 5 음식점 검색하기 (네이버 검색 OpenAPI)
 ㅑStep 6 카카오톡 보내기

 

Step 1) 공공데이터포털 API 신청 및 인증키 얻기

Step 1-1) 날씨정보

 

국가 공공데이터 포털에 접속해서 [로그인] 혹은 [회원가입]을 합니다.

 

검색 창에 "단기예보"를 입력해줍니다.

검색 결과 [오픈 API] 탭에서 [기상청 날씨예보 정보]로 이동합니다.

 

[활용신청]을 눌러줍니다.

활용목적을 반드시 작성하시고, [단기예보조회]가 선택되어 있는지 확인하고, 라이선스를 확인하여 [동의합니다]를 체크하고 [신청] 버튼을 눌러줍니다.

 

그럼 신청한 서비스를 이용할 수 있는 인증키를 확인하실 수 있습니다. 이 인증키를 잘 보관해주세요!

Step 1-2) 미세먼지

이번에는 미세먼지 정보도 신청해보겠습니다.

검색 창에 "한국환경공단 대기오염"을 검색하면 첫 번째로 검색됩니다.

[활용신청]을 누르시고

 

 

[상세기능정보]에서 [시도별 실시간 평균정보 조회]를 선택하고 아래 라이선스 동의와 [신청] 클릭

인증키를 확인하실 수 있습니다.

 

 

이제 날씨(단기예보)와 미세먼지(대기오염)를 각각 어떻게 가져오는지 확인해보겠습니다.

 

 

 

Step 2) 날씨 정보 가져오기

날씨 정보는 기온기상 상태 정보를 가져오도록 하겠습니다.

참고) 최고 1일 간의 자료만 제공합니다.

개발 가이드 확인법

요청시 필요한 URL 정보와 파라메터는 다음과 같습니다.

여기서 base_time은 문서를 보면 1일 총 8번 다음과 같은 시간에 표출됩니다. 예제는 0800시로 하겠습니다.

지역 정보(X,Y)는 첨부된 엑셀 파일에서 검색할 수 있습니다. 예제는 국민대 주소로 X:60 Y:128로 입력하겠습니다.

 

import requests
import json
import datetime

vilage_weather_url = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst?"

service_key = "YOUR SERVICE KEY"

today = datetime.datetime.today()
base_date = today.strftime("%Y%m%d") # "20200214" == 기준 날짜
base_time = "0800" # 날씨 값

nx = "60"
ny = "128"

payload = "serviceKey=" + service_key + "&" +\
    "dataType=json" + "&" +\
    "base_date=" + base_date + "&" +\
    "base_time=" + base_time + "&" +\
    "nx=" + nx + "&" +\
    "ny=" + ny

# 값 요청
res = requests.get(vilage_weather_url + payload)

items = res.json().get('response').get('body').get('items')
#{'item': [{'baseDate': '20200214',
#   'baseTime': '0500',
#   'category': 'POP',
#   'fcstDate': '20200214',
#   'fcstTime': '0900',
#   'fcstValue': '0',
#   'nx': 60,
#   'ny': 128},
#  {'baseDate': '20200214',
#   'baseTime': '0500',
#   'category': 'PTY',
#   'fcstDate': '20200214',
#   'fcstTime': '0900',
#   'fcstValue': '0',
#   'nx': 60,
#   'ny': 128},
#      'ny': 128},
#     {'baseDate': '20200214'

요청을 완료하면 다음과 같은 값들이 반환됩니다.

이 값들 중에서 PTY(강수형태)와 TMP(1시간 기온) 정보를 통해 비가 오는지, 기온은 어떠한지 알 수 있습니다.

 

PTY : 0-없음 1-비 2-비/눈 3-눈 4-소나기

 

data = dict()
data['date'] = base_date

weather_data = dict()
for item in items['item']:
    # 기온
    if item['category'] == 'TMP':
        weather_data['tmp'] = item['fcstValue']
    
    # 기상상태
    if item['category'] == 'PTY':
        
        weather_code = item['fcstValue']
        
        if weather_code == '1':
            weather_state = '비'
        elif weather_code == '2':
            weather_state = '비/눈'
        elif weather_code == '3':
            weather_state = '눈'
        elif weather_code == '4':
            weather_state = '소나기'
        else:
            weather_state = '없음'
        
        weather_data['code'] = weather_code
        weather_data['state'] = weather_state

data['weather'] = weather_data
data['weather']
# {'code': '0', 'state': '없음', 'tmp': '9'} # 9도 / 기상 이상 없음

 

Step 3) 미세먼지 정보 가져오기

미세먼지의 PM10과 PM2.5를 가져와보도록 하겠습니다.

 

dust_url = "http://openapi.airkorea.or.kr/openapi/services/rest/ArpltnInforInqireSvc/getCtprvnMesureLIst?"

service_key = "YOUR SERVICE KEY"

item_code_pm10 = "PM10"
item_code_pm25 = "PM25"

data_gubun = "HOUR"
search_condition = "WEEK"

payload = "serviceKey=" + service_key + "&" +\
    "dataType=json" + "&" +\
    "dataGubun=" + data_gubun + "&" +\
    "searchCondition=" + search_condition  + "&" +\
    "itemCode="

# pm10 pm2.5 수치 가져오기
pm10_res = requests.get(dust_url + payload + item_code_pm10)
pm25_res = requests.get(dust_url + payload + item_code_pm25)

미세먼지는 XML 형식으로 제공되기 때문에 날씨 데이터를 다룰 때와는 다른 방식으로 파싱해야 합니다.

# xml 파싱하기
import xml.etree.ElementTree as elemTree

pm10_tree = elemTree.fromstring(pm10_res.text)
pm25_tree = elemTree.fromstring(pm25_res.text)

dust_data = dict()
for tree in [pm10_tree, pm25_tree]:
    item = tree.find("body").find("items").find("item")
    code = item.findtext("itemCode")
    value = int(item.findtext("seoul"))
    
    dust_data[code] = {'value' : value}

# 결과 값
dust_data
# {'PM10': {'value': 94}, 'PM2.5': {'value': 71}}

가져온 미세먼지(PM10)와 초미세먼지(PM2.5)를 기준에 맞춰 상태 값을 추가해주겠습니다.

# PM10 미세먼지 30 80 150
pm10_value = dust_data.get('PM10').get('value')
if pm10_value <= 30:
    pm10_state = "좋음"
elif pm10_value <= 80:
    pm10_state = "보통"
elif pm10_value <= 150:
    pm10_state = "나쁨"
else:
    pm10_state = "매우나쁨"

pm25_value = dust_data.get('PM2.5').get('value')
# PM2.5 초미세먼지 15 35 75
if pm25_value <= 15:
    pm25_state = "좋음"
elif pm25_value <= 35:
    pm25_state = "보통"
elif pm25_value <= 75:
    pm25_state = "나쁨"
else:
    pm25_state = "매우나쁨"

# 미세먼지가 나쁜 상태인지(1)/아닌지(0)
if pm10_value > 80 or  pm25_value > 75:
    dust_code = "1"
else:
    dust_code = "0"

dust_data.get('PM10')['state'] = pm10_state
dust_data.get('PM2.5')['state'] = pm25_state
dust_data['code'] = dust_code

data['dust'] = dust_data
data['dust']
#{
# 'PM10': {'value': 94, 'state': '나쁨'},
# 'PM2.5': {'value': 71, 'state': '나쁨'}
#}

 

# 날씨 정보
# data
{
  'weather': {
    'code': '0', 'state': '없음', 'tmp': '9'
  },
  'date': '20200214',
  'dust': {
    'PM10': {'value': 94, 'state': '나쁨'},
    'PM2.5': {'value': 71, 'state': '나쁨'},
    'code': '1'
  }
}

 

Step 4) 날씨에 따른 음식 데이터 구하기

 

구글에 검색해서 비 오는 날, 미세먼지 많은 날 추천 음식들을 모아봤습니다.

(음식은 검색하고 개인적으로 취합한 것이기에 여러분에 맞춰서 변경하길 추천합니다!)

 

비 오는 날/눈 오는 날
  부대찌개,  아구찜, 해물탕,  칼국수, 수제비,  짬뽕,  우동,  치킨, 맥주,  국밥,  김치부침개,  두부김치,  파전
미세먼지
  콩나물국밥,  고등어,  굴, 쌀국수, 마라탕

 

rain_foods = "부대찌개,아구찜,해물탕,칼국수,수제비,짬뽕,우동,치킨,국밥,김치부침개,두부김치,파전".split(',')
pmhigh_foods = "콩나물국밥,고등어,굴,쌀국수,마라탕".split(',')

해당 날씨의 음식 리스트에서 랜덤으로 추천해주고 평소 날씨의 경우에는 단순히 맛집으로 검색하여 추천해주겠습니다.

 

Step 5) 음식점 검색하기 - 네이버 검색 API

인증

# 네이버 인증
# https://developers.naver.com/apps
# 해당 사이트에서 로그인 후 "Cliend ID"와 "Client Secret"을 얻어오세요
ncreds = {
    "client_id": "<CLIENT_ID>",      
    "client_secret" : "<CLIENT_SECRET>"
}
nheaders = {
    "X-Naver-Client-Id" : ncreds.get('client_id'),
    "X-Naver-Client-Secret" : ncreds.get('client_secret')
}

날씨 조건 설정

기상 : 비/눈/소나기 여부 미세먼지 : 높음 이상 여부 추천 결과
O O 경우 1: 비 오는 날 음식 3개
O X
X O 경우 2: 미세먼지 음식 3개
X X 경우 3: 리뷰 순 맛집 음식 3개 추천
# 경우 1 : 비/눈/소나기           => 비오는날 음식 3개 추천
# 경우 2 : 초/미세먼지 나쁨 이상  => 미세먼지에 좋은 음식 3개 추천
# 경우 3 : 정상                   => 블로그 리뷰 순 맛집 추천

# weather_state
if data.get('weather').get('code') != '0':
    weather_state = '1'
elif data.get('dust').get('code') == '1':
    weather_state = '2'
else:
    weather_state = '3'


음식 리스트 뽑기

import random
# random.sample(x, k=len(x)) 무작위로 리스트 섞기

foods_list = None

# 경우 1, 2, 3
if weather_state == '1':
    foods_list = random.sample(rain_foods, k=len(rain_foods))
elif weather_state == '2':
    foods_list = random.sample(pmhigh_foods, k=len(pmhigh_foods))
else:
    foods_list = ['']

foods_list
# ['쌀국수', '굴', '콩나물국밥', '마라탕', '고등어']

 

네이버 맛집 검색하기

import urllib
# urllib.parse.quote(query) URL에서 검색어를 인코딩하기 위한 라이브러리

# 네이버 지역 검색 주소
naver_local_url = "https://openapi.naver.com/v1/search/local.json?"

# 검색에 사용될 파라미터
# 정렬 sort : 리뷰순(comment)
# 검색어 query : 인코딩된 문자열
params_format = "sort=comment&query="

# 위치는 사용자가 사용할 지역으로 변경가능
location = "국민대"

# 추천된 맛집을 담을 리스트
recommands = []
for food in foods_list:
    # 검색어 지정
    query = location + " " + food + " 맛집"
    # 지역검색 요청 파라메터 설정
    params = "sort=comment" \
              + "&query=" + query \
              + "&display=" + '5'
    
    # 검색
    # headers : 네이버 인증 정보
    res = requests.get(naver_local_url + params, headers=nheaders)
    
    # 맛집 검색 결과
    result_list = res.json().get('items')

    # 경우 3 처리
    # 맛집 검색 결과에서 가장 상위 3개를 가져옴
    if weather_state == '3':
        for i in range(0,3):
            recommands.append(result_list[i])
        break
    
    # 경우 1,2 처리
    # 해당 음식 검색 결과에서 가장 상위를 가져옴
    if result_list:
        recommands.append(result_list[0])
        # 3개를 찾았다면 검색 중단
        if len(recommands) >= 3:
            break
# recommands
[{'title': '사이공본가 정릉점',
  'link': '',
  'category': '음식점>베트남음식',
  'description': '',
  'telephone': '02-914-0420',
  'address': '서울특별시 성북구 정릉동 396-41',
  'roadAddress': '서울특별시 성북구 보국문로11길 23 1층',
  'mapx': '312677',
  'mapy': '556720'},
 {'title': '전주<b>콩나물국밥</b> 본점',
  'link': '',
  'category': '한식>국밥',
  'description': '',
  'telephone': '02-941-4733',
  'address': '서울특별시 성북구 정릉동 284-11',
  'roadAddress': '서울특별시 성북구 보국문로 75 대영빌딩',
  'mapx': '312675',
  'mapy': '556993'},
 {'title': '태극쿵푸<b>마라탕</b>',
  'link': '',
  'category': '중식>중식당',
  'description': '',
  'telephone': '',
  'address': '서울특별시 성북구 정릉동 344-4',
  'roadAddress': '서울특별시 성북구 솔샘로6길 53-1',
  'mapx': '312616',
  'mapy': '556799'}]

 

Step 6) 카카오톡 보내기

좌 : 텍스트 템플릿, 우 : 리스트 템플릿

인증

# 카카오톡 인증
# https://developers.kakao.com/docs/restapi/tool
# 해당 사이트에서 로그인 후 'Access token'을 얻어오세요
kcreds = {
    "access_token" : "<ACCESS TOKEN>"
}
kheaders = {
    "Authorization": "Bearer " + kcreds.get('access_token')
}

날씨 정보 카카오톡 보내기 (텍스트 템플릿)

import json

# 카카오톡 URL 주소
kakaotalk_template_url = "https://kapi.kakao.com/v2/api/talk/memo/default/send"

# 날씨 상세 정보 URL
weather_url = "https://search.naver.com/search.naver?sm=top_hty&fbm=0&ie=utf8&query=%EB%82%A0%EC%94%A8"

# 날씨 정보 만들기 
text = f"""\
#날씨 정보 ({data['date']})
기온 : {data['weather']['tmp']}
기우  : {data['weather']['state']}
미세먼지 : {data['dust']['PM10']['value']} {data['dust']['PM10']['state']}
초미세먼지 : {data['dust']['PM2.5']['value']} {data['dust']['PM2.5']['state']}
"""

# 텍스트 템플릿 형식 만들기
template = {
  "object_type": "text",
  "text": text,
  "link": {
    "web_url": weather_url,
    "mobile_web_url": weather_url
  },
  "button_title": "날씨 상세보기"
}

# JSON 형식 -> 문자열 변환
payload = {
    "template_object" : json.dumps(template)
}

# 카카오톡 보내기
res = requests.post(kakaotalk_template_url, data=payload, headers=kheaders)

if res.json().get('result_code') == 0:
    print('메시지를 성공적으로 보냈습니다.')
else:
    print('메시지를 성공적으로 보내지 못했습니다. 오류메시지 : ' + str(res.json()))

 

 

추천 맛집 카카오톡 보내기 (리스트 템플릿)

# 리스트 템플릿 형식 만들기
contents = []
template = {
    "object_type" : "list",
    "header_title" : "현재 날씨에 따른 음식 추천",
    "header_link" : {
        "web_url": weather_url,
        "mobile_web_url" : weather_url
    },
    "contents" : contents,
    "buttons" : [
        {
            "title" : "날씨 정보 상세보기",
            "link" : {
                "web_url": weather_url,
                "mobile_web_url" : weather_url
            }
        }
    ],
}

# contents 만들기
for place in recommands:
    title = place.get('title')  # 장소 이름
    # title : 태극쿵푸<b>마라탕</b>
    # html 태그 제거
    title = title.replace('<b>','').replace('</b>','')
    
    category = place.get('category')  # 장소 카테고리
    telephone = place.get('telephone')  # 장소 전화번호
    address = place.get('address')  # 장소 지번 주소

    # 각 장소를 클릭할 때 네이버 검색으로 연결해주기 위해 작성된 코드
    enc_address = urllib.parse.quote(address + ' ' + title)
    query = "query=" + enc_address

    # 장소 카테고리가 카페이면 카페 이미지
    # 이외에는 음식 이미지
    if '카페' in category:
        image_url = "https://freesvg.org/img/pitr_Coffee_cup_icon.png"
    else:
        image_url = "https://freesvg.org/img/bentolunch.png?w=150&h=150&fit=fill"

    # 전화번호가 있다면 제목과 함께 넣어줍니다.
    if telephone:
        title = title + "\ntel) " + telephone

    # 카카오톡 리스트 템플릿 형식에 맞춰줍니다.
    content = {
        "title": "[" + category + "] " + title,
        "description": ' '.join(address.split()[1:]),
        "image_url": image_url,
        "image_width": 50, "image_height": 50,
        "link": {
            "web_url": "https://search.naver.com/search.naver?" + query,
            "mobile_web_url": "https://search.naver.com/search.naver?" + query
        }
    }
    
    contents.append(content)

# JSON 형식 -> 문자열 변환
payload = {
    "template_object" : json.dumps(template)
}

# 카카오톡 보내기
res = requests.post(kakaotalk_template_url, data=payload, headers=kheaders)

if res.json().get('result_code') == 0:
    print('메시지를 성공적으로 보냈습니다.')
else:
    print('메시지를 성공적으로 보내지 못했습니다. 오류메시지 : ' + str(res.json()))

 

 

참고

Python 신규 버전(3.6 이상) 부터는 Literal String Interpolation 이라는, 간단히 줄여서 f-string 이라고 불리는 새로운 기능을 제공해 준다. 

text = f"""\
#날씨 정보 ({data['date']}) \
기온 : {data['weather']['tmp']} \
기우  : {data['weather']['state']} \
미세먼지 : {data['dust']['PM10']['value']} {data['dust']['PM10']['state']} \
초미세먼지 : {data['dust']['PM2.5']['value']} {data['dust']['PM2.5']['state']} \
"""

python 3.5 이하 버전을 사용한다면, 아래와 같이 작성하면 같은 의미가 된다.

text = "#날씨 정보 (%s) \n" %(data['date']) \
+ "기온 : %s \n" %(data['weather']['tmp']) \
+ "기우  : %s \n" %(data['weather']['state']) \
+ "미세먼지 : %d %s \n" %(data['dust']['PM10']['value'], data['dust']['PM10']['state']) \
+ "초미세먼지 : %d %s \n" %(data['dust']['PM2.5']['value'], data['dust']['PM2.5']['state'])

 




 

 

저작물의 저작권은 작성자에게 있습니다.

공유는 자유롭게 하시되 댓글 남겨주세요~

상업적 용도로는 무단 사용을 금지합니다.

끝까지 읽어주셔서 감사합니다^^

 

 

반응형
Comments